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

Fix cd media crash

Closes #838

See merge request openlp/openlp!338
This commit is contained in:
Tim Bentley 2021-08-28 07:04:16 +00:00
commit 690062b56b
4 changed files with 57 additions and 44 deletions

View File

@ -260,8 +260,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# if this is an optical device use special handling # if this is an optical device use special handling
if service_item.is_capable(ItemCapabilities.IsOptical): if service_item.is_capable(ItemCapabilities.IsOptical):
self.log_debug('video is optical and live') self.log_debug('video is optical and live')
path = service_item.get_frame_path() path_string = path_to_str(service_item.get_frame_path())
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path) (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path_string)
is_valid = self.media_setup_optical(name, title, audio_track, subtitle_track, start, end, display, is_valid = self.media_setup_optical(name, title, audio_track, subtitle_track, start, end, display,
controller) controller)
elif service_item.is_capable(ItemCapabilities.CanStream): elif service_item.is_capable(ItemCapabilities.CanStream):
@ -281,8 +281,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
elif controller.preview_display: elif controller.preview_display:
if service_item.is_capable(ItemCapabilities.IsOptical): if service_item.is_capable(ItemCapabilities.IsOptical):
self.log_debug('video is optical and preview') self.log_debug('video is optical and preview')
path = service_item.get_frame_path() path_string = path_to_str(service_item.get_frame_path())
(name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path) (name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(path_string)
is_valid = self.media_setup_optical(name, title, audio_track, subtitle_track, start, end, display, is_valid = self.media_setup_optical(name, title, audio_track, subtitle_track, start, end, display,
controller) controller)
elif service_item.is_capable(ItemCapabilities.CanStream): elif service_item.is_capable(ItemCapabilities.CanStream):
@ -482,7 +482,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
start_again = False start_again = False
stopped = False stopped = False
if controller.media_info.is_playing and controller.media_info.length > 0: if controller.media_info.is_playing and controller.media_info.length > 0:
if controller.media_info.timer + TICK_TIME >= controller.media_info.length: if controller.media_info.timer - controller.media_info.start_time + TICK_TIME >= \
controller.media_info.length:
if controller.media_info.is_looping_playback: if controller.media_info.is_looping_playback:
start_again = True start_again = True
else: else:
@ -494,12 +495,12 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
stopped = True stopped = True
if start_again: if start_again:
controller.media_info.timer = 0 controller.media_info.timer = controller.media_info.start_time
self._update_seek_ui(controller) self._update_seek_ui(controller)
return not stopped return not stopped
def _update_seek_ui(self, controller): def _update_seek_ui(self, controller):
seconds = controller.media_info.timer // 1000 seconds = (controller.media_info.timer - controller.media_info.start_time) // 1000
minutes = seconds // 60 minutes = seconds // 60
seconds %= 60 seconds %= 60
total_seconds = controller.media_info.length // 1000 total_seconds = controller.media_info.length // 1000
@ -592,7 +593,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.set_hide_mode(display.hide_mode or HideMode.Blank) controller.set_hide_mode(display.hide_mode or HideMode.Blank)
else: else:
self._media_set_visibility(controller, False) self._media_set_visibility(controller, False)
controller.seek_slider.setSliderPosition(0) controller.seek_slider.setSliderPosition(controller.media_info.start_time)
total_seconds = controller.media_info.length // 1000 total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60 total_minutes = total_seconds // 60
total_seconds %= 60 total_seconds %= 60
@ -601,7 +602,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.mediabar.actions['playbackStop'].setDisabled(True) controller.mediabar.actions['playbackStop'].setDisabled(True)
controller.mediabar.actions['playbackPause'].setVisible(False) controller.mediabar.actions['playbackPause'].setVisible(False)
controller.media_info.is_playing = False controller.media_info.is_playing = False
controller.media_info.timer = 0 controller.media_info.timer = controller.media_info.start_time
controller.output_has_changed() controller.output_has_changed()
return True return True
return False return False

View File

@ -48,12 +48,12 @@ class MediaPlayer(RegistryProperties):
""" """
return False return False
def setup(self, controller, display, live_display): def setup(self, controller, display):
""" """
Create the related widgets for the current display Create the related widgets for the current display
:param controller: Which Controller is running the show.
:param display: The display to be updated. :param display: The display to be updated.
:param live_display: Is the display a live one.
""" """
pass pass
@ -66,11 +66,11 @@ class MediaPlayer(RegistryProperties):
""" """
return True return True
def resize(self, display): def resize(self, controller):
""" """
If the main display size or position is changed, the media widgets If the main display size or position is changed, the media widgets
should also resized should also resized
:param display: The display to be updated. :param controller: Which Controller is running the show.
""" """
pass pass
@ -83,53 +83,53 @@ class MediaPlayer(RegistryProperties):
""" """
pass pass
def pause(self, display): def pause(self, controller):
""" """
Pause of current Media File Pause of current Media File
:param display: The display to be updated. :param controller: Which Controller is running the show.
""" """
pass pass
def stop(self, display): def stop(self, controller):
""" """
Stop playing of current Media File Stop playing of current Media File
:param display: The display to be updated. :param controller: Which Controller is running the show.
""" """
pass pass
def volume(self, display, volume): def volume(self, controller, volume):
""" """
Change volume of current Media File Change volume of current Media File
:param display: The display to be updated. :param controller: Which Controller is running the show.
:param volume: The volume to set. :param volume: The volume to set.
""" """
pass pass
def seek(self, display, seek_value): def seek(self, controller, seek_value):
""" """
Change playing position of current Media File Change playing position of current Media File
:param display: The display to be updated. :param controller: Which Controller is running the show.
:param seek_value: The where to seek to. :param seek_value: The where to seek to.
""" """
pass pass
def reset(self, display): def reset(self, controller):
""" """
Remove the current loaded video Remove the current loaded video
:param display: The display to be updated. :param controller: Which Controller is running the show.
""" """
pass pass
def set_visible(self, display, status): def set_visible(self, controller, status):
""" """
Show/Hide the media widgets Show/Hide the media widgets
:param display: The display to be updated. :param controller: Which Controller is running the show.
:param status: The status to be set. :param status: The status to be set.
""" """
pass pass

View File

@ -101,7 +101,8 @@ class VlcPlayer(MediaPlayer):
""" """
Set up the media player Set up the media player
:param controller: The display where the media is. :param controller: The controller where the media is
:param display: The display where the media is.
:return: :return:
""" """
vlc = get_vlc() vlc = get_vlc()
@ -182,12 +183,15 @@ class VlcPlayer(MediaPlayer):
controller.vlc_media_player.set_media(controller.vlc_media) controller.vlc_media_player.set_media(controller.vlc_media)
controller.vlc_media_player.play() controller.vlc_media_player.play()
# Wait for media to start playing. In this case VLC actually returns an error. # Wait for media to start playing. In this case VLC actually returns an error.
self.media_state_wait(output_display, vlc.State.Playing) self.media_state_wait(controller, vlc.State.Playing)
# If subitems exists, this is a CD # If subitems exists, this is a CD
audio_cd_tracks = controller.vlc_media.subitems() audio_cd_tracks = controller.vlc_media.subitems()
if not audio_cd_tracks or audio_cd_tracks.count() < 1: if not audio_cd_tracks or audio_cd_tracks.count() < 1:
return False return False
controller.vlc_media_player = audio_cd_tracks.item_at_index(controller.media_info.title_track) controller.vlc_media = audio_cd_tracks.item_at_index(int(controller.media_info.title_track))
# 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)}")
elif controller.media_info.media_type == MediaType.Stream: elif controller.media_info.media_type == MediaType.Stream:
controller.vlc_media = controller.vlc_instance.media_new_location(file[0]) controller.vlc_media = controller.vlc_instance.media_new_location(file[0])
controller.vlc_media.add_options(file[1]) controller.vlc_media.add_options(file[1])
@ -241,7 +245,7 @@ class VlcPlayer(MediaPlayer):
:return: :return:
""" """
vlc = get_vlc() vlc = get_vlc()
start_time = 0 start_time = controller.media_info.start_time
log.debug('vlc play') log.debug('vlc play')
if controller.is_live: if controller.is_live:
if self.get_live_state() != MediaState.Paused and controller.media_info.timer > 0: if self.get_live_state() != MediaState.Paused and controller.media_info.timer > 0:
@ -284,7 +288,8 @@ class VlcPlayer(MediaPlayer):
self.volume(controller, controller.media_info.volume) self.volume(controller, controller.media_info.volume)
if start_time > 0 and controller.vlc_media_player.is_seekable(): if start_time > 0 and controller.vlc_media_player.is_seekable():
controller.vlc_media_player.set_time(int(start_time)) controller.vlc_media_player.set_time(int(start_time))
controller.seek_slider.setMaximum(controller.media_info.length) 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) self.set_state(MediaState.Playing, controller)
return True return True
@ -362,8 +367,7 @@ class VlcPlayer(MediaPlayer):
""" """
if not controller.seek_slider.isSliderDown(): if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True) controller.seek_slider.blockSignals(True)
if controller.media_info.media_type == MediaType.CD \ if controller.media_info.media_type == MediaType.DVD:
or controller.media_info.media_type == MediaType.DVD:
controller.seek_slider.setSliderPosition( controller.seek_slider.setSliderPosition(
controller.vlc_media_player.get_time() - int(controller.media_info.start_time)) controller.vlc_media_player.get_time() - int(controller.media_info.start_time))
else: else:

