Add a delay to closing vlc

This gives time for the web engine to appear, preventing any flicker to the desktop
This commit is contained in:
Daniel 2020-11-17 03:59:38 +00:00 committed by Raoul Snyman
parent f47a168733
commit 06e0d9d522
9 changed files with 473 additions and 168 deletions

View File

@ -1016,9 +1016,6 @@ var Display = {
targetElement.style.cssText = "";
targetElement.setAttribute("data-background", backgroundContent);
targetElement.setAttribute("data-background-size", "cover");
if (!!backgroundHtml) {
background.innerHTML = backgroundHtml;
}
// set up the main area
if (!is_text) {

View File

@ -41,8 +41,7 @@ from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import DisplayControllerType, HideMode
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path, \
VIDEO_EXT, AUDIO_EXT
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path
from openlp.core.ui.media.remote import register_views
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
@ -50,6 +49,7 @@ from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
log = logging.getLogger(__name__)
TICK_TIME = 200
HIDE_DELAY_TIME = 2500
class MediaController(RegistryBase, LogMixin, RegistryProperties):
@ -68,10 +68,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# Timer for video state
self.live_timer = QtCore.QTimer()
self.live_timer.setInterval(TICK_TIME)
self.live_hide_timer = QtCore.QTimer()
self.live_hide_timer.setSingleShot(True)
self.live_kill_timer = QtCore.QTimer()
self.live_kill_timer.setSingleShot(True)
self.preview_timer = QtCore.QTimer()
self.preview_timer.setInterval(TICK_TIME)
# Signals
self.live_timer.timeout.connect(self._media_state_live)
self.live_hide_timer.timeout.connect(self._on_media_hide_live)
self.live_kill_timer.timeout.connect(self._on_media_kill_live)
self.preview_timer.timeout.connect(self._media_state_preview)
Registry().register_function('playbackPlay', self.media_play_msg)
Registry().register_function('playbackPause', self.media_pause_msg)
@ -154,31 +160,29 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
Check if there is a running Live media Player and do updating stuff (e.g. update the UI)
"""
display = self._define_display(self._display_controllers(DisplayControllerType.Live))
if DisplayControllerType.Live in self.current_media_players:
self.current_media_players[DisplayControllerType.Live].resize(self.live_controller)
self.current_media_players[DisplayControllerType.Live].update_ui(self.live_controller, display)
self.tick(self._display_controllers(DisplayControllerType.Live))
if self.current_media_players[DisplayControllerType.Live].get_live_state() is not MediaState.Playing:
media_player = self.current_media_players[DisplayControllerType.Live]
media_player.resize(self.live_controller)
media_player.update_ui(self.live_controller, self._define_display(self.live_controller))
if not self.tick(self.live_controller):
self.live_timer.stop()
else:
self.live_timer.stop()
self.media_stop(self._display_controllers(DisplayControllerType.Live))
self.media_stop(self.live_controller)
def _media_state_preview(self):
"""
Check if there is a running Preview media Player and do updating stuff (e.g. update the UI)
"""
display = self._define_display(self._display_controllers(DisplayControllerType.Preview))
if DisplayControllerType.Preview in self.current_media_players:
self.current_media_players[DisplayControllerType.Preview].resize(self.live_controller)
self.current_media_players[DisplayControllerType.Preview].update_ui(self.preview_controller, display)
self.tick(self._display_controllers(DisplayControllerType.Preview))
if self.current_media_players[DisplayControllerType.Preview].get_preview_state() is not MediaState.Playing:
media_player = self.current_media_players[DisplayControllerType.Preview]
media_player.resize(self.preview_controller)
media_player.update_ui(self.preview_controller, self._define_display(self.preview_controller))
if not self.tick(self.preview_controller):
self.preview_timer.stop()
else:
self.preview_timer.stop()
self.media_stop(self._display_controllers(DisplayControllerType.Preview))
self.media_stop(self.preview_controller)
def setup_display(self, controller, preview):
"""
@ -224,9 +228,11 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
is_valid = True
controller = self._display_controllers(source)
log.debug(f'load_video is_live:{controller.is_live}')
# stop running videos
self.media_reset(controller)
controller.media_info = ItemMediaInfo()
controller.media_info.media_type = MediaType.Video
if controller.is_live:
controller.media_info.volume = self.settings.value('media/live volume')
else:
@ -234,6 +240,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# background will always loop video.
if service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
controller.media_info.file_info = service_item.background_audio
controller.media_info.media_type = MediaType.Audio
# is_background indicates we shouldn't override the normal display
controller.media_info.is_background = True
else:
if service_item.is_capable(ItemCapabilities.HasBackgroundStream):
(name, mrl, options) = parse_stream_path(service_item.stream_mrl)
@ -268,6 +277,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.length = service_item.media_length
is_valid = self._check_file_type(controller, display)
controller.media_info.start_time = service_item.start_time
controller.media_info.timer = service_item.start_time
controller.media_info.end_time = service_item.end_time
elif controller.preview_display:
if service_item.is_capable(ItemCapabilities.IsOptical):
@ -292,11 +302,21 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
translate('MediaPlugin.MediaItem', 'Unsupported File'))
return False
self.log_debug('video media type: {tpe} '.format(tpe=str(controller.media_info.media_type)))
# If both the preview and live view have a stream, make sure only the live view continues streaming
if controller.media_info.media_type == MediaType.Stream:
if controller.is_live:
if self.preview_controller.media_info.media_type == MediaType.Stream:
self.log_warning('stream can only be displayed in one instance, killing preview stream')
self.preview_controller.on_media_close()
else:
if self.live_controller.media_info.media_type == MediaType.Stream:
self.log_warning('stream cannot be previewed while also streaming live')
return
autoplay = False
if service_item.requires_media():
if service_item.requires_media() and hidden == HideMode.Theme:
autoplay = True
# Preview requested
if not controller.is_live:
elif not controller.is_live:
autoplay = True
# Visible or background requested or Service Item wants to autostart
elif not hidden or service_item.will_auto_start:
@ -355,6 +375,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
else:
controller.media_info.media_type = MediaType.DVD
controller.media_info.start_time = start
controller.media_info.timer = start
controller.media_info.end_time = end
controller.media_info.length = (end - start)
controller.media_info.title_track = title
@ -386,38 +407,23 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
return True
return False
for file in controller.media_info.file_info:
if file.is_file:
suffix = '*%s' % file.suffix.lower()
file = str(file)
if suffix in VIDEO_EXT:
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Video
return True
if suffix in AUDIO_EXT:
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Audio
return True
else:
file = str(file)
if self.vlc_player.can_folder:
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Video
return True
if not file.is_file and not self.vlc_player.can_folder:
return False
file = str(file)
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
return True
return False
def media_play_msg(self, msg, status=True):
def media_play_msg(self, msg):
"""
Responds to the request to play a loaded video
:param msg: First element is the controller which should be used
:param status:
"""
return self.media_play(msg[0], status)
return self.media_play(msg[0])
def on_media_play(self):
"""
@ -425,14 +431,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
return self.media_play(self.live_controller)
def media_play(self, controller, first_time=True):
def media_play(self, controller):
"""
Responds to the request to play a loaded video
:param controller: The controller to be played
:param first_time:
"""
self.log_debug(f"media_play with {first_time}")
self.log_debug(f'media_play is_live:{controller.is_live}')
controller.seek_slider.blockSignals(True)
controller.volume_slider.blockSignals(True)
display = self._define_display(controller)
@ -441,25 +446,29 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.volume_slider.blockSignals(False)
return False
self.media_volume(controller, controller.media_info.volume)
if first_time:
self.current_media_players[controller.controller_type].set_visible(controller, True)
controller.mediabar.actions['playbackPlay'].setVisible(False)
controller.mediabar.actions['playbackPause'].setVisible(True)
controller.mediabar.actions['playbackStop'].setDisabled(False)
self._media_set_visibility(controller, True)
controller.mediabar.actions['playbackPlay'].setVisible(False)
controller.mediabar.actions['playbackPause'].setVisible(True)
controller.mediabar.actions['playbackStop'].setDisabled(False)
controller.mediabar.actions['playbackLoop'].setChecked(controller.media_info.is_looping_playback)
controller.mediabar.actions['playbackStop'].setVisible(not controller.media_info.is_background or
controller.media_info.media_type is MediaType.Audio)
controller.mediabar.actions['playbackLoop'].setVisible((not controller.media_info.is_background and
controller.media_info.media_type is not MediaType.Stream)
or controller.media_info.media_type is MediaType.Audio)
# Start Timer for ui updates
if controller.is_live:
if controller.hide_menu.defaultAction().isChecked():
controller.hide_menu.defaultAction().trigger()
# Start Timer for ui updates
if not self.live_timer.isActive():
self.live_timer.start()
else:
# Start Timer for ui updates
if not self.preview_timer.isActive():
self.preview_timer.start()
controller.seek_slider.blockSignals(False)
controller.volume_slider.blockSignals(False)
controller.media_info.is_playing = True
if not controller.media_info.is_background:
if controller.is_live:
controller.set_hide_mode(None)
display = self._define_display(controller)
display.hide_display(HideMode.Screen)
controller._set_theme(controller.service_item)
@ -472,30 +481,35 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
Add a tick while the media is playing but only count if not paused
:param controller: The Controller to be processed
:return: Is the video still running?
"""
start_again = False
stopped = False
if controller.media_info.is_playing and controller.media_info.length > 0:
if controller.media_info.timer > controller.media_info.length:
if controller.media_info.timer + TICK_TIME >= controller.media_info.length:
if controller.media_info.is_looping_playback:
start_again = True
else:
self.media_stop(controller)
elif controller.media_info.timer > controller.media_info.length - TICK_TIME * 4:
if not controller.media_info.is_looping_playback:
display = self._define_display(controller)
display.show_display()
stopped = True
controller.media_info.timer += TICK_TIME
seconds = controller.media_info.timer // 1000
minutes = seconds // 60
seconds %= 60
total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60
total_seconds %= 60
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
(minutes, seconds, total_minutes, total_seconds))
self._update_seek_ui(controller)
else:
stopped = True
if start_again:
controller.seek_slider.setSliderPosition(0)
self.media_play(controller, False)
return not stopped
def _update_seek_ui(self, controller):
seconds = controller.media_info.timer // 1000
minutes = seconds // 60
seconds %= 60
total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60
total_seconds %= 60
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
(minutes, seconds, total_minutes, total_seconds))
def media_pause_msg(self, msg):
"""
@ -517,12 +531,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param controller: The Controller to be paused
"""
self.log_debug(f'media_stop is_live:{controller.is_live}')
if controller.controller_type in self.current_media_players:
self.current_media_players[controller.controller_type].pause(controller)
controller.mediabar.actions['playbackPlay'].setVisible(True)
controller.mediabar.actions['playbackStop'].setDisabled(False)
controller.mediabar.actions['playbackPause'].setVisible(False)
controller.media_info.is_playing = False
# Add a tick to the timer to prevent it finishing the video before it can loop back or stop
# If the clip finishes, we hit a bug where we cannot start the video
controller.media_info.timer += TICK_TIME
controller.output_has_changed()
return True
return False
@ -565,9 +583,19 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param controller: The controller that needs to be stopped
"""
self.log_debug(f'media_stop is_live:{controller.is_live}')
if controller.controller_type in self.current_media_players:
self.current_media_players[controller.controller_type].stop(controller)
self.current_media_players[controller.controller_type].set_visible(controller, False)
if controller.is_live:
self.live_hide_timer.start(HIDE_DELAY_TIME)
if not controller.media_info.is_background:
display = self._define_display(controller)
if display.hide_mode == HideMode.Screen:
controller.set_hide_mode(HideMode.Blank)
else:
controller.set_hide_mode(display.hide_mode or HideMode.Blank)
else:
self._media_set_visibility(controller, False)
controller.seek_slider.setSliderPosition(0)
total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60
@ -577,10 +605,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.mediabar.actions['playbackStop'].setDisabled(True)
controller.mediabar.actions['playbackPause'].setVisible(False)
controller.media_info.is_playing = False
controller.media_info.timer = 1000
controller.media_timer = 0
display = self._define_display(controller)
display.show_display()
controller.media_info.timer = 0
controller.output_has_changed()
return True
return False
@ -631,33 +656,76 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
self.current_media_players[controller.controller_type].seek(controller, seek_value)
controller.media_info.timer = seek_value
self._update_seek_ui(controller)
def media_reset(self, controller):
def media_reset(self, controller, delayed=False):
"""
Responds to the request to reset a loaded video
:param controller: The controller to use.
:param delayed: Should the controller briefly remain visible.
"""
self.log_debug('media_reset')
self.set_controls_visible(controller, False)
if controller.controller_type in self.current_media_players:
display = self._define_display(controller)
display.show_display()
hide_mode = controller.get_hide_mode()
if hide_mode is None:
display.show_display()
else:
display.hide_display(hide_mode)
self.current_media_players[controller.controller_type].reset(controller)
self.current_media_players[controller.controller_type].set_visible(controller, False)
del self.current_media_players[controller.controller_type]
if controller.is_live and delayed:
self.live_kill_timer.start(HIDE_DELAY_TIME)
else:
if controller.is_live:
self.live_kill_timer.stop()
else:
self._media_set_visibility(controller, False)
del self.current_media_players[controller.controller_type]
def media_hide(self, msg):
def media_hide_msg(self, msg):
"""
Hide the related video Widget
:param msg: First element is the boolean for Live indication
"""
is_live = msg[1]
if not is_live:
self.media_hide(is_live)
def media_hide(self, is_live, delayed=False):
"""
Pause and hide the related video Widget if is_live
:param is_live: Live indication
:param delayed: Should the controller briefly remain visible.
"""
self.log_debug(f'media_hide is_live:{is_live}')
if not is_live or self.live_kill_timer.isActive():
return
if self.live_controller.controller_type in self.current_media_players and \
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
self.media_pause(self.live_controller)
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, False)
if delayed:
self.live_hide_timer.start(HIDE_DELAY_TIME)
else:
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
def _on_media_hide_live(self):
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
def _on_media_kill_live(self):
self._media_set_visibility(self.live_controller, False)
del self.current_media_players[self.live_controller.controller_type]
def _media_set_visibility(self, controller, visible):
"""
Set the live video Widget visibility
"""
if controller.is_live:
self.live_hide_timer.stop()
visible = visible and controller.media_info.media_type is not MediaType.Audio
self.current_media_players[controller.controller_type].set_visible(controller, visible)
def media_blank(self, msg):
"""
@ -668,13 +736,30 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
is_live = msg[1]
hide_mode = msg[2]
if not is_live:
self.log_debug(f'media_blank is_live:{is_live}')
if not is_live or self.live_controller.controller_type not in self.current_media_players:
return
Registry().execute('live_display_hide', hide_mode)
if self.live_controller.controller_type in self.current_media_players and \
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
self.media_pause(self.live_controller)
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, False)
if self.live_kill_timer.isActive():
# If pressing blank when the video is being removed, remove it instantly
self._media_set_visibility(self.live_controller, False)
self.media_reset(self.live_controller)
return
if not self.live_controller.media_info.is_background:
Registry().execute('live_display_hide', hide_mode)
controller_type = self.live_controller.controller_type
playing = self.current_media_players[controller_type].get_live_state() == MediaState.Playing
if hide_mode == HideMode.Theme:
if not playing:
self.media_play(self.live_controller)
else:
self.live_hide_timer.stop()
else:
if hide_mode == HideMode.Screen:
if playing:
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
else:
self.live_hide_timer.start(HIDE_DELAY_TIME)
def media_unblank(self, msg):
"""
@ -683,24 +768,28 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param msg: First element is not relevant in this context
Second element is the boolean for Live indication
"""
Registry().execute('live_display_show')
is_live = msg[1]
if not is_live:
self.log_debug(f'media_blank is_live:{is_live}')
if not is_live or self.live_kill_timer.isActive():
return
if self.live_controller.controller_type in self.current_media_players and \
self.current_media_players[self.live_controller.controller_type].get_live_state() != \
MediaState.Playing:
if self.media_play(self.live_controller):
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, True)
# Start Timer for ui updates
if not self.live_timer.isActive():
self.live_timer.start()
Registry().execute('live_display_show')
if self.live_controller.controller_type in self.current_media_players:
if self.current_media_players[self.live_controller.controller_type].get_live_state() != \
MediaState.Playing:
self.media_play(self.live_controller)
else:
self._media_set_visibility(self.live_controller, True)
if not self.live_controller.media_info.is_background:
display = self._define_display(self.live_controller)
display.hide_display(HideMode.Screen)
def finalise(self):
"""
Reset all the media controllers when OpenLP shuts down
"""
self.live_timer.stop()
self.live_hide_timer.stop()
self.live_kill_timer.stop()
self.preview_timer.stop()
self.media_reset(self._display_controllers(DisplayControllerType.Live))
self.media_reset(self._display_controllers(DisplayControllerType.Preview))

