示例#1
0
class PlayerTestCase(unittest.TestCase):
    # Default values to use
    audio_format_1 = AudioFormat(1, 8, 11025)
    audio_format_2 = AudioFormat(2, 8, 11025)
    audio_format_3 = AudioFormat(2, 16, 44100)
    video_format_1 = VideoFormat(800, 600)
    video_format_2 = VideoFormat(1920, 1280)
    video_format_2.frame_rate = 25

    def setUp(self):
        self.player = Player()

        self._get_audio_driver_patcher = mock.patch(
            'pyglet.media.player.get_audio_driver')
        self.mock_get_audio_driver = self._get_audio_driver_patcher.start()
        self.mock_audio_driver = self.mock_get_audio_driver.return_value
        self.mock_audio_driver_player = self.mock_audio_driver.create_audio_player.return_value

        self._clock_patcher = mock.patch('pyglet.clock')
        self.mock_clock = self._clock_patcher.start()

        self._texture_patcher = mock.patch('pyglet.image.Texture.create')
        self.mock_texture_create = self._texture_patcher.start()
        self.mock_texture = self.mock_texture_create.return_value
        # Need to do this as side_effect instead of return_value, or reset_mock will recurse
        self.mock_texture.get_transform.side_effect = lambda flip_y: self.mock_texture

    def tearDown(self):
        self._get_audio_driver_patcher.stop()
        self._clock_patcher.stop()
        self._texture_patcher.stop()

    def reset_mocks(self):
        # These mocks will recursively reset their children
        self.mock_get_audio_driver.reset_mock()
        self.mock_clock.reset_mock()
        self.mock_texture_create.reset_mock()

    def create_mock_source(self, audio_format, video_format):
        mock_source = mock.MagicMock(spec=Source)
        type(mock_source).audio_format = mock.PropertyMock(
            return_value=audio_format)
        type(mock_source).video_format = mock.PropertyMock(
            return_value=video_format)
        type(mock_source.get_queue_source.return_value
             ).audio_format = mock.PropertyMock(return_value=audio_format)
        type(mock_source.get_queue_source.return_value
             ).video_format = mock.PropertyMock(return_value=video_format)
        if video_format:
            mock_source.video_format.frame_rate = 30
        return mock_source

    def set_video_data_for_mock_source(self, mock_source,
                                       timestamp_data_pairs):
        """Make the given mock source return video data. Video data is given in pairs of
        timestamp and data to return."""
        def _get_frame():
            if timestamp_data_pairs:
                current_frame = timestamp_data_pairs.pop(0)
                return current_frame[1]

        def _get_timestamp():
            if timestamp_data_pairs:
                return timestamp_data_pairs[0][0]

        queue_source = mock_source.get_queue_source.return_value
        queue_source.get_next_video_timestamp.side_effect = _get_timestamp
        queue_source.get_next_video_frame.side_effect = _get_frame

    def assert_not_playing_yet(self, current_source=None):
        """Assert the the player did not start playing yet."""
        self.assertFalse(self.mock_get_audio_driver.called,
                         msg='No audio driver required yet')
        self.assertAlmostEqual(self.player.time, 0.)
        self.assert_not_playing(current_source)

    def assert_not_playing(self, current_source=None):
        self._assert_playing(False, current_source)

    def assert_now_playing(self, current_source):
        self._assert_playing(True, current_source)

    def _assert_playing(self, playing, current_source=None):
        self.assertEqual(self.player.playing, playing)
        queued_source = (current_source.get_queue_source.return_value
                         if current_source is not None else None)
        self.assertIs(self.player.source, queued_source)

    def assert_driver_player_created_for(self, source):
        """Assert that a driver specific audio player is created to play back given source"""
        self._assert_player_created_for(self.mock_get_audio_driver,
                                        self.mock_audio_driver, source)

    def _assert_player_created_for(self, mock_get_audio_driver,
                                   mock_audio_driver, source):
        mock_get_audio_driver.assert_called_once_with()
        self.assertEqual(mock_audio_driver.create_audio_player.call_count, 1)
        call_args = mock_audio_driver.create_audio_player.call_args
        args, kwargs = call_args
        arg_source, arg_player = args
        self.assertIs(arg_source, source.get_queue_source.return_value)
        self.assertIs(arg_player, self.player)

    def assert_no_new_driver_player_created(self):
        """Assert that no new driver specific audio player is created."""
        self.assertFalse(self.mock_get_audio_driver.called,
                         msg='No new audio driver should be created')

    # def assert_in_current_play_list(self, *sources):
    #     self.assertIsNotNone(self.current_play_list, msg='No previous call to create driver player')

    #     queue_sources = deque(source.get_queue_source.return_value for source in sources)
    #     self.assertSequenceEqual(self.current_play_list._sources, queue_sources)

    def assert_driver_player_destroyed(self):
        self.mock_audio_driver_player.delete.assert_called_once_with()

    def assert_driver_player_not_destroyed(self):
        self.assertFalse(self.mock_audio_driver_player.delete.called)

    def assert_silent_driver_player_destroyed(self):
        self.mock_silent_audio_driver_player.delete.assert_called_once_with()

    def assert_driver_player_started(self):
        self.mock_audio_driver_player.play.assert_called_once_with()

    def assert_driver_player_stopped(self):
        self.mock_audio_driver_player.stop.assert_called_once_with()

    def assert_driver_player_cleared(self):
        self.mock_audio_driver_player.clear.assert_called_once_with()

    def assert_source_seek(self, source, time):
        source.get_queue_source.return_value.seek.assert_called_once_with(time)

    def assert_new_texture_created(self, video_format):
        self.mock_texture_create.assert_called_once_with(
            video_format.width, video_format.height, GL_TEXTURE_RECTANGLE)

    def assert_no_new_texture_created(self):
        self.assertFalse(self.mock_texture_create.called)

    def assert_texture_updated(self, frame_data):
        self.mock_texture.blit_into.assert_called_once_with(
            frame_data, 0, 0, 0)

    def assert_texture_not_updated(self):
        self.assertFalse(self.mock_texture.blit_into.called)

    def assert_update_texture_scheduled(self):
        self.mock_clock.schedule_once.assert_called_once_with(
            self.player.update_texture, 0)

    def assert_update_texture_unscheduled(self):
        self.mock_clock.unschedule.assert_called_with(
            self.player.update_texture)

    def pretend_player_at_time(self, t):
        self.player._mclock.set_time(t)

    def pretend_silent_driver_player_at_time(self, t):
        self.mock_silent_audio_driver_player.get_time.return_value = t

    def test_queue_single_audio_source_and_play(self):
        """Queue a single audio source and start playing it."""

        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)

    def test_queue_multiple_audio_sources_same_format_and_play(self):
        """Queue multiple audio sources using the same audio format and start playing."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)

        self.player.queue(mock_source1)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

    def test_queue_multiple_audio_sources_different_format_and_play_and_skip(
            self):
        """Queue multiple audio sources having different formats and start playing. Different
        formats should be played by seperate driver players."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        mock_source3 = self.create_mock_source(self.audio_format_3, None)

        self.player.queue(mock_source1)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source2)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source3)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source3)

    def test_queue_multiple_audio_sources_same_format_and_play_and_skip(self):
        """When multiple audio sources with the same format are queued, they are played
        using the same driver player. Skipping to the next source is just advancing the
        source group.
        """
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_not_destroyed()
        self.assert_no_new_driver_player_created()
        self.assert_now_playing(mock_source2)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_not_destroyed()
        self.assert_no_new_driver_player_created()
        self.assert_now_playing(mock_source3)

    def test_on_eos(self):
        """The player receives on_eos for every source, but does not need to do anything.
        """
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()

        self.reset_mocks()
        self.player.dispatch_event('on_eos')
        self.assert_driver_player_not_destroyed()

    def test_player_stops_after_last_eos(self):
        """If the last playlist source is eos, the player stops."""
        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)

        self.reset_mocks()
        self.player.dispatch_event('on_eos')
        self.assert_driver_player_destroyed()
        self.assert_not_playing(None)

    def test_eos_events(self):
        """Test receiving various eos events: on source eos,
        on playlist exhausted and on player eos and on player next source.
        """
        on_eos_mock = mock.MagicMock(return_value=None)
        self.player.event('on_eos')(on_eos_mock)
        on_player_eos_mock = mock.MagicMock(return_value=None)
        self.player.event('on_player_eos')(on_player_eos_mock)
        on_player_next_source_mock = mock.MagicMock(return_value=None)
        self.player.event('on_player_next_source')(on_player_next_source_mock)

        def reset_eos_mocks():
            on_eos_mock.reset_mock()
            on_player_eos_mock.reset_mock()
            on_player_next_source_mock.reset_mock()

        def assert_eos_events_received(on_eos=False,
                                       on_player_eos=False,
                                       on_player_next_source=False):
            self.assertEqual(on_eos_mock.called, on_eos)
            self.assertEqual(on_player_eos_mock.called, on_player_eos)
            self.assertEqual(on_player_next_source_mock.called,
                             on_player_next_source)

        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_2, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)

        self.reset_mocks()
        reset_eos_mocks()
        # Pretend the current source in the group was eos and next source started
        self.player.dispatch_event('on_eos')
        self.assert_driver_player_not_destroyed()
        assert_eos_events_received(on_eos=True, on_player_next_source=True)
        self.assertEqual(len(self.player._playlists), 2)

        # Pretend playlist is exhausted. Should be no more sources to play.
        self.player.dispatch_event('on_eos')
        self.reset_mocks()
        reset_eos_mocks()
        self.player.dispatch_event('on_eos')
        self.assert_not_playing(None)
        assert_eos_events_received(on_eos=True, on_player_eos=True)

    def test_pause_resume(self):
        """A stream can be paused. After that play will resume where paused."""
        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)

        self.reset_mocks()
        self.pretend_player_at_time(0.5)
        self.player.pause()
        self.assert_driver_player_stopped()
        self.assert_driver_player_not_destroyed()

        self.reset_mocks()
        self.player.play()
        self.assertAlmostEqual(
            self.player.time,
            0.5,
            places=2,
            msg='While playing, player should return time from driver player')
        self.assert_driver_player_started()
        self.assert_no_new_driver_player_created()
        self.assert_now_playing(mock_source)

    def test_delete(self):
        """Test clean up of the player when delete() is called."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        mock_source3 = self.create_mock_source(self.audio_format_3, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()

        self.reset_mocks()
        self.pretend_player_at_time(1.)
        self.player.delete()
        self.assert_driver_player_destroyed()

    def test_empty_player(self):
        """A player without queued sources should not start a driver player and should not raise
        exceptions"""
        self.assert_not_playing_yet(None)

        self.reset_mocks()
        self.player.play()
        self.assert_no_new_driver_player_created()

        self.reset_mocks()
        self.player.pause()
        self.assert_no_new_driver_player_created()
        self.assert_driver_player_not_destroyed()

        self.reset_mocks()
        self.player.next_source()
        self.assert_no_new_driver_player_created()
        self.assert_driver_player_not_destroyed()

        self.reset_mocks()
        self.player.seek(0.8)
        self.assert_no_new_driver_player_created()
        self.assert_driver_player_not_destroyed()

        self.player.delete()

    def test_set_player_properties_before_playing(self):
        """When setting player properties before a driver specific player is
        created, these settings should be propagated after creating the
        player.
        """
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.reset_mocks()
        self.player.volume = 10.
        self.player.min_distance = 2.
        self.player.max_distance = 3.
        self.player.position = (4, 4, 4)
        self.player.pitch = 5.0
        self.player.cone_orientation = (6, 6, 6)
        self.player.cone_inner_angle = 7.
        self.player.cone_outer_angle = 8.
        self.player.cone_outer_gain = 9.

        def assert_properties_set():
            self.mock_audio_driver_player.set_volume.assert_called_once_with(
                10.)
            self.mock_audio_driver_player.set_min_distance.assert_called_once_with(
                2.)
            self.mock_audio_driver_player.set_max_distance.assert_called_once_with(
                3.)
            self.mock_audio_driver_player.set_position.assert_called_once_with(
                (4, 4, 4))
            self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
            self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with(
                (6, 6, 6))
            self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(
                7.)
            self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(
                8.)
            self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(
                9.)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_now_playing(mock_source1)
        assert_properties_set()

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        assert_properties_set()

    def test_set_player_properties_while_playing(self):
        """When setting player properties while playing, the properties should
        be propagated to the driver specific player right away."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.volume = 10.
        self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)

        self.reset_mocks()
        self.player.min_distance = 2.
        self.mock_audio_driver_player.set_min_distance.assert_called_once_with(
            2.)

        self.reset_mocks()
        self.player.max_distance = 3.
        self.mock_audio_driver_player.set_max_distance.assert_called_once_with(
            3.)

        self.reset_mocks()
        self.player.position = (4, 4, 4)
        self.mock_audio_driver_player.set_position.assert_called_once_with(
            (4, 4, 4))

        self.reset_mocks()
        self.player.pitch = 5.0
        self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)

        self.reset_mocks()
        self.player.cone_orientation = (6, 6, 6)
        self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with(
            (6, 6, 6))

        self.reset_mocks()
        self.player.cone_inner_angle = 7.
        self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(
            7.)

        self.reset_mocks()
        self.player.cone_outer_angle = 8.
        self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(
            8.)

        self.reset_mocks()
        self.player.cone_outer_gain = 9.
        self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(
            9.)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
        self.mock_audio_driver_player.set_min_distance.assert_called_once_with(
            2.)
        self.mock_audio_driver_player.set_max_distance.assert_called_once_with(
            3.)
        self.mock_audio_driver_player.set_position.assert_called_once_with(
            (4, 4, 4))
        self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
        self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with(
            (6, 6, 6))
        self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(
            7.)
        self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(
            8.)
        self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(
            9.)

    def test_seek(self):
        """Test seeking to a specific time in the current source."""
        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.reset_mocks()
        mock_source.reset_mock()
        self.player.seek(0.7)
        self.assert_source_seek(mock_source, 0.7)

        self.reset_mocks()
        mock_source.reset_mock()
        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_now_playing(mock_source)

        self.reset_mocks()
        mock_source.reset_mock()
        self.player.seek(0.2)
        self.assert_source_seek(mock_source, 0.2)
        # Clear buffers for immediate result
        self.assert_driver_player_cleared()

    def test_video_queue_and_play(self):
        """Sources can also include video. Instead of using a player to
        continuously play the video, a texture is updated based on the
        video packet timestamp."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.2, 'a')])
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled()

        self.reset_mocks()
        self.pretend_player_at_time(0.2)
        self.player.update_texture()
        self.assert_texture_updated('a')
        self.assertIs(self.player.texture, self.mock_texture)

    def test_video_seek(self):
        """Sources with video can also be seeked. It's the Source
        responsibility to present the Player with audio and video at the
        correct time."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source,
                                            [(0.0, 'a'), (0.1, 'b'),
                                             (0.2, 'c'), (0.3, 'd'),
                                             (0.4, 'e'), (0.5, 'f')])
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled()

        self.reset_mocks()
        self.pretend_player_at_time(0.0)
        self.player.update_texture()
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.player.seek(0.3)
        self.assert_source_seek(mock_source, 0.3)
        self.assert_no_new_texture_created()
        self.assert_texture_updated('d')

        self.reset_mocks()
        self.pretend_player_at_time(0.4)
        self.player.update_texture()
        self.assert_texture_updated('e')

    def test_video_frame_rate(self):
        """Videos texture are scheduled according to the video packet
        timestamp."""
        mock_source1 = self.create_mock_source(self.audio_format_1,
                                               self.video_format_1)
        mock_source2 = self.create_mock_source(self.audio_format_1,
                                               self.video_format_2)
        for mock_source in (mock_source1, mock_source2):
            self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'),
                                                              (0.1, 'b'),
                                                              (0.2, 'c'),
                                                              (0.3, 'd'),
                                                              (0.4, 'e'),
                                                              (0.5, 'f')])

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)

        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled()

        self.reset_mocks()
        self.player.next_source()
        self.assert_new_texture_created(self.video_format_2)
        self.assert_update_texture_unscheduled()
        # schedule_once called twice:
        #   - Once for seeking back to 0 the previous source in next_source()
        #   - Once for scheduling the next source update_texture
        assert self.mock_clock.schedule_once.call_count == 2
        self.mock_clock.schedule_once.assert_called_with(
            self.player.update_texture, 0)

    def test_video_seek_next_frame(self):
        """It is possible to jump directly to the next frame of video and adjust the audio player
        accordingly."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'),
                                                          (0.2, 'b')])
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled()

        self.reset_mocks()
        self.pretend_player_at_time(0.0)
        self.player.update_texture()
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.player.seek_next_frame()
        self.assert_source_seek(mock_source, 0.2)
        self.assert_texture_updated('b')

    def test_video_runs_out_of_frames(self):
        """When the video runs out of frames, it stops updating the texture. The audio player is
        responsible for triggering eos."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'),
                                                          (0.1, 'b')])
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled()

        self.reset_mocks()
        self.pretend_player_at_time(0.0)
        self.player.update_texture()
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.pretend_player_at_time(0.1)
        self.player.update_texture()
        self.assert_texture_updated('b')

        self.reset_mocks()
        self.pretend_player_at_time(0.2)
        self.player.update_texture()
        self.assert_texture_not_updated()

        self.reset_mocks()
        self.player.seek_next_frame()
        self.assert_texture_not_updated()

    def test_video_without_audio(self):
        """It is possible to have videos without audio streams."""
        mock_source = self.create_mock_source(None, self.video_format_1)
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled()
        self.assert_no_new_driver_player_created()

    def test_audio_source_with_silent_driver(self):
        """An audio source with a silent driver."""
        mock_source = self.create_mock_source(self.audio_format_3, None)
        self.mock_get_audio_driver.return_value = None
        self.player.queue(mock_source)
        self.player.play()
