Merge branch 'fix-dvd-media-crash' into 'master'

Fix dvd media crash

Closes #876

See merge request openlp/openlp!356
This commit is contained in:
Tim Bentley 2021-09-12 08:28:58 +00:00
commit 191e8dc672
5 changed files with 116 additions and 130 deletions

View File

@ -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):
"""

View File

@ -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)

View File

@ -1006,6 +1006,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)
@ -1535,12 +1536,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()

View File

@ -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

View File

@ -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)