View File

@ -134,7 +134,6 @@ class VlcPlayer(MediaPlayer):
# creating an empty vlc media player
controller.vlc_media_player = controller.vlc_instance.media_player_new()
controller.vlc_widget.resize(controller.size())
controller.vlc_widget.raise_()
controller.vlc_widget.hide()
# The media player has to be 'connected' to the QFrame.
# (otherwise a video would be displayed in it's own window)
@ -245,22 +244,22 @@ class VlcPlayer(MediaPlayer):
start_time = 0
log.debug('vlc play')
if controller.is_live:
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
if self.get_live_state() != MediaState.Paused and controller.media_info.timer > 0:
start_time = controller.media_info.timer
else:
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
if self.get_preview_state() != MediaState.Paused and controller.media_info.timer > 0:
start_time = controller.media_info.timer
threading.Thread(target=controller.vlc_media_player.play).start()
if not self.media_state_wait(controller, vlc.State.Playing):
return False
if controller.is_live:
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
if self.get_live_state() != MediaState.Paused and controller.media_info.timer > 0:
log.debug('vlc play, start time set')
start_time = controller.media_info.start_time
start_time = controller.media_info.timer
else:
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
if self.get_preview_state() != MediaState.Paused and controller.media_info.timer > 0:
log.debug('vlc play, start time set')
start_time = controller.media_info.start_time
start_time = controller.media_info.timer
log.debug('mediatype: ' + str(controller.media_info.media_type))
# Set tracks for the optical device
if controller.media_info.media_type == MediaType.DVD and \
@ -278,10 +277,10 @@ class VlcPlayer(MediaPlayer):
if controller.media_info.subtitle_track > 0:
controller.vlc_media_player.video_set_spu(controller.media_info.subtitle_track)
log.debug('vlc play, subtitle_track set: ' + str(controller.media_info.subtitle_track))
if controller.media_info.start_time > 0:
log.debug('vlc play, starttime set: ' + str(controller.media_info.start_time))
start_time = controller.media_info.start_time
controller.media_info.length = controller.media_info.end_time - controller.media_info.start_time
if controller.media_info.timer > 0:
log.debug('vlc play, starttime set: ' + str(controller.media_info.timer))
start_time = controller.media_info.timer
controller.media_info.length = controller.media_info.end_time - controller.media_info.timer
self.volume(controller, controller.media_info.volume)
if start_time > 0 and controller.vlc_media_player.is_seekable():
controller.vlc_media_player.set_time(int(start_time))
@ -343,7 +342,6 @@ class VlcPlayer(MediaPlayer):
:param controller: The controller where the media is
"""
controller.vlc_media_player.stop()
controller.vlc_widget.setVisible(False)
self.set_state(MediaState.Off, controller)
def set_visible(self, controller, status):
@ -362,14 +360,6 @@ class VlcPlayer(MediaPlayer):
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
"""
vlc = get_vlc()
# Stop video if playback is finished.
if controller.vlc_media.get_state() == vlc.State.Ended:
self.stop(controller)
if controller.media_info.end_time > 0:
if controller.vlc_media_player.get_time() > controller.media_info.end_time:
self.stop(controller)
self.set_visible(controller, False)
if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True)
if controller.media_info.media_type == MediaType.CD \