示例#2
0
class PlayerTestCase(FutureTestCase):
    # Default values to use
    audio_format_1 = AudioFormat(1, 8, 11025)
    audio_format_2 = AudioFormat(2, 8, 11025)
    audio_format_3 = AudioFormat(2, 16, 44100)
    video_format_1 = VideoFormat(800, 600)
    video_format_2 = VideoFormat(1920, 1280)
    video_format_2.frame_rate = 25

    def setUp(self):
        self.player = Player()

        self._get_audio_driver_patcher = mock.patch(
            'pyglet.media.player.get_audio_driver')
        self.mock_get_audio_driver = self._get_audio_driver_patcher.start()
        self.mock_audio_driver = self.mock_get_audio_driver.return_value
        self.mock_audio_driver_player = self.mock_audio_driver.create_audio_player.return_value

        self._get_silent_audio_driver_patcher = mock.patch(
            'pyglet.media.player.get_silent_audio_driver')
        self.mock_get_silent_audio_driver = self._get_silent_audio_driver_patcher.start(
        )
        self.mock_silent_audio_driver = self.mock_get_silent_audio_driver.return_value
        self.mock_silent_audio_driver_player = self.mock_silent_audio_driver.create_audio_player.return_value

        self._clock_patcher = mock.patch('pyglet.clock')
        self.mock_clock = self._clock_patcher.start()

        self._texture_patcher = mock.patch('pyglet.image.Texture.create')
        self.mock_texture_create = self._texture_patcher.start()
        self.mock_texture = self.mock_texture_create.return_value
        # Need to do this as side_effect instead of return_value, or reset_mock will recurse
        self.mock_texture.get_transform.side_effect = lambda flip_y: self.mock_texture

        self.current_playing_source_group = None

    def tearDown(self):
        self._get_audio_driver_patcher.stop()
        self._get_silent_audio_driver_patcher.stop()
        self._clock_patcher.stop()
        self._texture_patcher.stop()

    def reset_mocks(self):
        # These mocks will recursively reset their children
        self.mock_get_audio_driver.reset_mock()
        self.mock_get_silent_audio_driver.reset_mock()
        self.mock_clock.reset_mock()
        self.mock_texture_create.reset_mock()

    def create_mock_source(self, audio_format, video_format):
        mock_source = mock.MagicMock()
        type(mock_source).audio_format = mock.PropertyMock(
            return_value=audio_format)
        type(mock_source).video_format = mock.PropertyMock(
            return_value=video_format)
        type(mock_source._get_queue_source.return_value
             ).audio_format = mock.PropertyMock(return_value=audio_format)
        type(mock_source._get_queue_source.return_value
             ).video_format = mock.PropertyMock(return_value=video_format)
        return mock_source

    def set_video_data_for_mock_source(self, mock_source,
                                       timestamp_data_pairs):
        """Make the given mock source return video data. Video data is given in pairs of timestamp
        and data to return."""
        def _get_frame():
            if timestamp_data_pairs:
                current_frame = timestamp_data_pairs.pop(0)
                return current_frame[1]

        def _get_timestamp():
            if timestamp_data_pairs:
                return timestamp_data_pairs[0][0]

        queue_source = mock_source._get_queue_source.return_value
        queue_source.get_next_video_timestamp.side_effect = _get_timestamp
        queue_source.get_next_video_frame.side_effect = _get_frame

    def assert_not_playing_yet(self, current_source=None):
        """Assert the the player did not start playing yet."""
        self.assertFalse(self.mock_get_audio_driver.called,
                         msg='No audio driver required yet')
        self.assertAlmostEqual(self.player.time, 0.)
        self.assert_not_playing(current_source)

    def assert_not_playing(self, current_source=None):
        self._assert_playing(False, current_source)

    def assert_now_playing(self, current_source):
        self._assert_playing(True, current_source)

    def _assert_playing(self, playing, current_source=None):
        self.assertEqual(self.player.playing, playing)
        queued_source = (current_source._get_queue_source.return_value
                         if current_source is not None else None)
        self.assertIs(self.player.source, queued_source)

    def assert_driver_player_created_for(self, *sources):
        """Assert that a driver specific audio player is created to play back given sources"""
        self._assert_player_created_for(self.mock_get_audio_driver,
                                        self.mock_audio_driver, *sources)

    def assert_silent_driver_player_created_for(self, *sources):
        """Assert that a silent audio player is created for given sources."""
        self._assert_player_created_for(self.mock_get_silent_audio_driver,
                                        self.mock_silent_audio_driver,
                                        *sources)

    def _assert_player_created_for(self, mock_get_audio_driver,
                                   mock_audio_driver, *sources):
        mock_get_audio_driver.assert_called_once_with()
        self.assertEqual(mock_audio_driver.create_audio_player.call_count, 1)
        call_args = mock_audio_driver.create_audio_player.call_args
        self.assertIsInstance(call_args[0][0], SourceGroup)
        self.assertIs(call_args[0][1], self.player)

        self.current_playing_source_group = call_args[0][0]
        self.assert_in_current_playing_source_group(*sources)

    def assert_no_new_driver_player_created(self):
        """Assert that no new driver specific audio player is created."""
        self.assertFalse(self.mock_get_audio_driver.called,
                         msg='No new audio driver should be created')

    def assert_in_current_playing_source_group(self, *sources):
        self.assertIsNotNone(self.current_playing_source_group,
                             msg='No previous call to create driver player')

        queue_sources = [
            source._get_queue_source.return_value for source in sources
        ]
        self.assertListEqual(self.current_playing_source_group._sources,
                             queue_sources)

    def assert_driver_player_destroyed(self):
        self.mock_audio_driver_player.delete.assert_called_once_with()

    def assert_driver_player_not_destroyed(self):
        self.assertFalse(self.mock_audio_driver_player.delete.called)

    def assert_silent_driver_player_destroyed(self):
        self.mock_silent_audio_driver_player.delete.assert_called_once_with()

    def assert_driver_player_started(self):
        self.mock_audio_driver_player.play.assert_called_once_with()

    def assert_driver_player_stopped(self):
        self.mock_audio_driver_player.stop.assert_called_once_with()

    def assert_driver_player_cleared(self):
        self.mock_audio_driver_player.clear.assert_called_once_with()

    def assert_source_seek(self, source, time):
        source._get_queue_source.return_value.seek.assert_called_once_with(
            time)

    def assert_new_texture_created(self, video_format):
        self.mock_texture_create.assert_called_once_with(video_format.width,
                                                         video_format.height,
                                                         rectangle=True)

    def assert_no_new_texture_created(self):
        self.assertFalse(self.mock_texture_create.called)

    def assert_texture_updated(self, frame_data):
        self.mock_texture.blit_into.assert_called_once_with(
            frame_data, 0, 0, 0)

    def assert_texture_not_updated(self):
        self.assertFalse(self.mock_texture.blit_into.called)

    def assert_update_texture_scheduled(self, period):
        self.mock_clock.schedule_interval.assert_called_once_with(
            self.player.update_texture, period)

    def assert_update_texture_unscheduled(self):
        self.mock_clock.unschedule.assert_called_once_with(
            self.player.update_texture)

    def pretend_driver_player_at_time(self, t):
        self.mock_audio_driver_player.get_time.return_value = t

    def pretend_silent_driver_player_at_time(self, t):
        self.mock_silent_audio_driver_player.get_time.return_value = t

    def test_queue_single_audio_source_and_play(self):
        """Queue a single audio source and start playing it."""

        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)

    def test_queue_multiple_audio_sources_same_format_and_play(self):
        """Queue multiple audio sources using the same audio format and start playing."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)

        self.player.queue(mock_source1)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1, mock_source2,
                                              mock_source3)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

    def test_queue_multiple_audio_sources_different_format_and_play_and_skip(
            self):
        """Queue multiple audio sources having different formats and start playing. Different
        formats should be played by seperate driver players."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        mock_source3 = self.create_mock_source(self.audio_format_3, None)

        self.player.queue(mock_source1)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source2)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source3)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source3)

    def test_queue_multiple_audio_sources_same_format_and_play_and_skip(self):
        """When multiple audio sources with the same format are queued, they are played using the
        same driver player. Skipping to the next source is just advancing the source group.
        """
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1, mock_source2,
                                              mock_source3)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.next_source()
        self.assert_in_current_playing_source_group(mock_source2, mock_source3)
        self.assert_driver_player_not_destroyed()
        self.assert_no_new_driver_player_created()
        self.assert_now_playing(mock_source2)

        self.reset_mocks()
        self.player.next_source()
        self.assert_in_current_playing_source_group(mock_source3)
        self.assert_driver_player_not_destroyed()
        self.assert_no_new_driver_player_created()
        self.assert_now_playing(mock_source3)

    def test_on_eos_ignored(self):
        """The player receives on_eos for every source, but does not need to do anything."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1, mock_source2,
                                              mock_source3)
        self.assert_driver_player_started()

        self.reset_mocks()
        self.player.dispatch_event('on_eos')
        self.assert_driver_player_not_destroyed()
        # The following is not completely realistic, in normal cases the source group would have
        # advanced to the next source, but in this case we want to see it is also not manually
        # advanced by the player
        self.assert_in_current_playing_source_group(mock_source1, mock_source2,
                                                    mock_source3)

    def test_on_source_group_eos_advance_to_next_group(self):
        """If a source group is depleted and a next group is available, start a new player for
        the next group."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        mock_source3 = self.create_mock_source(self.audio_format_3, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()

        self.reset_mocks()
        self.player.dispatch_event('on_source_group_eos')
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        self.assert_now_playing(mock_source2)

    def test_player_stops_after_last_group_eos(self):
        """If the last or only source group is eos, the player stops."""
        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)

        self.reset_mocks()
        self.player.dispatch_event('on_source_group_eos')
        self.assert_driver_player_destroyed()
        self.assert_not_playing(None)

    def test_eos_events(self):
        """Test receiving various eos events: on source eos, on source group eos and on player eos.
        """
        on_eos_mock = mock.MagicMock(return_value=None)
        self.player.event('on_eos')(on_eos_mock)
        on_source_group_eos_mock = mock.MagicMock(return_value=None)
        self.player.event('on_source_group_eos')(on_source_group_eos_mock)
        on_player_eos_mock = mock.MagicMock(return_value=None)
        self.player.event('on_player_eos')(on_player_eos_mock)

        def reset_eos_mocks():
            on_eos_mock.reset_mock()
            on_source_group_eos_mock.reset_mock()
            on_player_eos_mock.reset_mock()

        def assert_eos_events_received(on_eos=False,
                                       on_source_group_eos=False,
                                       on_player_eos=False):
            self.assertEqual(on_eos_mock.called, on_eos)
            self.assertEqual(on_source_group_eos_mock.called,
                             on_source_group_eos)
            self.assertEqual(on_player_eos_mock.called, on_player_eos)

        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_2, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1, mock_source2)

        self.reset_mocks()
        reset_eos_mocks()
        # Pretend the current source in the group was eos and next source started
        self.current_playing_source_group.next_source()
        self.player.dispatch_event('on_eos')
        self.assert_driver_player_not_destroyed()
        assert_eos_events_received(on_eos=True)

        self.reset_mocks()
        reset_eos_mocks()
        # Pretend current source group is eos, triggers player to play next source group on a new
        # player
        self.player.dispatch_event('on_source_group_eos')
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source3)
        assert_eos_events_received(on_source_group_eos=True)

        self.reset_mocks()
        reset_eos_mocks()
        # Pretend current source group is eos. Should be no more source groups to play.
        self.player.dispatch_event('on_source_group_eos')
        self.assert_driver_player_destroyed()
        self.assert_not_playing(None)
        assert_eos_events_received(on_source_group_eos=True,
                                   on_player_eos=True)

    def test_pause_resume(self):
        """A stream can be paused. After that play will resume where paused."""
        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.5)
        self.player.pause()
        self.assert_driver_player_stopped()
        self.assert_driver_player_not_destroyed()

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.6)
        self.assertEqual(
            self.player.time,
            0.5,
            msg='While paused, player should returned paused time')

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_started()
        self.assert_no_new_driver_player_created()
        self.assert_now_playing(mock_source)
        self.assertEqual(
            self.player.time,
            0.6,
            msg='While playing, player should return time from driver player')

    def test_delete(self):
        """Test clean up of the player when delete() is called."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        mock_source3 = self.create_mock_source(self.audio_format_3, None)

        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.player.queue(mock_source3)
        self.assert_not_playing_yet(mock_source1)

        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()

        self.reset_mocks()
        self.pretend_driver_player_at_time(1.)
        self.player.delete()
        self.assert_driver_player_stopped()
        self.assert_driver_player_destroyed()

    def test_empty_player(self):
        """A player without queued sources should not start a driver player and should not raise
        exceptions"""
        self.assert_not_playing_yet(None)

        self.reset_mocks()
        self.player.play()
        self.assert_no_new_driver_player_created()

        self.reset_mocks()
        self.player.pause()
        self.assert_no_new_driver_player_created()
        self.assert_driver_player_not_destroyed()

        self.reset_mocks()
        self.player.next_source()
        self.assert_no_new_driver_player_created()
        self.assert_driver_player_not_destroyed()

        self.reset_mocks()
        self.player.seek(0.8)
        self.assert_no_new_driver_player_created()
        self.assert_driver_player_not_destroyed()

        self.player.delete()

    def test_set_player_properties_before_playing(self):
        """When setting player properties before a driver specific player is created, these settings
        should be propagated after creating the player."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.reset_mocks()
        self.player.volume = 10.
        self.player.min_distance = 2.
        self.player.max_distance = 3.
        self.player.position = (4, 4, 4)
        self.player.pitch = 5.0
        self.player.cone_orientation = (6, 6, 6)
        self.player.cone_inner_angle = 7.
        self.player.cone_outer_angle = 8.
        self.player.cone_outer_gain = 9.

        def assert_properties_set():
            self.mock_audio_driver_player.set_volume.assert_called_once_with(
                10.)
            self.mock_audio_driver_player.set_min_distance.assert_called_once_with(
                2.)
            self.mock_audio_driver_player.set_max_distance.assert_called_once_with(
                3.)
            self.mock_audio_driver_player.set_position.assert_called_once_with(
                (4, 4, 4))
            self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
            self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with(
                (6, 6, 6))
            self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(
                7.)
            self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(
                8.)
            self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(
                9.)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_now_playing(mock_source1)
        assert_properties_set()

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        assert_properties_set()

    def test_set_player_properties_while_playing(self):
        """When setting player properties while playing, the properties should be propagated to the
        driver specific player right away."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_2, None)
        self.player.queue(mock_source1)
        self.player.queue(mock_source2)
        self.assert_not_playing_yet(mock_source1)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.volume = 10.
        self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)

        self.reset_mocks()
        self.player.min_distance = 2.
        self.mock_audio_driver_player.set_min_distance.assert_called_once_with(
            2.)

        self.reset_mocks()
        self.player.max_distance = 3.
        self.mock_audio_driver_player.set_max_distance.assert_called_once_with(
            3.)

        self.reset_mocks()
        self.player.position = (4, 4, 4)
        self.mock_audio_driver_player.set_position.assert_called_once_with(
            (4, 4, 4))

        self.reset_mocks()
        self.player.pitch = 5.0
        self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)

        self.reset_mocks()
        self.player.cone_orientation = (6, 6, 6)
        self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with(
            (6, 6, 6))

        self.reset_mocks()
        self.player.cone_inner_angle = 7.
        self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(
            7.)

        self.reset_mocks()
        self.player.cone_outer_angle = 8.
        self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(
            8.)

        self.reset_mocks()
        self.player.cone_outer_gain = 9.
        self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(
            9.)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2)
        self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
        self.mock_audio_driver_player.set_min_distance.assert_called_once_with(
            2.)
        self.mock_audio_driver_player.set_max_distance.assert_called_once_with(
            3.)
        self.mock_audio_driver_player.set_position.assert_called_once_with(
            (4, 4, 4))
        self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
        self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with(
            (6, 6, 6))
        self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(
            7.)
        self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(
            8.)
        self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(
            9.)

    def test_seek(self):
        """Test seeking to a specific time in the current source."""
        mock_source = self.create_mock_source(self.audio_format_1, None)
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.reset_mocks()
        mock_source.reset_mock()
        self.player.seek(0.7)
        self.assert_source_seek(mock_source, 0.7)

        self.reset_mocks()
        mock_source.reset_mock()
        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_now_playing(mock_source)

        self.reset_mocks()
        mock_source.reset_mock()
        self.player.seek(0.2)
        self.assert_source_seek(mock_source, 0.2)
        # Clear buffers for immediate result
        self.assert_driver_player_cleared()

    def test_queue_source_group(self):
        """Source groups can also be queued. They are added as is without checking compatibility
        with the current source group."""
        mock_source1 = self.create_mock_source(self.audio_format_1, None)
        mock_source2 = self.create_mock_source(self.audio_format_1, None)
        mock_source3 = self.create_mock_source(self.audio_format_1, None)
        group = SourceGroup(self.audio_format_1, None)
        group.queue(mock_source2)
        group.queue(mock_source3)

        self.player.queue(mock_source1)
        self.player.queue(group)
        self.assert_not_playing_yet(mock_source1)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source1)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source1)

        self.reset_mocks()
        self.player.next_source()
        self.assert_driver_player_destroyed()
        self.assert_driver_player_created_for(mock_source2, mock_source3)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source2)

    def test_video_queue_and_play(self):
        """Sources can also include video. Instead of using a player to continuously play the video
        a texture is updated on a fixed interval."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.2, 'a')])
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.reset_mocks()
        self.player.play()
        self.assert_driver_player_created_for(mock_source)
        self.assert_driver_player_started()
        self.assert_now_playing(mock_source)
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled(1 / 30)

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.2)
        self.player.update_texture()
        self.assert_texture_updated('a')
        self.assertIs(self.player.get_texture(), self.mock_texture)

    def test_video_peek_before_play(self):
        """Before starting to play a video source, we can peek and see a frame. It will then
        wait for the next frame when play has started."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.1, 'a'),
                                                          (0.2, 'b')])
        self.player.queue(mock_source)
        self.assert_not_playing_yet(mock_source)

        self.reset_mocks()
        self.player.update_texture(time=0.1)
        self.assert_new_texture_created(self.video_format_1)
        self.assert_not_playing_yet(mock_source)
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.player.play()
        self.assert_now_playing(mock_source)
        self.assert_update_texture_scheduled(1 / 30)
        self.assert_no_new_texture_created()
        self.assert_texture_not_updated()

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.0)
        self.player.update_texture()
        self.assert_no_new_texture_created()
        self.assert_texture_not_updated()

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.1)
        self.player.update_texture()
        self.assert_no_new_texture_created()
        self.assert_texture_not_updated()

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.2)
        self.player.update_texture()
        self.assert_no_new_texture_created()
        self.assert_texture_updated('b')

    def test_video_seek(self):
        """Sources with video can also be seeked. This will cause the audio source to seek and
        video will follow the timestamps."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source,
                                            [(0.0, 'a'), (0.1, 'b'),
                                             (0.2, 'c'), (0.3, 'd'),
                                             (0.4, 'e'), (0.5, 'f')])
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled(1 / 30)

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.0)
        self.player.update_texture()
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.player.seek(0.3)
        self.assert_source_seek(mock_source, 0.3)
        self.assert_no_new_texture_created()
        self.assert_texture_updated('d')

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.3)
        self.player.update_texture()
        self.assert_texture_not_updated()

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.4)
        self.player.update_texture()
        self.assert_texture_updated('e')

    def test_video_frame_rate(self):
        """Videos with different framerates need to be rescheduled at the clock."""
        mock_source1 = self.create_mock_source(self.audio_format_1,
                                               self.video_format_1)
        mock_source2 = self.create_mock_source(self.audio_format_1,
                                               self.video_format_2)
        self.player.queue(mock_source1)
        self.player.queue(mock_source2)

        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled(1 / 30)

        self.reset_mocks()
        self.player.next_source()
        self.assert_new_texture_created(self.video_format_2)
        self.assert_update_texture_unscheduled()
        self.assert_update_texture_scheduled(1 / 25)

    def test_video_seek_next_frame(self):
        """It is possible to jump directly to the next frame of video and adjust the audio player
        accordingly."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'),
                                                          (0.2, 'b')])
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled(1 / 30)

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.0)
        self.player.update_texture()
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.player.seek_next_frame()
        self.assert_source_seek(mock_source, 0.2)
        self.assert_texture_updated('b')

    def test_video_runs_out_of_frames(self):
        """When the video runs out of frames, it stops updating the texture. The audio player is
        responsible for triggering eos."""
        mock_source = self.create_mock_source(self.audio_format_1,
                                              self.video_format_1)
        self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'),
                                                          (0.1, 'b')])
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled(1 / 30)

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.0)
        self.player.update_texture()
        self.assert_texture_updated('a')

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.1)
        self.player.update_texture()
        self.assert_texture_updated('b')

        self.reset_mocks()
        self.pretend_driver_player_at_time(0.2)
        self.player.update_texture()
        self.assert_texture_not_updated()

        self.reset_mocks()
        self.player.seek_next_frame()
        self.assert_texture_not_updated()

    def test_video_without_audio(self):
        """It is possible to have videos without audio streams. A special audio driver will take
        care of providing the timing."""
        mock_source = self.create_mock_source(None, self.video_format_1)
        self.player.queue(mock_source)
        self.player.play()
        self.assert_new_texture_created(self.video_format_1)
        self.assert_update_texture_scheduled(1 / 30)
        self.assert_no_new_driver_player_created()
        self.assert_silent_driver_player_created_for(mock_source)

        self.reset_mocks()
        self.pretend_silent_driver_player_at_time(1.)
        self.player.delete()
        self.assert_silent_driver_player_destroyed()