diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index b15353b44..f215ebc66 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -274,10 +274,10 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): else: self.log_debug('video is not optical or stream, but live') controller.media_info.length = service_item.media_length + 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 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): self.log_debug('video is optical and preview') @@ -294,6 +294,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): else: self.log_debug('video is not optical or stream, but preview') controller.media_info.length = service_item.media_length + 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 is_valid = self._check_file_type(controller, display) if not is_valid: # Media could not be loaded correctly @@ -383,10 +386,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): self.vlc_player.load(controller, display, filename) self.resize(controller, self.vlc_player) self.current_media_players[controller.controller_type] = self.vlc_player - if audio_track == -1 and subtitle_track == -1: - controller.media_info.media_type = MediaType.CD - else: - controller.media_info.media_type = MediaType.DVD return True def _check_file_type(self, controller, display): @@ -482,14 +481,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): 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.start_time + TICK_TIME >= \ - controller.media_info.length: + controller.media_info.timer += TICK_TIME + if controller.media_info.timer >= controller.media_info.start_time + controller.media_info.length: if controller.media_info.is_looping_playback: start_again = True else: self.media_stop(controller) stopped = True - controller.media_info.timer += TICK_TIME self._update_seek_ui(controller) else: stopped = True @@ -500,14 +498,14 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): return not stopped def _update_seek_ui(self, controller): - seconds = (controller.media_info.timer - controller.media_info.start_time) // 1000 + 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 + end_seconds = controller.media_info.end_time // 1000 + end_minutes = end_seconds // 60 + end_seconds %= 60 controller.position_label.setText(' %02d:%02d / %02d:%02d' % - (minutes, seconds, total_minutes, total_seconds)) + (minutes, seconds, end_minutes, end_seconds)) def media_pause_msg(self, msg): """ @@ -593,16 +591,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): controller.set_hide_mode(display.hide_mode or HideMode.Blank) else: self._media_set_visibility(controller, False) - controller.seek_slider.setSliderPosition(controller.media_info.start_time) - total_seconds = controller.media_info.length // 1000 - total_minutes = total_seconds // 60 - total_seconds %= 60 - controller.position_label.setText(' %02d:%02d / %02d:%02d' % (0, 0, total_minutes, total_seconds)) controller.mediabar.actions['playbackPlay'].setVisible(True) controller.mediabar.actions['playbackStop'].setDisabled(True) controller.mediabar.actions['playbackPause'].setVisible(False) controller.media_info.is_playing = False controller.media_info.timer = controller.media_info.start_time + controller.seek_slider.setSliderPosition(controller.media_info.start_time) + self._update_seek_ui(controller) controller.output_has_changed() return True return False @@ -651,9 +646,11 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties): :param controller: The controller to use. :param seek_value: The value to set. """ - self.current_media_players[controller.controller_type].seek(controller, seek_value) - controller.media_info.timer = seek_value - self._update_seek_ui(controller) + # This may be triggered by setting the slider max/min before the current_media_players dict is set + if controller.controller_type in self.current_media_players: + 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, delayed=False): """ diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 32339ca00..83ac20198 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -132,6 +132,7 @@ class VlcPlayer(MediaPlayer): controller.vlc_instance = vlc.Instance(command_line_options) if not controller.vlc_instance: return + log.debug(f"VLC version: {vlc.libvlc_get_version()}") # creating an empty vlc media player controller.vlc_media_player = controller.vlc_instance.media_player_new() controller.vlc_widget.resize(controller.size()) @@ -189,18 +190,43 @@ class VlcPlayer(MediaPlayer): if not audio_cd_tracks or audio_cd_tracks.count() < 1: return False controller.vlc_media = audio_cd_tracks.item_at_index(int(controller.media_info.title_track)) + if not controller.vlc_media: + return False # VLC's start and stop time options work on seconds controller.vlc_media.add_option(f"start-time={int(controller.media_info.start_time // 1000)}") controller.vlc_media.add_option(f"stop-time={int(controller.media_info.end_time // 1000)}") + controller.vlc_media_player.set_media(controller.vlc_media) + elif controller.media_info.media_type == MediaType.DVD: + if is_win(): + path = '/' + path + dvd_location = 'dvd://' + path + '#' + controller.media_info.title_track + controller.vlc_media = controller.vlc_instance.media_new_location(dvd_location) + log.debug(f"vlc dvd load: {dvd_location}") + controller.vlc_media.add_option(f"start-time={int(controller.media_info.start_time // 1000)}") + controller.vlc_media.add_option(f"stop-time={int(controller.media_info.end_time // 1000)}") + controller.vlc_media_player.set_media(controller.vlc_media) + controller.vlc_media_player.play() + # Wait for media to start playing. In this case VLC returns an error. + self.media_state_wait(controller, vlc.State.Playing) + if controller.media_info.audio_track > 0: + res = controller.vlc_media_player.audio_set_track(controller.media_info.audio_track) + log.debug('vlc play, audio_track set: ' + str(controller.media_info.audio_track) + ' ' + str(res)) + if controller.media_info.subtitle_track > 0: + res = 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) + ' ' + str(res)) elif controller.media_info.media_type == MediaType.Stream: controller.vlc_media = controller.vlc_instance.media_new_location(file[0]) controller.vlc_media.add_options(file[1]) + controller.vlc_media_player.set_media(controller.vlc_media) else: controller.vlc_media = controller.vlc_instance.media_new_path(path) - # put the media in the media player - controller.vlc_media_player.set_media(controller.vlc_media) + controller.vlc_media_player.set_media(controller.vlc_media) + controller.media_info.start_time = 0 + controller.media_info.end_time = controller.media_info.length # parse the metadata of the file controller.vlc_media.parse() + controller.seek_slider.setMinimum(controller.media_info.start_time) + controller.seek_slider.setMaximum(controller.media_info.end_time) self.volume(controller, controller.media_info.volume) return True @@ -245,51 +271,11 @@ class VlcPlayer(MediaPlayer): :return: """ vlc = get_vlc() - start_time = controller.media_info.start_time - log.debug('vlc play') - if controller.is_live: - 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.timer > 0: - start_time = controller.media_info.timer + log.debug('vlc play, mediatype: ' + str(controller.media_info.media_type)) 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.timer > 0: - log.debug('vlc play, start time set') - start_time = controller.media_info.timer - else: - 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.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 \ - self.get_live_state() != MediaState.Paused and self.get_preview_state() != MediaState.Paused: - log.debug('vlc play, playing started') - if controller.media_info.title_track > 0: - log.debug('vlc play, title_track set: ' + str(controller.media_info.title_track)) - controller.vlc_media_player.set_title(controller.media_info.title_track) - controller.vlc_media_player.play() - if not self.media_state_wait(controller, vlc.State.Playing): - return False - if controller.media_info.audio_track > 0: - controller.vlc_media_player.audio_set_track(controller.media_info.audio_track) - log.debug('vlc play, audio_track set: ' + str(controller.media_info.audio_track)) - 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.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)) - controller.seek_slider.setMaximum(controller.media_info.start_time + controller.media_info.length) - controller.seek_slider.setMinimum(controller.media_info.start_time) self.set_state(MediaState.Playing, controller) return True @@ -334,9 +320,6 @@ class VlcPlayer(MediaPlayer): :param seek_value: The position of where a seek goes to :param controller: The controller where the media is """ - if controller.media_info.media_type == MediaType.CD \ - or controller.media_info.media_type == MediaType.DVD: - seek_value += int(controller.media_info.start_time) if controller.vlc_media_player.is_seekable(): controller.vlc_media_player.set_time(seek_value) @@ -367,9 +350,5 @@ class VlcPlayer(MediaPlayer): """ if not controller.seek_slider.isSliderDown(): controller.seek_slider.blockSignals(True) - if controller.media_info.media_type == MediaType.DVD: - controller.seek_slider.setSliderPosition( - controller.vlc_media_player.get_time() - int(controller.media_info.start_time)) - else: - controller.seek_slider.setSliderPosition(controller.vlc_media_player.get_time()) + controller.seek_slider.setSliderPosition(controller.vlc_media_player.get_time()) controller.seek_slider.blockSignals(False) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index b8c26b477..bf5b113f2 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1001,6 +1001,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): # 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). + self._raise_displays() QtCore.QTimer.singleShot(100, self._raise_displays) QtCore.QTimer.singleShot(500, self._raise_displays) QtCore.QTimer.singleShot(1000, self._raise_displays) @@ -1530,12 +1531,13 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): if State().check_preconditions('media'): if self.is_live and not item.is_media() and item.requires_media(): self.media_controller.load_video(self.controller_type, item, self._current_hide_mode) - elif self.is_live and self._current_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(): - # avoid loading the video if this is preview and the media is background + elif self.is_live: + if self._current_hide_mode == HideMode.Theme: + self.set_hide_mode(HideMode.Blank) self.media_controller.load_video(self.controller_type, item, self._current_hide_mode) + elif item.is_media(): + # avoid loading the video if this is preview and the media is background + self.media_controller.load_video(self.controller_type, item) if not self.is_live: self.preview_display.show() diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index e9106004d..c0392be93 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -254,9 +254,10 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro self.blockSignals(True) # Get titles, insert in combobox titles = self.vlc_media_player.video_get_title_description() + titles = get_vlc().track_description_list(titles) self.titles_combo_box.clear() for title in titles: - self.titles_combo_box.addItem(title.name.decode(), title.id) + self.titles_combo_box.addItem(title[1].decode(), title[0]) # Re-enable signals self.blockSignals(False) # Main title is usually title #1 diff --git a/tests/openlp_core/ui/media/test_vlcplayer.py b/tests/openlp_core/ui/media/test_vlcplayer.py index c01029a11..3516a07eb 100644 --- a/tests/openlp_core/ui/media/test_vlcplayer.py +++ b/tests/openlp_core/ui/media/test_vlcplayer.py @@ -356,7 +356,10 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win): mocked_controller.media_info.title_track = 1 mocked_vlc_media = MagicMock() mocked_media = MagicMock() - mocked_media.get_duration.return_value = 10000 + mocked_controller.media_info.volume = 100 + mocked_controller.media_info.start_time = 20000 + mocked_controller.media_info.end_time = 30000 + mocked_controller.media_info.length = 10000 mocked_controller.vlc_instance.media_new_location.return_value = mocked_vlc_media mocked_controller.vlc_media_player.get_media.return_value = mocked_media mocked_subitems = MagicMock() @@ -378,6 +381,8 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win): mocked_vlc_media.parse.assert_called_with() mocked_volume.assert_called_with(mocked_controller, 100) mocked_media_state_wait.assert_called_with(mocked_controller, ANY) + mocked_controller.seek_slider.setMinimum.assert_called_with(20000) + mocked_controller.seek_slider.setMaximum.assert_called_with(30000) assert result is True @@ -475,6 +480,56 @@ def test_load_audio_cd_no_tracks(mocked_normcase, mocked_get_vlc, mocked_is_win) assert result is False +@patch('openlp.core.ui.media.vlcplayer.is_win') +@patch('openlp.core.ui.media.vlcplayer.get_vlc') +@patch('openlp.core.ui.media.vlcplayer.os.path.normcase') +def test_load_dvd(mocked_normcase, mocked_get_vlc, mocked_is_win): + """ + Test loading a DVD into VLC + """ + # GIVEN: A mocked out get_vlc() method + mocked_is_win.return_value = False + media_path = '/dev/sr0' + mocked_normcase.side_effect = lambda x: x + mocked_vlc = MagicMock() + mocked_get_vlc.return_value = mocked_vlc + mocked_display = MagicMock() + mocked_controller = MagicMock() + mocked_controller.media_info.volume = 100 + mocked_controller.media_info.media_type = MediaType.DVD + mocked_controller.media_info.title_track = '2' + mocked_controller.media_info.audio_track = 2 + mocked_controller.media_info.subtitle_track = 4 + mocked_vlc_media = MagicMock() + mocked_media = MagicMock() + mocked_controller.media_info.volume = 100 + mocked_controller.media_info.start_time = 20000 + mocked_controller.media_info.end_time = 30000 + mocked_controller.media_info.length = 10000 + mocked_controller.vlc_instance.media_new_location.return_value = mocked_vlc_media + mocked_controller.vlc_media_player.get_media.return_value = mocked_media + vlc_player = VlcPlayer(None) + + # WHEN: A DVD clip is loaded into VLC + with patch.object(vlc_player, 'volume') as mocked_volume, \ + patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait: + result = vlc_player.load(mocked_controller, mocked_display, media_path) + + # THEN: The video should be loaded + mocked_normcase.assert_called_with(media_path) + mocked_controller.vlc_instance.media_new_location.assert_called_with('dvd://' + media_path + '#2') + assert mocked_vlc_media == mocked_controller.vlc_media + mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media) + mocked_controller.vlc_media_player.audio_set_track.assert_called_with(2) + mocked_controller.vlc_media_player.video_set_spu.assert_called_with(4) + mocked_vlc_media.parse.assert_called_with() + mocked_volume.assert_called_with(mocked_controller, 100) + mocked_media_state_wait.assert_called_with(mocked_controller, ANY) + mocked_controller.seek_slider.setMinimum.assert_called_with(20000) + mocked_controller.seek_slider.setMaximum.assert_called_with(30000) + assert result is True + + @patch('openlp.core.ui.media.vlcplayer.get_vlc') @patch('openlp.core.ui.media.vlcplayer.datetime', MockDateTime) def test_media_state_wait(mocked_get_vlc): @@ -581,12 +636,7 @@ def test_play(mocked_get_vlc, mocked_threading): mocked_display = MagicMock() mocked_controller = MagicMock() mocked_media = MagicMock() - mocked_media.get_duration.return_value = 50000 - mocked_controller.media_info.start_time = 0 - mocked_controller.media_info.media_type = MediaType.Video mocked_controller.media_info.volume = 100 - mocked_controller.media_info.start_time = 20000 - mocked_controller.media_info.length = 10000 mocked_controller.vlc_media_player.get_media.return_value = mocked_media vlc_player = VlcPlayer(None) vlc_player.set_state(MediaState.Paused, mocked_controller) @@ -600,8 +650,6 @@ def test_play(mocked_get_vlc, mocked_threading): # THEN: A bunch of things should happen to play the media mocked_thread.start.assert_called_with() mocked_volume.assert_called_with(mocked_controller, 100) - mocked_controller.seek_slider.setMaximum.assert_called_with(30000) - mocked_controller.seek_slider.setMinimum.assert_called_with(20000) assert MediaState.Playing == vlc_player.get_live_state() assert result is True, 'The value returned from play() should be True' @@ -635,47 +683,6 @@ def test_play_media_wait_state_not_playing(mocked_get_vlc, mocked_threading): assert result is False -@patch('openlp.core.ui.media.vlcplayer.threading') -@patch('openlp.core.ui.media.vlcplayer.get_vlc') -def test_play_dvd(mocked_get_vlc, mocked_threading): - """ - Test the play() method with a DVD - """ - # GIVEN: A bunch of mocked out things - mocked_thread = MagicMock() - mocked_threading.Thread.return_value = mocked_thread - mocked_vlc = MagicMock() - mocked_get_vlc.return_value = mocked_vlc - mocked_controller = MagicMock() - mocked_output_display = MagicMock() - mocked_controller.media_info = ItemMediaInfo() - mocked_controller.media_info.start_time = 0 - mocked_controller.media_info.end_time = 50 - mocked_controller.media_info.media_type = MediaType.DVD - mocked_controller.media_info.volume = 100 - mocked_controller.media_info.title_track = 1 - mocked_controller.media_info.audio_track = 1 - mocked_controller.media_info.subtitle_track = 1 - vlc_player = VlcPlayer(None) - vlc_player.set_state(MediaState.Paused, mocked_output_display) - - # WHEN: play() is called - with patch.object(vlc_player, 'media_state_wait', return_value=True), \ - patch.object(vlc_player, 'volume') as mocked_volume, \ - patch.object(vlc_player, 'get_live_state', return_value=MediaState.Loaded): - result = vlc_player.play(mocked_controller, mocked_output_display) - - # THEN: A bunch of things should happen to play the media - mocked_thread.start.assert_called_with() - mocked_controller.vlc_media_player.set_title.assert_called_with(1) - mocked_controller.vlc_media_player.play.assert_called_with() - mocked_controller.vlc_media_player.audio_set_track.assert_called_with(1) - mocked_controller.vlc_media_player.video_set_spu.assert_called_with(1) - mocked_volume.assert_called_with(mocked_controller, 100) - assert MediaState.Playing == vlc_player.get_live_state() - assert result is True, 'The value returned from play() should be True' - - @patch('openlp.core.ui.media.vlcplayer.get_vlc') def test_pause(mocked_get_vlc): """ @@ -918,7 +925,7 @@ def test_update_ui_dvd(mocked_get_vlc): mocked_controller.seek_slider.isSliderDown.return_value = False mocked_display = MagicMock() mocked_controller.vlc_media.get_state.return_value = 1 - mocked_controller.vlc_media_player.get_time.return_value = 300 + mocked_controller.vlc_media_player.get_time.return_value = 200 mocked_controller.media_info.media_type = MediaType.DVD vlc_player = VlcPlayer(None)