View File

@ -525,6 +525,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.song_menu.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.song_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Go To'), self.toolbar))
def _raise_displays(self):
for display in self.displays:
display.raise_()
def _slide_shortcut_activated(self):
"""
Called, when a shortcut has been activated to jump to a chorus, verse, etc.
@ -946,8 +950,12 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
self.info_label.setText(self.service_item.title)
self.slide_list = {}
if old_item and old_item.requires_media():
self.on_media_close()
if old_item:
# Close the old item if it's not to be used by the new sevice 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])
row = 0
width = self.main_window.control_splitter.sizes()[self.split]
if self.service_item.is_text():
@ -989,26 +997,15 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if self.service_item.is_command():
self.preview_display.load_verses(media_empty_song, True)
self.on_media_start(self.service_item)
# Let media window init, then put webengine back on top
self.application.process_events()
for display in self.displays:
display.raise_()
# Try to get display back on top of media window asap. If the media window
# is not loaded by the time _raise_displays is run, lyrics (web display)
# will be under the media window (not good).
QtCore.QTimer.singleShot(100, self._raise_displays)
QtCore.QTimer.singleShot(500, self._raise_displays)
QtCore.QTimer.singleShot(1000, self._raise_displays)
self.slide_selected(True)
if self.service_item.from_service:
self.preview_widget.setFocus()
if old_item:
# Close the old item after the new one is opened
# This avoids the service theme/desktop flashing on screen
# However opening a new item of the same type will automatically
# close the previous, so make sure we don't close the new one.
if old_item.is_command() and not self.service_item.is_command() or \
old_item.is_command() and not old_item.is_media() and self.service_item.is_media():
if old_item.is_media():
self.on_media_close()
else:
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
if old_item.is_media() and not self.service_item.is_media():
self.on_media_close()
if self.is_live:
Registry().execute('slidecontroller_{item}_started'.format(item=self.type_prefix), [self.service_item])
# Need to process events four times to get correct controller width
@ -1528,7 +1525,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
:param item: The service item to be processed
"""
if State().check_preconditions('media'):
if self.is_live and self.get_hide_mode() == HideMode.Theme:
if self.is_live and not item.is_media() and item.requires_media():
self.media_controller.load_video(self.controller_type, item, self.get_hide_mode())
elif self.is_live and self.get_hide_mode() == HideMode.Theme:
self.media_controller.load_video(self.controller_type, item, HideMode.Blank)
self.set_hide_mode(HideMode.Blank)
elif self.is_live or item.is_media():
@ -1542,7 +1541,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
Respond to a request to close the Video if we have media
"""
if State().check_preconditions('media'):
self.media_controller.media_reset(self)
self.media_controller.media_reset(self, delayed=True)
def _reset_blank(self, no_theme):
"""

