1import random 2from unittest import mock 3 4from tests.base.future_test import FutureTestCase 5 6from pyglet.media.player import Player, PlayerGroup 7from pyglet.media.codecs.base import AudioFormat, VideoFormat, Source 8 9 10class PlayerTestCase(FutureTestCase): 11 # Default values to use 12 audio_format_1 = AudioFormat(1, 8, 11025) 13 audio_format_2 = AudioFormat(2, 8, 11025) 14 audio_format_3 = AudioFormat(2, 16, 44100) 15 video_format_1 = VideoFormat(800, 600) 16 video_format_2 = VideoFormat(1920, 1280) 17 video_format_2.frame_rate = 25 18 19 def setUp(self): 20 self.player = Player() 21 22 self._get_audio_driver_patcher = mock.patch('pyglet.media.player.get_audio_driver') 23 self.mock_get_audio_driver = self._get_audio_driver_patcher.start() 24 self.mock_audio_driver = self.mock_get_audio_driver.return_value 25 self.mock_audio_driver_player = self.mock_audio_driver.create_audio_player.return_value 26 27 self._clock_patcher = mock.patch('pyglet.clock') 28 self.mock_clock = self._clock_patcher.start() 29 30 self._texture_patcher = mock.patch('pyglet.image.Texture.create') 31 self.mock_texture_create = self._texture_patcher.start() 32 self.mock_texture = self.mock_texture_create.return_value 33 # Need to do this as side_effect instead of return_value, or reset_mock will recurse 34 self.mock_texture.get_transform.side_effect = lambda flip_y: self.mock_texture 35 36 def tearDown(self): 37 self._get_audio_driver_patcher.stop() 38 self._clock_patcher.stop() 39 self._texture_patcher.stop() 40 41 def reset_mocks(self): 42 # These mocks will recursively reset their children 43 self.mock_get_audio_driver.reset_mock() 44 self.mock_clock.reset_mock() 45 self.mock_texture_create.reset_mock() 46 47 def create_mock_source(self, audio_format, video_format): 48 mock_source = mock.MagicMock(spec=Source) 49 type(mock_source).audio_format = mock.PropertyMock(return_value=audio_format) 50 type(mock_source).video_format = mock.PropertyMock(return_value=video_format) 51 type(mock_source.get_queue_source.return_value).audio_format = mock.PropertyMock(return_value=audio_format) 52 type(mock_source.get_queue_source.return_value).video_format = mock.PropertyMock(return_value=video_format) 53 if video_format: 54 mock_source.video_format.frame_rate = 30 55 return mock_source 56 57 def set_video_data_for_mock_source(self, mock_source, timestamp_data_pairs): 58 """Make the given mock source return video data. Video data is given in pairs of 59 timestamp and data to return.""" 60 def _get_frame(): 61 if timestamp_data_pairs: 62 current_frame = timestamp_data_pairs.pop(0) 63 return current_frame[1] 64 65 def _get_timestamp(): 66 if timestamp_data_pairs: 67 return timestamp_data_pairs[0][0] 68 queue_source = mock_source.get_queue_source.return_value 69 queue_source.get_next_video_timestamp.side_effect = _get_timestamp 70 queue_source.get_next_video_frame.side_effect = _get_frame 71 72 def assert_not_playing_yet(self, current_source=None): 73 """Assert the the player did not start playing yet.""" 74 self.assertFalse(self.mock_get_audio_driver.called, msg='No audio driver required yet') 75 self.assertAlmostEqual(self.player.time, 0.) 76 self.assert_not_playing(current_source) 77 78 def assert_not_playing(self, current_source=None): 79 self._assert_playing(False, current_source) 80 81 def assert_now_playing(self, current_source): 82 self._assert_playing(True, current_source) 83 84 def _assert_playing(self, playing, current_source=None): 85 self.assertEqual(self.player.playing, playing) 86 queued_source = (current_source.get_queue_source.return_value 87 if current_source is not None 88 else None) 89 self.assertIs(self.player.source, queued_source) 90 91 def assert_driver_player_created_for(self, source): 92 """Assert that a driver specific audio player is created to play back given source""" 93 self._assert_player_created_for(self.mock_get_audio_driver, self.mock_audio_driver, source) 94 95 def _assert_player_created_for(self, mock_get_audio_driver, mock_audio_driver, source): 96 mock_get_audio_driver.assert_called_once_with() 97 self.assertEqual(mock_audio_driver.create_audio_player.call_count, 1) 98 call_args = mock_audio_driver.create_audio_player.call_args 99 args, kwargs = call_args 100 arg_source, arg_player = args 101 self.assertIs(arg_source, source.get_queue_source.return_value) 102 self.assertIs(arg_player, self.player) 103 104 def assert_no_new_driver_player_created(self): 105 """Assert that no new driver specific audio player is created.""" 106 self.assertFalse(self.mock_get_audio_driver.called, msg='No new audio driver should be created') 107 108 # def assert_in_current_play_list(self, *sources): 109 # self.assertIsNotNone(self.current_play_list, msg='No previous call to create driver player') 110 111 # queue_sources = deque(source.get_queue_source.return_value for source in sources) 112 # self.assertSequenceEqual(self.current_play_list._sources, queue_sources) 113 114 def assert_driver_player_destroyed(self): 115 self.mock_audio_driver_player.delete.assert_called_once_with() 116 117 def assert_driver_player_not_destroyed(self): 118 self.assertFalse(self.mock_audio_driver_player.delete.called) 119 120 def assert_silent_driver_player_destroyed(self): 121 self.mock_silent_audio_driver_player.delete.assert_called_once_with() 122 123 def assert_driver_player_started(self): 124 self.mock_audio_driver_player.play.assert_called_once_with() 125 126 def assert_driver_player_stopped(self): 127 self.mock_audio_driver_player.stop.assert_called_once_with() 128 129 def assert_driver_player_cleared(self): 130 self.mock_audio_driver_player.clear.assert_called_once_with() 131 132 def assert_source_seek(self, source, time): 133 source.get_queue_source.return_value.seek.assert_called_once_with(time) 134 135 def assert_new_texture_created(self, video_format): 136 self.mock_texture_create.assert_called_once_with(video_format.width, video_format.height, rectangle=True) 137 138 def assert_no_new_texture_created(self): 139 self.assertFalse(self.mock_texture_create.called) 140 141 def assert_texture_updated(self, frame_data): 142 self.mock_texture.blit_into.assert_called_once_with(frame_data, 0, 0, 0) 143 144 def assert_texture_not_updated(self): 145 self.assertFalse(self.mock_texture.blit_into.called) 146 147 def assert_update_texture_scheduled(self): 148 self.mock_clock.schedule_once.assert_called_once_with(self.player.update_texture, 0) 149 150 def assert_update_texture_unscheduled(self): 151 self.mock_clock.unschedule.assert_called_with(self.player.update_texture) 152 153 def pretend_player_at_time(self, t): 154 self.player._timer.set_time(t) 155 156 def pretend_silent_driver_player_at_time(self, t): 157 self.mock_silent_audio_driver_player.get_time.return_value = t 158 159 def test_queue_single_audio_source_and_play(self): 160 """Queue a single audio source and start playing it.""" 161 162 mock_source = self.create_mock_source(self.audio_format_1, None) 163 self.player.queue(mock_source) 164 self.assert_not_playing_yet(mock_source) 165 166 self.player.play() 167 self.assert_driver_player_created_for(mock_source) 168 self.assert_driver_player_started() 169 self.assert_now_playing(mock_source) 170 171 def test_queue_multiple_audio_sources_same_format_and_play(self): 172 """Queue multiple audio sources using the same audio format and start playing.""" 173 mock_source1 = self.create_mock_source(self.audio_format_1, None) 174 mock_source2 = self.create_mock_source(self.audio_format_1, None) 175 mock_source3 = self.create_mock_source(self.audio_format_1, None) 176 177 self.player.queue(mock_source1) 178 self.assert_not_playing_yet(mock_source1) 179 180 self.player.queue(mock_source2) 181 self.assert_not_playing_yet(mock_source1) 182 183 self.player.queue(mock_source3) 184 self.assert_not_playing_yet(mock_source1) 185 186 self.player.play() 187 self.assert_driver_player_created_for(mock_source1) 188 self.assert_driver_player_started() 189 self.assert_now_playing(mock_source1) 190 191 def test_queue_multiple_audio_sources_different_format_and_play_and_skip(self): 192 """Queue multiple audio sources having different formats and start playing. Different 193 formats should be played by seperate driver players.""" 194 mock_source1 = self.create_mock_source(self.audio_format_1, None) 195 mock_source2 = self.create_mock_source(self.audio_format_2, None) 196 mock_source3 = self.create_mock_source(self.audio_format_3, None) 197 198 self.player.queue(mock_source1) 199 self.assert_not_playing_yet(mock_source1) 200 201 self.player.queue(mock_source2) 202 self.assert_not_playing_yet(mock_source1) 203 204 self.player.queue(mock_source3) 205 self.assert_not_playing_yet(mock_source1) 206 207 self.player.play() 208 self.assert_driver_player_created_for(mock_source1) 209 self.assert_driver_player_started() 210 self.assert_now_playing(mock_source1) 211 212 self.reset_mocks() 213 self.player.next_source() 214 self.assert_driver_player_destroyed() 215 self.assert_driver_player_created_for(mock_source2) 216 self.assert_driver_player_started() 217 self.assert_now_playing(mock_source2) 218 219 self.reset_mocks() 220 self.player.next_source() 221 self.assert_driver_player_destroyed() 222 self.assert_driver_player_created_for(mock_source3) 223 self.assert_driver_player_started() 224 self.assert_now_playing(mock_source3) 225 226 def test_queue_multiple_audio_sources_same_format_and_play_and_skip(self): 227 """When multiple audio sources with the same format are queued, they are played 228 using the same driver player. Skipping to the next source is just advancing the 229 source group. 230 """ 231 mock_source1 = self.create_mock_source(self.audio_format_1, None) 232 mock_source2 = self.create_mock_source(self.audio_format_1, None) 233 mock_source3 = self.create_mock_source(self.audio_format_1, None) 234 235 self.player.queue(mock_source1) 236 self.player.queue(mock_source2) 237 self.player.queue(mock_source3) 238 239 self.player.play() 240 self.assert_driver_player_created_for(mock_source1) 241 self.assert_driver_player_started() 242 self.assert_now_playing(mock_source1) 243 244 self.reset_mocks() 245 self.player.next_source() 246 self.assert_driver_player_not_destroyed() 247 self.assert_no_new_driver_player_created() 248 self.assert_now_playing(mock_source2) 249 250 self.reset_mocks() 251 self.player.next_source() 252 self.assert_driver_player_not_destroyed() 253 self.assert_no_new_driver_player_created() 254 self.assert_now_playing(mock_source3) 255 256 def test_on_eos(self): 257 """The player receives on_eos for every source, but does not need to do anything. 258 """ 259 mock_source1 = self.create_mock_source(self.audio_format_1, None) 260 mock_source2 = self.create_mock_source(self.audio_format_1, None) 261 mock_source3 = self.create_mock_source(self.audio_format_1, None) 262 263 self.player.queue(mock_source1) 264 self.player.queue(mock_source2) 265 self.player.queue(mock_source3) 266 267 self.player.play() 268 self.assert_driver_player_created_for(mock_source1) 269 self.assert_driver_player_started() 270 271 self.reset_mocks() 272 self.player.dispatch_event('on_eos') 273 self.assert_driver_player_not_destroyed() 274 275 def test_player_stops_after_last_eos(self): 276 """If the last playlist source is eos, the player stops.""" 277 mock_source = self.create_mock_source(self.audio_format_1, None) 278 self.player.queue(mock_source) 279 self.assert_not_playing_yet(mock_source) 280 281 self.player.play() 282 self.assert_driver_player_created_for(mock_source) 283 self.assert_driver_player_started() 284 self.assert_now_playing(mock_source) 285 286 self.reset_mocks() 287 self.player.dispatch_event('on_eos') 288 self.assert_driver_player_destroyed() 289 self.assert_not_playing(None) 290 291 def test_eos_events(self): 292 """Test receiving various eos events: on source eos, 293 on playlist exhausted and on player eos and on player next source. 294 """ 295 on_eos_mock = mock.MagicMock(return_value=None) 296 self.player.event('on_eos')(on_eos_mock) 297 on_player_eos_mock = mock.MagicMock(return_value=None) 298 self.player.event('on_player_eos')(on_player_eos_mock) 299 on_player_next_source_mock = mock.MagicMock(return_value=None) 300 self.player.event('on_player_next_source')(on_player_next_source_mock) 301 302 def reset_eos_mocks(): 303 on_eos_mock.reset_mock() 304 on_player_eos_mock.reset_mock() 305 on_player_next_source_mock.reset_mock() 306 307 def assert_eos_events_received(on_eos=False, on_player_eos=False, 308 on_player_next_source=False): 309 self.assertEqual(on_eos_mock.called, on_eos) 310 self.assertEqual(on_player_eos_mock.called, on_player_eos) 311 self.assertEqual(on_player_next_source_mock.called, on_player_next_source) 312 313 mock_source1 = self.create_mock_source(self.audio_format_1, None) 314 mock_source2 = self.create_mock_source(self.audio_format_1, None) 315 mock_source3 = self.create_mock_source(self.audio_format_2, None) 316 317 self.player.queue(mock_source1) 318 self.player.queue(mock_source2) 319 self.player.queue(mock_source3) 320 self.assert_not_playing_yet(mock_source1) 321 322 self.player.play() 323 self.assert_driver_player_created_for(mock_source1) 324 325 self.reset_mocks() 326 reset_eos_mocks() 327 # Pretend the current source in the group was eos and next source started 328 self.player.dispatch_event('on_eos') 329 self.assert_driver_player_not_destroyed() 330 assert_eos_events_received(on_eos=True, on_player_next_source=True) 331 self.assertEqual(len(self.player._playlists), 2) 332 333 # Pretend playlist is exhausted. Should be no more sources to play. 334 self.player.dispatch_event('on_eos') 335 self.reset_mocks() 336 reset_eos_mocks() 337 self.player.dispatch_event('on_eos') 338 self.assert_not_playing(None) 339 assert_eos_events_received(on_eos=True, on_player_eos=True) 340 341 def test_pause_resume(self): 342 """A stream can be paused. After that play will resume where paused.""" 343 mock_source = self.create_mock_source(self.audio_format_1, None) 344 self.player.queue(mock_source) 345 self.player.play() 346 self.assert_driver_player_created_for(mock_source) 347 self.assert_driver_player_started() 348 self.assert_now_playing(mock_source) 349 350 self.reset_mocks() 351 self.pretend_player_at_time(0.5) 352 self.player.pause() 353 self.assert_driver_player_stopped() 354 self.assert_driver_player_not_destroyed() 355 356 self.reset_mocks() 357 self.player.play() 358 self.assertAlmostEqual( 359 self.player.time, 0.5, places=2, 360 msg='While playing, player should return time from driver player' 361 ) 362 self.assert_driver_player_started() 363 self.assert_no_new_driver_player_created() 364 self.assert_now_playing(mock_source) 365 366 def test_delete(self): 367 """Test clean up of the player when delete() is called.""" 368 mock_source1 = self.create_mock_source(self.audio_format_1, None) 369 mock_source2 = self.create_mock_source(self.audio_format_2, None) 370 mock_source3 = self.create_mock_source(self.audio_format_3, None) 371 372 self.player.queue(mock_source1) 373 self.player.queue(mock_source2) 374 self.player.queue(mock_source3) 375 self.assert_not_playing_yet(mock_source1) 376 377 self.player.play() 378 self.assert_driver_player_created_for(mock_source1) 379 self.assert_driver_player_started() 380 381 self.reset_mocks() 382 self.pretend_player_at_time(1.) 383 self.player.delete() 384 self.assert_driver_player_destroyed() 385 386 def test_empty_player(self): 387 """A player without queued sources should not start a driver player and should not raise 388 exceptions""" 389 self.assert_not_playing_yet(None) 390 391 self.reset_mocks() 392 self.player.play() 393 self.assert_no_new_driver_player_created() 394 395 self.reset_mocks() 396 self.player.pause() 397 self.assert_no_new_driver_player_created() 398 self.assert_driver_player_not_destroyed() 399 400 self.reset_mocks() 401 self.player.next_source() 402 self.assert_no_new_driver_player_created() 403 self.assert_driver_player_not_destroyed() 404 405 self.reset_mocks() 406 self.player.seek(0.8) 407 self.assert_no_new_driver_player_created() 408 self.assert_driver_player_not_destroyed() 409 410 self.player.delete() 411 412 def test_set_player_properties_before_playing(self): 413 """When setting player properties before a driver specific player is 414 created, these settings should be propagated after creating the 415 player. 416 """ 417 mock_source1 = self.create_mock_source(self.audio_format_1, None) 418 mock_source2 = self.create_mock_source(self.audio_format_2, None) 419 self.player.queue(mock_source1) 420 self.player.queue(mock_source2) 421 self.assert_not_playing_yet(mock_source1) 422 423 self.reset_mocks() 424 self.player.volume = 10. 425 self.player.min_distance = 2. 426 self.player.max_distance = 3. 427 self.player.position = (4, 4, 4) 428 self.player.pitch = 5.0 429 self.player.cone_orientation = (6, 6, 6) 430 self.player.cone_inner_angle = 7. 431 self.player.cone_outer_angle = 8. 432 self.player.cone_outer_gain = 9. 433 434 def assert_properties_set(): 435 self.mock_audio_driver_player.set_volume.assert_called_once_with(10.) 436 self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.) 437 self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.) 438 self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4)) 439 self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.) 440 self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6)) 441 self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.) 442 self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.) 443 self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.) 444 445 self.reset_mocks() 446 self.player.play() 447 self.assert_driver_player_created_for(mock_source1) 448 self.assert_now_playing(mock_source1) 449 assert_properties_set() 450 451 self.reset_mocks() 452 self.player.next_source() 453 self.assert_driver_player_destroyed() 454 self.assert_driver_player_created_for(mock_source2) 455 assert_properties_set() 456 457 def test_set_player_properties_while_playing(self): 458 """When setting player properties while playing, the properties should 459 be propagated to the driver specific player right away.""" 460 mock_source1 = self.create_mock_source(self.audio_format_1, None) 461 mock_source2 = self.create_mock_source(self.audio_format_2, None) 462 self.player.queue(mock_source1) 463 self.player.queue(mock_source2) 464 self.assert_not_playing_yet(mock_source1) 465 466 self.reset_mocks() 467 self.player.play() 468 self.assert_driver_player_created_for(mock_source1) 469 self.assert_now_playing(mock_source1) 470 471 self.reset_mocks() 472 self.player.volume = 10. 473 self.mock_audio_driver_player.set_volume.assert_called_once_with(10.) 474 475 self.reset_mocks() 476 self.player.min_distance = 2. 477 self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.) 478 479 self.reset_mocks() 480 self.player.max_distance = 3. 481 self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.) 482 483 self.reset_mocks() 484 self.player.position = (4, 4, 4) 485 self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4)) 486 487 self.reset_mocks() 488 self.player.pitch = 5.0 489 self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.) 490 491 self.reset_mocks() 492 self.player.cone_orientation = (6, 6, 6) 493 self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6)) 494 495 self.reset_mocks() 496 self.player.cone_inner_angle = 7. 497 self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.) 498 499 self.reset_mocks() 500 self.player.cone_outer_angle = 8. 501 self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.) 502 503 self.reset_mocks() 504 self.player.cone_outer_gain = 9. 505 self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.) 506 507 self.reset_mocks() 508 self.player.next_source() 509 self.assert_driver_player_destroyed() 510 self.assert_driver_player_created_for(mock_source2) 511 self.mock_audio_driver_player.set_volume.assert_called_once_with(10.) 512 self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.) 513 self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.) 514 self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4)) 515 self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.) 516 self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6)) 517 self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.) 518 self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.) 519 self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.) 520 521 def test_seek(self): 522 """Test seeking to a specific time in the current source.""" 523 mock_source = self.create_mock_source(self.audio_format_1, None) 524 self.player.queue(mock_source) 525 self.assert_not_playing_yet(mock_source) 526 527 self.reset_mocks() 528 mock_source.reset_mock() 529 self.player.seek(0.7) 530 self.assert_source_seek(mock_source, 0.7) 531 532 self.reset_mocks() 533 mock_source.reset_mock() 534 self.player.play() 535 self.assert_driver_player_created_for(mock_source) 536 self.assert_now_playing(mock_source) 537 538 self.reset_mocks() 539 mock_source.reset_mock() 540 self.player.seek(0.2) 541 self.assert_source_seek(mock_source, 0.2) 542 # Clear buffers for immediate result 543 self.assert_driver_player_cleared() 544 545 def test_video_queue_and_play(self): 546 """Sources can also include video. Instead of using a player to 547 continuously play the video, a texture is updated based on the 548 video packet timestamp.""" 549 mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1) 550 self.set_video_data_for_mock_source(mock_source, [(0.2, 'a')]) 551 self.player.queue(mock_source) 552 self.assert_not_playing_yet(mock_source) 553 554 self.reset_mocks() 555 self.player.play() 556 self.assert_driver_player_created_for(mock_source) 557 self.assert_driver_player_started() 558 self.assert_now_playing(mock_source) 559 self.assert_new_texture_created(self.video_format_1) 560 self.assert_update_texture_scheduled() 561 562 self.reset_mocks() 563 self.pretend_player_at_time(0.2) 564 self.player.update_texture() 565 self.assert_texture_updated('a') 566 self.assertIs(self.player.texture, self.mock_texture) 567 568 def test_video_seek(self): 569 """Sources with video can also be seeked. It's the Source 570 responsibility to present the Player with audio and video at the 571 correct time.""" 572 mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1) 573 self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.1, 'b'), (0.2, 'c'), 574 (0.3, 'd'), (0.4, 'e'), (0.5, 'f')]) 575 self.player.queue(mock_source) 576 self.player.play() 577 self.assert_new_texture_created(self.video_format_1) 578 self.assert_update_texture_scheduled() 579 580 self.reset_mocks() 581 self.pretend_player_at_time(0.0) 582 self.player.update_texture() 583 self.assert_texture_updated('a') 584 585 self.reset_mocks() 586 self.player.seek(0.3) 587 self.assert_source_seek(mock_source, 0.3) 588 self.assert_no_new_texture_created() 589 self.assert_texture_updated('d') 590 591 self.reset_mocks() 592 self.pretend_player_at_time(0.4) 593 self.player.update_texture() 594 self.assert_texture_updated('e') 595 596 def test_video_frame_rate(self): 597 """Videos texture are scheduled according to the video packet 598 timestamp.""" 599 mock_source1 = self.create_mock_source(self.audio_format_1, self.video_format_1) 600 mock_source2 = self.create_mock_source(self.audio_format_1, self.video_format_2) 601 for mock_source in (mock_source1, mock_source2): 602 self.set_video_data_for_mock_source( 603 mock_source, 604 [(0.0, 'a'), (0.1, 'b'), (0.2, 'c'), 605 (0.3, 'd'), (0.4, 'e'), (0.5, 'f')] 606 ) 607 608 self.player.queue(mock_source1) 609 self.player.queue(mock_source2) 610 611 self.player.play() 612 self.assert_new_texture_created(self.video_format_1) 613 self.assert_update_texture_scheduled() 614 615 self.reset_mocks() 616 self.player.next_source() 617 self.assert_new_texture_created(self.video_format_2) 618 self.assert_update_texture_unscheduled() 619 # schedule_once called twice: 620 # - Once for seeking back to 0 the previous source in next_source() 621 # - Once for scheduling the next source update_texture 622 assert self.mock_clock.schedule_once.call_count == 2 623 self.mock_clock.schedule_once.assert_called_with(self.player.update_texture, 0) 624 625 def test_video_seek_next_frame(self): 626 """It is possible to jump directly to the next frame of video and adjust the audio player 627 accordingly.""" 628 mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1) 629 self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.2, 'b')]) 630 self.player.queue(mock_source) 631 self.player.play() 632 self.assert_new_texture_created(self.video_format_1) 633 self.assert_update_texture_scheduled() 634 635 self.reset_mocks() 636 self.pretend_player_at_time(0.0) 637 self.player.update_texture() 638 self.assert_texture_updated('a') 639 640 self.reset_mocks() 641 self.player.seek_next_frame() 642 self.assert_source_seek(mock_source, 0.2) 643 self.assert_texture_updated('b') 644 645 def test_video_runs_out_of_frames(self): 646 """When the video runs out of frames, it stops updating the texture. The audio player is 647 responsible for triggering eos.""" 648 mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1) 649 self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.1, 'b')]) 650 self.player.queue(mock_source) 651 self.player.play() 652 self.assert_new_texture_created(self.video_format_1) 653 self.assert_update_texture_scheduled() 654 655 self.reset_mocks() 656 self.pretend_player_at_time(0.0) 657 self.player.update_texture() 658 self.assert_texture_updated('a') 659 660 self.reset_mocks() 661 self.pretend_player_at_time(0.1) 662 self.player.update_texture() 663 self.assert_texture_updated('b') 664 665 self.reset_mocks() 666 self.pretend_player_at_time(0.2) 667 self.player.update_texture() 668 self.assert_texture_not_updated() 669 670 self.reset_mocks() 671 self.player.seek_next_frame() 672 self.assert_texture_not_updated() 673 674 def test_video_without_audio(self): 675 """It is possible to have videos without audio streams.""" 676 mock_source = self.create_mock_source(None, self.video_format_1) 677 self.player.queue(mock_source) 678 self.player.play() 679 self.assert_new_texture_created(self.video_format_1) 680 self.assert_update_texture_scheduled() 681 self.assert_no_new_driver_player_created() 682 683 def test_audio_source_with_silent_driver(self): 684 """An audio source with a silent driver.""" 685 mock_source = self.create_mock_source(self.audio_format_3, None) 686 self.mock_get_audio_driver.return_value = None 687 self.player.queue(mock_source) 688 self.player.play() 689 690 691class PlayerGroupTestCase(FutureTestCase): 692 def create_mock_player(self, has_audio=True): 693 player = mock.MagicMock() 694 if has_audio: 695 audio_player = mock.PropertyMock(return_value=mock.MagicMock()) 696 else: 697 audio_player = mock.PropertyMock(return_value=None) 698 type(player)._audio_player = audio_player 699 return player 700 701 def assert_players_started(self, *players): 702 for player in players: 703 player.play.assert_called_once_with() 704 705 def assert_audio_players_started(self, *players): 706 # Find the one player that was used to start the group, 707 # the rest should not be used 708 call_args = None 709 audio_players = [] 710 for player in players: 711 audio_player = player._audio_player 712 audio_players.append(audio_player) 713 if call_args is not None: 714 self.assertFalse(audio_player._play_group.called, 715 msg='Only one player should be used to start the group') 716 elif audio_player._play_group.called: 717 call_args = audio_player._play_group.call_args 718 719 self.assertIsNotNone(call_args, 720 msg='No player was used to start all audio players.') 721 started_players = call_args[0][0] 722 self.assertCountEqual(started_players, audio_players, 723 msg='Not all players with audio players were started') 724 725 def assert_players_stopped(self, *players): 726 for player in players: 727 player.pause.assert_called_once_with() 728 729 def assert_audio_players_stopped(self, *players): 730 # Find the one player that was used to start the group, 731 # the rest should not be used 732 call_args = None 733 audio_players = [] 734 for player in players: 735 audio_player = player._audio_player 736 audio_players.append(audio_player) 737 if call_args is not None: 738 self.assertFalse(audio_player._stop_group.called, 739 msg='Only one player should be used to stop the group') 740 elif audio_player._stop_group.called: 741 call_args = audio_player._stop_group.call_args 742 743 self.assertIsNotNone(call_args, 744 msg='No player was used to stop all audio players.') 745 stopped_players = call_args[0][0] 746 self.assertCountEqual(stopped_players, audio_players, 747 msg='Not all players with audio players were stopped') 748 749 def reset_mocks(self, *mocks): 750 for m in mocks: 751 m.reset_mock() 752 753 def test_empty_group(self): 754 """Just check nothing explodes on an empty group.""" 755 group = PlayerGroup([]) 756 group.play() 757 group.pause() 758 759 def test_only_with_audio(self): 760 """Test a group containing only players with audio.""" 761 players = [self.create_mock_player(has_audio=True) for _ in range(10)] 762 group = PlayerGroup(players) 763 764 group.play() 765 self.assert_audio_players_started(*players) 766 self.assert_players_started(*players) 767 self.reset_mocks(*players) 768 769 group.pause() 770 self.assert_audio_players_stopped(*players) 771 self.assert_players_stopped(*players) 772 773 def test_only_without_audio(self): 774 """Test a group containing only players without audio.""" 775 players = [self.create_mock_player(has_audio=False) for _ in range(10)] 776 group = PlayerGroup(players) 777 778 group.play() 779 self.assert_players_started(*players) 780 self.reset_mocks(*players) 781 782 group.pause() 783 self.assert_players_stopped(*players) 784 785 def test_mixed_players(self): 786 """Test a group containing both players with audio and players without audio.""" 787 players_with_audio = [self.create_mock_player(has_audio=True) for _ in range(10)] 788 players_without_audio = [self.create_mock_player(has_audio=False) for _ in range(10)] 789 players = players_with_audio + players_without_audio 790 random.shuffle(players) 791 group = PlayerGroup(players) 792 793 group.play() 794 self.assert_audio_players_started(*players_with_audio) 795 self.assert_players_started(*players) 796 self.reset_mocks(*players) 797 798 group.pause() 799 self.assert_audio_players_stopped(*players_with_audio) 800 self.assert_players_stopped(*players) 801