forked from openlp/openlp
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:
commit
690062b56b
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user