View File

@ -26,7 +26,7 @@ import sys
import pytest import pytest
from datetime import timedelta from datetime import timedelta
from unittest import skipIf from unittest import skipIf
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call, patch, ANY
from openlp.core.common import is_macosx from openlp.core.common import is_macosx
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
@ -367,7 +367,7 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
# WHEN: An audio CD is loaded into VLC # WHEN: An audio CD is loaded into VLC
with patch.object(vlc_player, 'volume') as mocked_volume, \ with patch.object(vlc_player, 'volume') as mocked_volume, \
patch.object(vlc_player, 'media_state_wait'): patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
result = vlc_player.load(mocked_controller, mocked_display, media_path) result = vlc_player.load(mocked_controller, mocked_display, media_path)
# THEN: The video should be loaded # THEN: The video should be loaded
@ -377,6 +377,7 @@ def test_load_audio_cd(mocked_normcase, mocked_get_vlc, mocked_is_win):
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media) mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
mocked_vlc_media.parse.assert_called_with() mocked_vlc_media.parse.assert_called_with()
mocked_volume.assert_called_with(mocked_controller, 100) mocked_volume.assert_called_with(mocked_controller, 100)
mocked_media_state_wait.assert_called_with(mocked_controller, ANY)
assert result is True assert result is True
@ -408,11 +409,12 @@ def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win
mocked_subitems.count.return_value = 1 mocked_subitems.count.return_value = 1
mocked_subitems.item_at_index.return_value = mocked_vlc_media mocked_subitems.item_at_index.return_value = mocked_vlc_media
mocked_vlc_media.subitems.return_value = mocked_subitems mocked_vlc_media.subitems.return_value = mocked_subitems
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
# WHEN: An audio CD is loaded into VLC # WHEN: An audio CD is loaded into VLC
with patch.object(vlc_player, 'volume') as mocked_volume, \ with patch.object(vlc_player, 'volume') as mocked_volume, \
patch.object(vlc_player, 'media_state_wait'): patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait:
result = vlc_player.load(mocked_controller, mocked_display, media_path) result = vlc_player.load(mocked_controller, mocked_display, media_path)
# THEN: The video should be loaded # THEN: The video should be loaded
@ -422,6 +424,7 @@ def test_load_audio_cd_on_windows(mocked_normcase, mocked_get_vlc, mocked_is_win
mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media) mocked_controller.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
mocked_vlc_media.parse.assert_called_with() mocked_vlc_media.parse.assert_called_with()
mocked_volume.assert_called_with(mocked_controller, 100) mocked_volume.assert_called_with(mocked_controller, 100)
mocked_media_state_wait.assert_called_with(mocked_controller, ANY)
assert result is True assert result is True
@ -482,15 +485,15 @@ def test_media_state_wait(mocked_get_vlc):
mocked_vlc = MagicMock() mocked_vlc = MagicMock()
mocked_vlc.State.Error = 1 mocked_vlc.State.Error = 1
mocked_get_vlc.return_value = mocked_vlc mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock() mocked_controller = MagicMock()
mocked_display.vlc_media.get_state.return_value = 2 mocked_controller.vlc_media.get_state.return_value = 2
Registry.create() Registry.create()
mocked_application = MagicMock() mocked_application = MagicMock()
Registry().register('application', mocked_application) Registry().register('application', mocked_application)
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
# WHEN: media_state_wait() is called # WHEN: media_state_wait() is called
result = vlc_player.media_state_wait(mocked_display, 2) result = vlc_player.media_state_wait(mocked_controller, 2)
# THEN: The results should be True # THEN: The results should be True
assert result is True assert result is True
@ -506,15 +509,15 @@ def test_media_state_wait_error(mocked_get_vlc, vlc_env):
mocked_vlc = MagicMock() mocked_vlc = MagicMock()
mocked_vlc.State.Error = 1 mocked_vlc.State.Error = 1
mocked_get_vlc.return_value = mocked_vlc mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock() mocked_controller = MagicMock()
mocked_display.vlc_media.get_state.return_value = 1 mocked_controller.vlc_media.get_state.return_value = 1
Registry.create() Registry.create()
mocked_application = MagicMock() mocked_application = MagicMock()
Registry().register('application', mocked_application) Registry().register('application', mocked_application)
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
# WHEN: media_state_wait() is called # WHEN: media_state_wait() is called
result = vlc_player.media_state_wait(mocked_display, 2) result = vlc_player.media_state_wait(mocked_controller, 2)
# THEN: The results should be True # THEN: The results should be True
assert result is False assert result is False
@ -532,15 +535,15 @@ def test_media_state_wait_times_out(mocked_get_vlc, vlc_env):
mocked_vlc = MagicMock() mocked_vlc = MagicMock()
mocked_vlc.State.Error = 1 mocked_vlc.State.Error = 1
mocked_get_vlc.return_value = mocked_vlc mocked_get_vlc.return_value = mocked_vlc
mocked_display = MagicMock() mocked_controller = MagicMock()
mocked_display.vlc_media.get_state.return_value = 2 mocked_controller.vlc_media.get_state.return_value = 2
Registry.create() Registry.create()
mocked_application = MagicMock() mocked_application = MagicMock()
Registry().register('application', mocked_application) Registry().register('application', mocked_application)
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
# WHEN: media_state_wait() is called # WHEN: media_state_wait() is called
result = vlc_player.media_state_wait(mocked_display, 3) result = vlc_player.media_state_wait(mocked_controller, 3)
# THEN: The results should be True # THEN: The results should be True
assert result is False assert result is False
@ -582,6 +585,8 @@ def test_play(mocked_get_vlc, mocked_threading):
mocked_controller.media_info.start_time = 0 mocked_controller.media_info.start_time = 0
mocked_controller.media_info.media_type = MediaType.Video mocked_controller.media_info.media_type = MediaType.Video
mocked_controller.media_info.volume = 100 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 mocked_controller.vlc_media_player.get_media.return_value = mocked_media
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
vlc_player.set_state(MediaState.Paused, mocked_controller) vlc_player.set_state(MediaState.Paused, mocked_controller)
@ -595,6 +600,9 @@ def test_play(mocked_get_vlc, mocked_threading):
# THEN: A bunch of things should happen to play the media # THEN: A bunch of things should happen to play the media
mocked_thread.start.assert_called_with() mocked_thread.start.assert_called_with()
mocked_volume.assert_called_with(mocked_controller, 100) 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 MediaState.Playing == vlc_player.get_live_state()
assert result is True, 'The value returned from play() should be True' assert result is True, 'The value returned from play() should be True'