View File

@ -68,7 +68,7 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
start = self.hour_spin_box.value() * 3600 + self.minute_spin_box.value() * 60 + self.second_spin_box.value()
end = self.hour_finish_spin_box.value() * 3600 + \
self.minute_finish_spin_box.value() * 60 + self.second_finish_spin_box.value()
if end > self.item['service_item'].media_length:
if end > self.item['service_item'].media_length // 1000:
critical_error_message_box(title=translate('OpenLP.StartTime_form', 'Time Validation Error'),
message=translate('OpenLP.StartTime_form',
'Finish time is set after the end of the media item'))
@ -78,14 +78,15 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
message=translate('OpenLP.StartTime_form',
'Start time is after the finish time of the media item'))
return
self.item['service_item'].start_time = start
self.item['service_item'].end_time = end
self.item['service_item'].start_time = start * 1000
self.item['service_item'].end_time = end * 1000
return QtWidgets.QDialog.accept(self)
def _time_split(self, seconds):
def _time_split(self, milliseconds):
"""
Split time up into hours minutes and seconds from seconds
Split time up into hours minutes and seconds from milliseconds
"""
seconds = milliseconds // 1000
hours = seconds // 3600
seconds -= 3600 * hours
minutes = seconds // 60

View File

@ -26,9 +26,9 @@ import pytest
from unittest.mock import MagicMock, patch
from openlp.core.common.registry import Registry
from openlp.core.ui import DisplayControllerType
from openlp.core.ui import DisplayControllerType, HideMode
from openlp.core.ui.media.mediacontroller import MediaController
from openlp.core.ui.media import ItemMediaInfo
from openlp.core.ui.media import ItemMediaInfo, MediaState
from tests.utils.constants import RESOURCE_PATH
@ -60,6 +60,36 @@ def test_resize(media_env):
mocked_player.resize.assert_called_with(mocked_display)
def test_load_video(media_env, settings):
"""
Test that the load_video runs correctly
"""
# GIVEN: A media controller and a service item
mocked_slide_controller = MagicMock()
mocked_service_item = MagicMock()
mocked_service_item.is_capable.return_value = False
settings.setValue('media/live volume', 1)
media_env.media_controller.current_media_players = MagicMock()
media_env.media_controller._check_file_type = MagicMock(return_value=True)
media_env.media_controller._display_controllers = MagicMock(return_value=mocked_slide_controller)
media_env.media_controller._define_display = MagicMock()
media_env.media_controller.media_reset = MagicMock()
media_env.media_controller.media_play = MagicMock()
media_env.media_controller.set_controls_visible = MagicMock()
# WHEN: load_video() is called
media_env.media_controller.load_video(DisplayControllerType.Live, mocked_service_item)
# THEN: The current controller's media should be reset
# The volume should be set from the settings
# The video should have autoplayed
# The controls should have been made visible
media_env.media_controller.media_reset.assert_called_once_with(mocked_slide_controller)
assert mocked_slide_controller.media_info.volume == 1
media_env.media_controller.media_play.assert_called_once_with(mocked_slide_controller)
media_env.media_controller.set_controls_visible.assert_called_once_with(mocked_slide_controller, True)
def test_check_file_type_null(media_env):
"""
Test that we don't try to play media when no players available
@ -125,10 +155,10 @@ def test_media_play_msg(media_env):
# WHEN: media_play_msg() is called
with patch.object(media_env.media_controller, u'media_play') as mocked_media_play:
media_env.media_controller.media_play_msg(message, False)
media_env.media_controller.media_play_msg(message)
# THEN: The underlying method is called
mocked_media_play.assert_called_with(1, False)
mocked_media_play.assert_called_with(1)
def test_media_pause_msg(media_env):
@ -161,6 +191,33 @@ def test_media_stop_msg(media_env):
mocked_media_stop.assert_called_with(1)
def test_media_stop(media_env):
"""
Test that the media controller takes the correct actions when stopping media
"""
# GIVEN: A live media controller and a message with two elements
mocked_slide_controller = MagicMock()
mocked_media_player = MagicMock()
mocked_display = MagicMock(hide_mode=None)
mocked_slide_controller.controller_type = 'media player'
mocked_slide_controller.media_info = MagicMock(is_background=False)
mocked_slide_controller.set_hide_mode = MagicMock()
mocked_slide_controller.is_live = True
media_env.media_controller.current_media_players = {'media player': mocked_media_player}
media_env.media_controller.live_hide_timer = MagicMock()
media_env.media_controller._define_display = MagicMock(return_value=mocked_display)
# WHEN: media_stop() is called
result = media_env.media_controller.media_stop(mocked_slide_controller)
# THEN: Result should be successful, media player should be stopped and the hide timer should have started
# The controller's hide mode should be set to Blank
assert result is True
mocked_media_player.stop.assert_called_once_with(mocked_slide_controller)
media_env.media_controller.live_hide_timer.start.assert_called_once()
mocked_slide_controller.set_hide_mode.assert_called_once_with(HideMode.Blank)
def test_media_volume_msg(media_env):
"""
Test that the media controller responds to the request to change the volume
@ -191,6 +248,59 @@ def test_media_seek_msg(media_env):
mocked_media_seek.assert_called_with(1, 800)
def test_media_reset(media_env):
"""
Test that the media controller conducts the correct actions when resetting
"""
# GIVEN: A media controller, mocked slide controller, mocked media player and mocked display
mocked_slide_controller = MagicMock()
mocked_media_player = MagicMock()
mocked_display = MagicMock(hide_mode=None)
mocked_slide_controller.controller_type = 'media player'
mocked_slide_controller.media_info = MagicMock(is_background=False)
mocked_slide_controller.get_hide_mode = MagicMock(return_value=None)
mocked_slide_controller.is_live = False
media_env.media_controller.current_media_players = {'media player': mocked_media_player}
media_env.media_controller.live_hide_timer = MagicMock()
media_env.media_controller._define_display = MagicMock(return_value=mocked_display)
media_env.media_controller._media_set_visibility = MagicMock()
# WHEN: media_reset() is called
media_env.media_controller.media_reset(mocked_slide_controller)
# THEN: The display should be shown, media should be hidden and removed
mocked_display.show_display.assert_called_once_with()
media_env.media_controller._media_set_visibility.assert_called_once_with(mocked_slide_controller, False)
assert 'media player' not in media_env.media_controller.current_media_players
def test_media_hide(media_env, registry):
"""
Test that the media controller conducts the correct actions when hiding
"""
# GIVEN: A media controller, mocked slide controller, mocked media player and mocked display
mocked_slide_controller = MagicMock()
mocked_media_player = MagicMock()
mocked_media_player.get_live_state.return_value = MediaState.Playing
mocked_slide_controller.controller_type = 'media player'
mocked_slide_controller.media_info = MagicMock(is_background=False)
mocked_slide_controller.get_hide_mode = MagicMock(return_value=None)
mocked_slide_controller.is_live = False
Registry().register('live_controller', mocked_slide_controller)
media_env.media_controller.current_media_players = {'media player': mocked_media_player}
media_env.media_controller.live_kill_timer = MagicMock(isActive=MagicMock(return_value=False))
media_env.media_controller._media_set_visibility = MagicMock()
media_env.media_controller.media_pause = MagicMock()
# WHEN: media_hide() is called
media_env.media_controller.media_hide(is_live=True)
# THEN: media should be paused and hidden, but the player should still exist
media_env.media_controller.media_pause.assert_called_once_with(mocked_slide_controller)
media_env.media_controller._media_set_visibility.assert_called_once_with(mocked_slide_controller, False)
assert 'media player' in media_env.media_controller.current_media_players
def test_media_length(media_env):
"""
Test the Media Info basic functionality
@ -334,6 +444,7 @@ def test_media_play(media_env):
media_env.current_media_players = MagicMock()
Registry().register('settings', MagicMock())
media_env.live_timer = MagicMock()
media_env.live_hide_timer = MagicMock()
mocked_controller = MagicMock()
mocked_controller.media_info.is_background = False
@ -343,4 +454,5 @@ def test_media_play(media_env):
# THEN: The web display should become transparent (only tests that the theme is reset here)
# And the function should return true to indicate success
assert result is True
media_env.live_hide_timer.stop.assert_called_once_with()
mocked_controller._set_theme.assert_called_once()

View File

@ -122,7 +122,6 @@ def test_setup(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win,
assert mocked_output_display.vlc_media_player == mocked_media_player_new
mocked_output_display.size.assert_called_with()
mocked_qframe.resize.assert_called_with((10, 10))
mocked_qframe.raise_.assert_called_with()
mocked_qframe.hide.assert_called_with()
mocked_media_player_new.set_xwindow.assert_called_with(2)
assert vlc_player.has_own_widget is True
@ -848,7 +847,7 @@ def test_reset():
# THEN: The media should be stopped and invisible
mocked_display.vlc_media_player.stop.assert_called_with()
mocked_display.vlc_widget.setVisible.assert_called_with(False)
mocked_display.vlc_widget.setVisible.assert_not_called()
assert MediaState.Off == vlc_player.get_live_state()
@ -886,15 +885,10 @@ def test_update_ui(mocked_get_vlc):
vlc_player = VlcPlayer(None)
# WHEN: update_ui() is called
with patch.object(vlc_player, 'stop') as mocked_stop, \
patch.object(vlc_player, 'set_visible') as mocked_set_visible:
vlc_player.update_ui(mocked_controller, mocked_display)
vlc_player.update_ui(mocked_controller, mocked_display)
# THEN: Certain methods should be called
mocked_stop.assert_called_with(mocked_controller)
assert 2 == mocked_stop.call_count
mocked_controller.vlc_media_player.get_time.assert_called_with()
mocked_set_visible.assert_called_with(mocked_controller, False)
mocked_controller.seek_slider.setSliderPosition.assert_called_with(400000)
expected_calls = [call(True), call(False)]
assert expected_calls == mocked_controller.seek_slider.blockSignals.call_args_list
@ -921,12 +915,9 @@ def test_update_ui_dvd(mocked_get_vlc):
vlc_player = VlcPlayer(None)
# WHEN: update_ui() is called
with patch.object(vlc_player, 'stop') as mocked_stop:
vlc_player.update_ui(mocked_controller, mocked_display)
vlc_player.update_ui(mocked_controller, mocked_display)
# THEN: Certain methods should be called
mocked_stop.assert_called_with(mocked_controller)
assert 1 == mocked_stop.call_count
mocked_controller.vlc_media_player.get_time.assert_called_with()
mocked_controller.seek_slider.setSliderPosition.assert_called_with(200)
expected_calls = [call(True), call(False)]

View File

@ -899,11 +899,137 @@ def test_process_item(mocked_execute, registry, state_media):
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to stop the presentation
assert 1 == mocked_execute.call_count, 'Execute should have been called 2 times'
assert 1 == mocked_execute.call_count, 'Execute should have been called once'
assert 'mocked_presentation_item_stop' == mocked_execute.call_args_list[0][0][0], \
'The presentation should have been stopped.'
@patch.object(Registry, 'execute')
def test_process_item_transition(mocked_execute, registry, state_media):
"""
Test that the correct actions are taken when a media service-item is closed followed by a image service-item
"""
# GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute
# and a slide controller with many mocks.
# and the setting 'themes/item transitions' = True
mocked_pres_item = MagicMock()
mocked_pres_item.name = 'mocked_image_item'
mocked_pres_item.is_command.return_value = True
mocked_pres_item.is_media.return_value = True
mocked_pres_item.is_image.return_value = False
mocked_pres_item.from_service = False
mocked_pres_item.get_frames.return_value = []
mocked_media_item = MagicMock()
mocked_media_item.name = 'mocked_media_item'
mocked_media_item.get_transition_delay.return_value = 0
mocked_media_item.is_text.return_value = False
mocked_media_item.is_command.return_value = False
mocked_media_item.is_media.return_value = False
mocked_media_item.requires_media.return_value = False
mocked_media_item.is_image.return_value = True
mocked_media_item.from_service = False
mocked_media_item.get_frames.return_value = []
mocked_settings = MagicMock()
mocked_settings.value.return_value = True
mocked_main_window = MagicMock()
Registry().register('main_window', mocked_main_window)
Registry().register('media_controller', MagicMock())
Registry().register('application', MagicMock())
Registry().register('settings', mocked_settings)
slide_controller = SlideController(None)
slide_controller.service_item = mocked_pres_item
slide_controller.is_live = True
slide_controller._reset_blank = MagicMock()
slide_controller.preview_widget = MagicMock()
slide_controller.preview_display = MagicMock()
slide_controller.enable_tool_bar = MagicMock()
slide_controller.on_controller_size_changed = MagicMock()
slide_controller.on_media_start = MagicMock()
slide_controller.on_media_close = MagicMock()
slide_controller.slide_selected = MagicMock()
slide_controller.new_song_menu = MagicMock()
slide_controller.on_stop_loop = MagicMock()
slide_controller.info_label = MagicMock()
slide_controller.song_menu = MagicMock()
slide_controller.displays = [MagicMock()]
slide_controller.toolbar = MagicMock()
slide_controller.split = 0
slide_controller.type_prefix = 'test'
slide_controller.screen_capture = 'old_capture'
# WHEN: _process_item is called
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to start the live item
# Media should be closed
# Controller size change should be called (because it's a live item and the interface might have changed)
# The screen capture should have been reset to none
assert 1 == mocked_execute.call_count, 'Execute should have been called once'
slide_controller.on_media_close.assert_called_once_with()
slide_controller.on_controller_size_changed.assert_called_once()
assert slide_controller.screen_capture is None
@patch.object(Registry, 'execute')
def test_process_item_text(mocked_execute, registry, state_media):
"""
Test that the correct actions are taken a text item is processed
"""
# GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute
# and a slide controller with many mocks.
# and the setting 'themes/item transitions' = True
mocked_media_item = MagicMock()
mocked_media_item.name = 'mocked_media_item'
mocked_media_item.get_transition_delay.return_value = 0
mocked_media_item.is_text.return_value = True
mocked_media_item.is_command.return_value = False
mocked_media_item.is_media.return_value = False
mocked_media_item.requires_media.return_value = False
mocked_media_item.is_image.return_value = False
mocked_media_item.from_service = False
mocked_media_item.get_frames.return_value = []
mocked_media_item.display_slides = [{'verse': 'Verse name'}]
mocked_settings = MagicMock()
mocked_settings.value.return_value = True
mocked_main_window = MagicMock()
Registry().register('main_window', mocked_main_window)
Registry().register('media_controller', MagicMock())
Registry().register('application', MagicMock())
Registry().register('settings', mocked_settings)
slide_controller = SlideController(None)
slide_controller.service_item = None
slide_controller.is_live = True
slide_controller._reset_blank = MagicMock()
slide_controller.preview_widget = MagicMock()
slide_controller.preview_display = MagicMock()
slide_controller.enable_tool_bar = MagicMock()
slide_controller.on_controller_size_changed = MagicMock()
slide_controller.on_media_start = MagicMock()
slide_controller.on_media_close = MagicMock()
slide_controller.slide_selected = MagicMock()
slide_controller.new_song_menu = MagicMock()
slide_controller.on_stop_loop = MagicMock()
slide_controller.info_label = MagicMock()
slide_controller.song_menu = MagicMock()
slide_controller.displays = [MagicMock()]
slide_controller.toolbar = MagicMock()
slide_controller.split = 0
slide_controller.type_prefix = 'test'
slide_controller.screen_capture = 'old_capture'
# WHEN: _process_item is called
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to start the live item
# Controller size change should be called (because it's a live item and the interface might have changed)
# The screen capture should have been reset to none
# The slide should have been added to the slide list with the correct index
assert 1 == mocked_execute.call_count, 'Execute should have been called once'
slide_controller.on_controller_size_changed.assert_called_once()
assert slide_controller.screen_capture is None
assert slide_controller.slide_list['Verse name'] == 0
@patch.object(Registry, 'execute')
def test_process_item_song_vlc(mocked_execute, registry, state_media):
"""

