Merge branch 'fix-song-flash-840' into 'master'

Fix song flash

Closes #840

See merge request openlp/openlp!367
This commit is contained in:
Raoul Snyman 2021-11-07 00:52:53 +00:00
commit e4036ea14f
4 changed files with 78 additions and 16 deletions

View File

@ -262,6 +262,7 @@ var Display = {
_animationState: AnimationState.NoAnimation, _animationState: AnimationState.NoAnimation,
_doTransitions: false, _doTransitions: false,
_doItemTransitions: false, _doItemTransitions: false,
_skipNextTransition: false,
_themeApplied: true, _themeApplied: true,
_revealConfig: { _revealConfig: {
margin: 0.0, margin: 0.0,
@ -365,7 +366,7 @@ var Display = {
Display.applyTheme(new_slides, is_text); Display.applyTheme(new_slides, is_text);
Display._slidesContainer.prepend(new_slides); Display._slidesContainer.prepend(new_slides);
var currentSlide = Reveal.getIndices(); var currentSlide = Reveal.getIndices();
if (Display._doItemTransitions && Display._slidesContainer.children.length >= 2) { if (Display._doItemTransitions && Display._slidesContainer.children.length >= 2 && !Display._skipNextTransition) {
// Set the slide one section ahead so we'll stay on the old slide after reinit // Set the slide one section ahead so we'll stay on the old slide after reinit
Reveal.slide(1, currentSlide.v); Reveal.slide(1, currentSlide.v);
Display.reinit(); Display.reinit();
@ -375,6 +376,7 @@ var Display = {
Reveal.slide(0, currentSlide.v); Reveal.slide(0, currentSlide.v);
Reveal.sync(); Reveal.sync();
Display._removeLastSection(); Display._removeLastSection();
Display._skipNextTransition = false;
} }
}, },
/** /**
@ -1121,6 +1123,18 @@ var Display = {
} }
} }
}, },
/**
* Called whenever openlp wants to finish completely with the current text/image slides
* because a different window (eg presentation or vlc) is going to be displaying the next item
* and we don't want any flashbacks to the current slide contents
*/
finishWithCurrentItem: function () {
Display.setTextSlide('');
var documentBody = $("body")[0];
documentBody.style.opacity = 1;
Display._skipNextTransition = true;
displayWatcher.pleaseRepaint();
},
/** /**
* Return the video types supported by the video tag * Return the video types supported by the video tag
*/ */

View File

@ -51,6 +51,10 @@ class DisplayWatcher(QtCore.QObject):
""" """
initialised = QtCore.pyqtSignal(bool) initialised = QtCore.pyqtSignal(bool)
def __init__(self, parent):
super().__init__()
self._display_window = parent
@QtCore.pyqtSlot(bool) @QtCore.pyqtSlot(bool)
def setInitialised(self, is_initialised): def setInitialised(self, is_initialised):
""" """
@ -59,6 +63,13 @@ class DisplayWatcher(QtCore.QObject):
log.info('Display is initialised: {init}'.format(init=is_initialised)) log.info('Display is initialised: {init}'.format(init=is_initialised))
self.initialised.emit(is_initialised) self.initialised.emit(is_initialised)
@QtCore.pyqtSlot()
def pleaseRepaint(self):
"""
Called from the js in the webengine view when it's requesting a repaint by Qt
"""
self._display_window.webview.update()
class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin): class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
""" """
@ -451,6 +462,16 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
if self.is_display and self.hide_mode == HideMode.Screen: if self.is_display and self.hide_mode == HideMode.Screen:
self.setVisible(False) self.setVisible(False)
def finish_with_current_item(self):
"""
This is called whenever the song/image display is followed by eg a presentation or video which
has its own display.
This function ensures that the current item won't flash momentarily when the webengineview
is displayed for a subsequent song or image.
"""
self.run_javascript('Display.finishWithCurrentItem();', True)
self.webview.update()
def set_scale(self, scale): def set_scale(self, scale):
""" """
Set the HTML scale Set the HTML scale

View File

@ -945,23 +945,19 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
[self.service_item, self.is_live, self._current_hide_mode, slide_no]) [self.service_item, self.is_live, self._current_hide_mode, slide_no])
else: else:
self._set_theme(self.service_item) self._set_theme(self.service_item)
# Reset blanking if needed
if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
self.info_label.setText(self.service_item.title) self.info_label.setText(self.service_item.title)
self.slide_list = {} self.slide_list = {}
if old_item: # if the old item was text or images (ie doesn't provide its own display) and the new item provides its own
# Close the old item if it's not to be used by the new service item # display then clear out the old item so that it doesn't flash momentarily when next showing text/image
if not self.service_item.is_media() and not self.service_item.requires_media(): # An item provides its own display if the capability ProvidesOwnDisplay is set or if it's a media item
self.on_media_close() old_item_provides_own_display = old_item and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
if old_item.is_command() and not old_item.is_media(): old_item.is_media())
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live]) new_item_provides_own_display = (self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
# if the old item was media which hid the main display then need to reset it if new service item uses it self.service_item.is_media())
if self.is_live and self._current_hide_mode is None and old_item.is_media() and not \ if self.is_live and not old_item_provides_own_display and new_item_provides_own_display:
old_item.requires_media() and not self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay): for display in self.displays:
for display in self.displays: display.finish_with_current_item()
display.show_display() # Prepare the new slides for text / image items
row = 0 row = 0
width = self.main_window.control_splitter.sizes()[self.split] width = self.main_window.control_splitter.sizes()[self.split]
if self.service_item.is_text(): if self.service_item.is_text():
@ -997,7 +993,23 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
row += 1 row += 1
self.slide_list[str(row)] = row - 1 self.slide_list[str(row)] = row - 1
self.preview_widget.replace_service_item(self.service_item, width, slide_no) self.preview_widget.replace_service_item(self.service_item, width, slide_no)
# Tidy up aspects associated with the old item
if old_item:
# Close the old item if it's not to be used by the new service item
if not self.service_item.is_media() and not self.service_item.requires_media():
self.on_media_close()
if old_item.is_command() and not old_item.is_media():
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
# if the old item was media which hid the main display then need to reset it if new service item uses it
if self.is_live and self._current_hide_mode is None and old_item.is_media() and not \
old_item.requires_media() and not self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
for display in self.displays:
display.show_display()
self.enable_tool_bar(self.service_item) self.enable_tool_bar(self.service_item)
# Reset blanking if needed
if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
if self.service_item.is_media() or self.service_item.requires_media(): if self.service_item.is_media() or self.service_item.requires_media():
self._set_theme(self.service_item) self._set_theme(self.service_item)
if self.service_item.is_command(): if self.service_item.is_command():

View File

@ -622,3 +622,18 @@ def test_display_watcher_set_initialised():
# THEN: initialised should have been emitted # THEN: initialised should have been emitted
mocked_initialised.emit.assert_called_once_with(True) mocked_initialised.emit.assert_called_once_with(True)
def test_display_watcher_please_repaint(display_window_env, mock_settings):
"""
Test that the repaint is initiated
"""
# GIVEN: A DisplayWindow instance with mocked out webview
display_window = DisplayWindow()
display_window.webview = MagicMock()
# WHEN: pleaseRepaint is called on the DisplayWatcher
display_window.display_watcher.pleaseRepaint()
# THEN: Qt update for the webview should have been triggered
assert display_window.webview.update.call_count == 1