View File

@ -65,9 +65,9 @@ def test_time_display(form):
"""
# GIVEN: A service item with with time
mocked_serviceitem = MagicMock()
mocked_serviceitem.start_time = 61
mocked_serviceitem.end_time = 3701
mocked_serviceitem.media_length = 3701
mocked_serviceitem.start_time = 61000
mocked_serviceitem.end_time = 3701000
mocked_serviceitem.media_length = 3701000
# WHEN displaying the UI and pressing enter
form.item = {'service_item': mocked_serviceitem}
@ -80,7 +80,7 @@ def test_time_display(form):
assert form.hour_spin_box.value() == 0
assert form.minute_spin_box.value() == 1
assert form.second_spin_box.value() == 1
assert form.item['service_item'].start_time == 61, 'The start time should stay the same'
assert form.item['service_item'].start_time == 61000, 'The start time should stay the same'
# WHEN displaying the UI, changing the time to 2min 3secs and pressing enter
form.item = {'service_item': mocked_serviceitem}
@ -95,4 +95,4 @@ def test_time_display(form):
assert form.hour_spin_box.value() == 0
assert form.minute_spin_box.value() == 2
assert form.second_spin_box.value() == 3
assert form.item['service_item'].start_time == 123, 'The start time should have changed'
assert form.item['service_item'].start_time == 123000, 'The start time should have changed'