1'use strict'; 2 3import React from 'react'; 4import { forEach } from 'lodash'; 5import { Video } from 'expo-av'; 6import { Asset } from 'expo-asset'; 7import { Platform } from 'react-native'; 8 9import { waitFor, retryForStatus, mountAndWaitFor as originalMountAndWaitFor } from './helpers'; 10 11export const name = 'Video'; 12const imageRemoteSource = { uri: 'http://via.placeholder.com/350x150' }; 13const videoRemoteSource = { uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }; 14const redirectingVideoRemoteSource = { uri: 'http://bit.ly/2mcW40Q' }; 15let webmSource = require('../assets/unsupported_bunny.webm'); 16let imageSource = require('../assets/black-128x256.png'); 17const mp4Source = require('../assets/big_buck_bunny.mp4'); 18const hlsStreamUri = 'http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8'; 19const hlsStreamUriWithRedirect = 'http://bit.ly/1iy90bn'; 20let source = null; // Local URI of the downloaded default source is set in a beforeAll callback. 21 22const style = { width: 200, height: 200 }; 23 24export function test(t, { setPortalChild, cleanupPortal }) { 25 t.describe('Video', () => { 26 t.beforeAll(async () => { 27 const mp4Asset = Asset.fromModule(mp4Source); 28 await mp4Asset.downloadAsync(); 29 source = { uri: mp4Asset.localUri }; 30 31 const imageAsset = Asset.fromModule(imageSource); 32 await imageAsset.downloadAsync(); 33 imageSource = { uri: imageAsset.localUri }; 34 35 const webmAsset = Asset.fromModule(webmSource); 36 await webmAsset.downloadAsync(); 37 webmSource = { uri: webmAsset.localUri }; 38 }); 39 40 let instance = null; 41 const refSetter = ref => { 42 instance = ref; 43 }; 44 45 t.afterEach(async () => { 46 instance = null; 47 await cleanupPortal(); 48 }); 49 50 const mountAndWaitFor = (child, propName = 'onLoad') => 51 originalMountAndWaitFor(child, propName, setPortalChild); 52 53 const testPropValues = (propName, values, moreTests) => 54 t.describe(`Video.props.${propName}`, () => { 55 forEach(values, value => 56 t.it(`sets it to \`${value}\``, async () => { 57 let instance = null; 58 const refSetter = ref => { 59 instance = ref; 60 }; 61 const element = React.createElement(Video, { 62 style, 63 source, 64 ref: refSetter, 65 [propName]: value, 66 }); 67 await mountAndWaitFor(element, 'onLoad'); 68 await retryForStatus(instance, { [propName]: value }); 69 }) 70 ); 71 72 if (moreTests) { 73 moreTests(); 74 } 75 }); 76 77 const testNoCrash = (propName, values) => 78 t.describe(`Video.props.${propName}`, () => { 79 forEach(values, value => 80 t.it(`setting to \`${value}\` doesn't crash`, async () => { 81 const element = React.createElement(Video, { style, source, [propName]: value }); 82 await mountAndWaitFor(element, 'onLoad'); 83 }) 84 ); 85 }); 86 87 const testPropSetter = (propName, propSetter, values, moreTests) => 88 t.describe(`Video.${propSetter}`, () => { 89 forEach(values, value => 90 t.it(`sets it to \`${value}\``, async () => { 91 let instance = null; 92 const refSetter = ref => { 93 instance = ref; 94 }; 95 const element = React.createElement(Video, { 96 style, 97 source, 98 ref: refSetter, 99 [propName]: value, 100 }); 101 await mountAndWaitFor(element); 102 await instance[propSetter](value); 103 const status = await instance.getStatusAsync(); 104 t.expect(status).toEqual(t.jasmine.objectContaining({ [propName]: value })); 105 }) 106 ); 107 108 if (moreTests) { 109 moreTests(); 110 } 111 }); 112 113 t.describe('Video.props.onLoadStart', () => { 114 t.it('gets called when the source starts loading', async () => { 115 await mountAndWaitFor(<Video style={style} source={source} />, 'onLoadStart'); 116 }); 117 }); 118 119 t.describe('Video.props.onLoad', () => { 120 t.it('gets called when the source loads', async () => { 121 await mountAndWaitFor(<Video style={style} source={source} />, 'onLoad'); 122 }); 123 124 t.it('gets called right when the video starts to play if it should autoplay', async () => { 125 const status = await mountAndWaitFor( 126 <Video style={style} source={videoRemoteSource} shouldPlay />, 127 'onLoad' 128 ); 129 t.expect(status.positionMillis).toEqual(0); 130 }); 131 }); 132 133 t.describe('Video.props.source', () => { 134 t.it('mounts even when the source is undefined', async () => { 135 await mountAndWaitFor(<Video style={style} />, 'ref'); 136 }); 137 138 t.it('loads `require` source', async () => { 139 const status = await mountAndWaitFor(<Video style={style} source={mp4Source} />); 140 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 141 }); 142 143 t.it('loads `Asset` source', async () => { 144 const status = await mountAndWaitFor( 145 <Video style={style} source={Asset.fromModule(mp4Source)} /> 146 ); 147 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 148 }); 149 150 t.it('loads `uri` source', async () => { 151 const status = await mountAndWaitFor(<Video style={style} source={videoRemoteSource} />); 152 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 153 }); 154 155 if (Platform.OS === 'android') { 156 t.it( 157 'calls onError when the file from the Internet redirects to a non-standard content', 158 async () => { 159 const error = await mountAndWaitFor( 160 <Video 161 style={style} 162 source={{ 163 uri: hlsStreamUriWithRedirect, 164 }} 165 />, 166 'onError' 167 ); 168 t.expect(error.toLowerCase()).toContain('format'); 169 } 170 ); 171 t.it( 172 'loads the file from the Internet that redirects to non-standard content when overrideFileExtensionAndroid is provided', 173 async () => { 174 let hasBeenRejected = false; 175 try { 176 const status = await mountAndWaitFor( 177 <Video 178 style={style} 179 source={{ uri: hlsStreamUriWithRedirect, overrideFileExtensionAndroid: 'm3u8' }} 180 /> 181 ); 182 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 183 } catch (error) { 184 hasBeenRejected = true; 185 } 186 t.expect(hasBeenRejected).toBe(false); 187 } 188 ); 189 } else { 190 t.it( 191 'loads the file from the Internet that redirects to non-standard content', 192 async () => { 193 let hasBeenRejected = false; 194 try { 195 const status = await mountAndWaitFor( 196 <Video style={style} source={{ uri: hlsStreamUriWithRedirect }} /> 197 ); 198 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 199 } catch (error) { 200 hasBeenRejected = true; 201 } 202 t.expect(hasBeenRejected).toBe(false); 203 } 204 ); 205 } 206 207 t.it('loads HLS stream', async () => { 208 const status = await mountAndWaitFor( 209 <Video style={style} source={{ uri: hlsStreamUri }} /> 210 ); 211 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 212 }); 213 214 t.it('loads redirecting `uri` source', async () => { 215 const status = await mountAndWaitFor( 216 <Video style={style} source={redirectingVideoRemoteSource} /> 217 ); 218 t.expect(status).toEqual(t.jasmine.objectContaining({ isLoaded: true })); 219 }); 220 221 // These two are flaky on iOS, sometimes they pass, sometimes they timeout. 222 t.it( 223 'calls onError when given image source', 224 async () => { 225 const error = await mountAndWaitFor( 226 <Video style={style} source={imageSource} shouldPlay />, 227 'onError' 228 ); 229 t.expect(error).toBeDefined(); 230 }, 231 30000 232 ); 233 234 if (Platform.OS === 'ios') { 235 t.it( 236 'calls onError with a reason when unsupported format given (WebM)', 237 async () => { 238 const error = await mountAndWaitFor( 239 <Video style={style} source={webmSource} shouldPlay />, 240 'onError' 241 ); 242 // We cannot check for the specific reason, 243 // as sometimes it isn't what we would expect it to be 244 // (we'd expect "This media format is not supported."), 245 // so let's check whether there is a reason-description separator, 246 // which is included only if `localizedFailureReason` is not nil. 247 t.expect(error).toContain(' - '); 248 }, 249 30000 250 ); 251 } 252 }); 253 254 testNoCrash('useNativeControls', [true, false]); 255 testNoCrash('usePoster', [true, false]); 256 testNoCrash('resizeMode', [ 257 Video.RESIZE_MODE_COVER, 258 Video.RESIZE_MODE_CONTAIN, 259 Video.RESIZE_MODE_STRETCH, 260 ]); 261 262 t.describe(`Video.props.posterSource`, () => { 263 t.it("doesn't crash if is set to required image", async () => { 264 const props = { 265 style, 266 source, 267 posterSource: imageSource, 268 }; 269 await mountAndWaitFor(<Video {...props} />); 270 }); 271 272 t.it("doesn't crash if is set to uri", async () => { 273 const props = { 274 style, 275 source, 276 posterSource: imageRemoteSource, 277 }; 278 await mountAndWaitFor(<Video {...props} />); 279 }); 280 }); 281 282 t.describe(`Video.props.onReadyForDisplay`, () => { 283 t.it('gets called with the `naturalSize` object', async () => { 284 const props = { 285 style, 286 source, 287 }; 288 const status = await mountAndWaitFor(<Video {...props} />, 'onReadyForDisplay'); 289 t.expect(status.naturalSize).toBeDefined(); 290 t.expect(status.naturalSize.width).toBeDefined(); 291 t.expect(status.naturalSize.height).toBeDefined(); 292 t.expect(status.naturalSize.orientation).toBeDefined(); 293 }); 294 295 t.it('gets called with the `status` object', async () => { 296 const props = { 297 style, 298 source, 299 }; 300 const status = await mountAndWaitFor(<Video {...props} />, 'onReadyForDisplay'); 301 t.expect(status.status).toBeDefined(); 302 t.expect(status.status.isLoaded).toBe(true); 303 }); 304 305 t.it('gets called when the component uses native controls', async () => { 306 const props = { 307 style, 308 source, 309 useNativeControls: true, 310 }; 311 const status = await mountAndWaitFor(<Video {...props} />, 'onReadyForDisplay'); 312 t.expect(status.status).toBeDefined(); 313 t.expect(status.status.isLoaded).toBe(true); 314 }); 315 316 t.it("gets called when the component doesn't use native controls", async () => { 317 const props = { 318 style, 319 source, 320 useNativeControls: false, 321 }; 322 const status = await mountAndWaitFor(<Video {...props} />, 'onReadyForDisplay'); 323 t.expect(status.status).toBeDefined(); 324 t.expect(status.status.isLoaded).toBe(true); 325 }); 326 }); 327 328 t.describe('Video fullscreen player', () => { 329 t.it('presents the player and calls callback func', async () => { 330 const onFullscreenUpdate = t.jasmine.createSpy('onFullscreenUpdate'); 331 await mountAndWaitFor( 332 <Video 333 style={style} 334 source={source} 335 ref={refSetter} 336 onFullscreenUpdate={onFullscreenUpdate} 337 />, 338 'onReadyForDisplay' 339 ); 340 const expectUpdate = fullscreenUpdate => 341 t.expect(onFullscreenUpdate).toHaveBeenCalledWith( 342 t.jasmine.objectContaining({ 343 fullscreenUpdate, 344 }) 345 ); 346 await instance.presentFullscreenPlayer(); 347 expectUpdate(Video.FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT); 348 expectUpdate(Video.FULLSCREEN_UPDATE_PLAYER_DID_PRESENT); 349 await instance.dismissFullscreenPlayer(); 350 expectUpdate(Video.FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS); 351 expectUpdate(Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS); 352 }); 353 354 if (Platform.OS === 'android') { 355 t.it("raises an error if the code didn't wait for completion", async () => { 356 let presentationError = null; 357 let dismissalError = null; 358 try { 359 await mountAndWaitFor( 360 <Video style={style} source={source} ref={refSetter} />, 361 'onReadyForDisplay' 362 ); 363 instance.presentFullscreenPlayer().catch(error => { 364 presentationError = error; 365 }); 366 await instance.dismissFullscreenPlayer(); 367 } catch (error) { 368 dismissalError = error; 369 } 370 371 t.expect(presentationError).toBeDefined(); 372 t.expect(dismissalError).toBeDefined(); 373 }); 374 } 375 376 t.it('rejects dismissal request if present request is being handled', async () => { 377 await mountAndWaitFor( 378 <Video style={style} source={source} ref={refSetter} />, 379 'onReadyForDisplay' 380 ); 381 let error = null; 382 const presentationPromise = instance.presentFullscreenPlayer(); 383 try { 384 await instance.dismissFullscreenPlayer(); 385 } catch (err) { 386 error = err; 387 } 388 t.expect(error).toBeDefined(); 389 await presentationPromise; 390 await instance.dismissFullscreenPlayer(); 391 }); 392 393 t.it('rejects presentation request if present request is already being handled', async () => { 394 await mountAndWaitFor( 395 <Video style={style} source={source} ref={refSetter} />, 396 'onReadyForDisplay' 397 ); 398 let error = null; 399 const presentationPromise = instance.presentFullscreenPlayer(); 400 try { 401 await instance.presentFullscreenPlayer(); 402 } catch (err) { 403 error = err; 404 } 405 t.expect(error).toBeDefined(); 406 await presentationPromise; 407 await instance.dismissFullscreenPlayer(); 408 }); 409 410 // NOTE(2018-10-17): Some of these tests are failing on iOS 411 const unreliablyIt = Platform.OS === 'ios' ? t.xit : t.it; 412 413 unreliablyIt( 414 'rejects all but the last request to change fullscreen mode before the video loads', 415 async () => { 416 // Adding second clause sometimes crashes the application, 417 // because by the time we call `present` second time, 418 // the video loads, so it handles the first request properly, 419 // rejects the second and it may reject the third request 420 // if it tries to be handled while the first presentation request 421 // is being handled. 422 let firstErrored = false; 423 // let secondErrored = false; 424 let thirdErrored = false; 425 // We're using remote source as this gives us time to request changes 426 // before the video loads. 427 const instance = await mountAndWaitFor( 428 <Video style={style} source={videoRemoteSource} />, 429 'ref' 430 ); 431 instance.dismissFullscreenPlayer().catch(() => { 432 firstErrored = true; 433 }); 434 // instance.presentFullscreenPlayer().catch(() => (secondErrored = true)); 435 try { 436 await instance.dismissFullscreenPlayer(); 437 } catch (_error) { 438 thirdErrored = true; 439 } 440 441 if (!firstErrored) { 442 // First present request finished too early so we cannot 443 // test this behavior at all. Normally I would put 444 // `t.pending` here, but as for the end of 2017 it doesn't work. 445 } else { 446 t.expect(firstErrored).toBe(true); 447 // t.expect(secondErrored).toBe(true); 448 t.expect(thirdErrored).toBe(false); 449 } 450 const pleaseDismiss = async () => { 451 try { 452 await instance.dismissFullscreenPlayer(); 453 } catch (error) { 454 pleaseDismiss(); 455 } 456 }; 457 await pleaseDismiss(); 458 } 459 ); 460 }); 461 462 // Actually values 2.0 and -0.5 shouldn't be allowed, however at the moment 463 // it is possible to set them through props successfully. 464 testPropValues('volume', [0.5, 1.0, 2.0, -0.5]); 465 testPropSetter('volume', 'setVolumeAsync', [0, 0.5, 1], () => { 466 t.it('errors when trying to set it to 2', async () => { 467 let error = null; 468 try { 469 const props = { style, source, ref: refSetter }; 470 await mountAndWaitFor(<Video {...props} />); 471 await instance.setVolumeAsync(2); 472 } catch (err) { 473 error = err; 474 } 475 t.expect(error).toBeDefined(); 476 t.expect(error.toString()).toMatch(/value .+ between/); 477 }); 478 479 t.it('errors when trying to set it to -0.5', async () => { 480 let error = null; 481 try { 482 const props = { style, source, ref: refSetter }; 483 await mountAndWaitFor(<Video {...props} />); 484 await instance.setVolumeAsync(-0.5); 485 } catch (err) { 486 error = err; 487 } 488 t.expect(error).toBeDefined(); 489 t.expect(error.toString()).toMatch(/value .+ between/); 490 }); 491 }); 492 493 testPropValues('isMuted', [true, false]); 494 testPropSetter('isMuted', 'setIsMutedAsync', [true, false]); 495 496 testPropValues('isLooping', [true, false]); 497 testPropSetter('isLooping', 'setIsLoopingAsync', [true, false]); 498 499 // Actually values 34 and -0.5 shouldn't be allowed, however at the moment 500 // it is possible to set them through props successfully. 501 testPropValues('rate', [0.5, 1.0, 2, 34, -0.5]); 502 testPropSetter('rate', 'setRateAsync', [0, 0.5, 1], () => { 503 t.it('errors when trying to set it above 32', async () => { 504 let error = null; 505 try { 506 const props = { style, source, ref: refSetter }; 507 await mountAndWaitFor(<Video {...props} />); 508 await instance.setRateAsync(34); 509 } catch (err) { 510 error = err; 511 } 512 t.expect(error).toBeDefined(); 513 t.expect(error.toString()).toMatch(/value .+ between/); 514 }); 515 516 t.it('errors when trying to set it under 0', async () => { 517 let error = null; 518 try { 519 const props = { style, source, ref: refSetter }; 520 await mountAndWaitFor(<Video {...props} />); 521 await instance.setRateAsync(-0.5); 522 } catch (err) { 523 error = err; 524 } 525 t.expect(error).toBeDefined(); 526 t.expect(error.toString()).toMatch(/value .+ between/); 527 }); 528 }); 529 530 testPropValues('shouldPlay', [true, false]); 531 testPropValues('shouldCorrectPitch', [true, false]); 532 533 t.describe('Video.onPlaybackStatusUpdate', () => { 534 t.it('gets called with `didJustFinish = true` when video is done playing', async () => { 535 const onPlaybackStatusUpdate = t.jasmine.createSpy('onPlaybackStatusUpdate'); 536 const props = { 537 onPlaybackStatusUpdate, 538 source, 539 style, 540 ref: refSetter, 541 }; 542 await mountAndWaitFor(<Video {...props} />); 543 await retryForStatus(instance, { isBuffering: false, isLoaded: true }); 544 const status = await instance.getStatusAsync(); 545 await instance.setStatusAsync({ 546 shouldPlay: true, 547 positionMillis: status.durationMillis - 500, 548 }); 549 await retryForStatus(instance, { isPlaying: true }); 550 await new Promise(resolve => { 551 setTimeout(() => { 552 t.expect(onPlaybackStatusUpdate).toHaveBeenCalledWith( 553 t.jasmine.objectContaining({ didJustFinish: true }) 554 ); 555 resolve(); 556 }, 1000); 557 }); 558 }); 559 }); 560 561 /*t.describe('Video.setProgressUpdateIntervalAsync', () => { 562 t.it('sets frequence of the progress updates', async () => { 563 const onPlaybackStatusUpdate = t.jasmine.createSpy('onPlaybackStatusUpdate'); 564 const props = { 565 style, 566 source, 567 ref: refSetter, 568 shouldPlay: true, 569 onPlaybackStatusUpdate, 570 }; 571 await mountAndWaitFor(<Video {...props} />); 572 const updateInterval = 100; 573 await instance.setProgressUpdateIntervalAsync(updateInterval); 574 await new Promise(resolve => { 575 setTimeout(() => { 576 const expectedArgsCount = Platform.OS === 'android' ? 5 : 9; 577 t.expect(onPlaybackStatusUpdate.calls.count()).toBeGreaterThan(expectedArgsCount); 578 579 const realMillis = map( 580 takeRight(filter(flatten(onPlaybackStatusUpdate.calls.allArgs()), 'isPlaying'), 4), 581 'positionMillis' 582 ); 583 584 for (let i = 3; i > 0; i--) { 585 const difference = Math.abs(realMillis[i] - realMillis[i - 1] - updateInterval); 586 t.expect(difference).toBeLessThan(updateInterval / 2 + 1); 587 } 588 589 resolve(); 590 }, 1500); 591 }); 592 }); 593 });*/ 594 595 t.describe('Video.setPositionAsync', () => { 596 t.it('sets position of the video', async () => { 597 const props = { style, source, ref: refSetter }; 598 await mountAndWaitFor(<Video {...props} />); 599 await retryForStatus(instance, { isBuffering: false }); 600 const status = await instance.getStatusAsync(); 601 await retryForStatus(instance, { playableDurationMillis: status.durationMillis }); 602 const positionMillis = 500; 603 await instance.setPositionAsync(positionMillis); 604 await retryForStatus(instance, { positionMillis }); 605 }); 606 }); 607 608 t.describe('Video.loadAsync', () => { 609 // NOTE(2018-03-08): Some of these tests are failing on iOS 610 const unreliablyIt = Platform.OS === 'ios' ? t.xit : t.it; 611 unreliablyIt('loads the video', async () => { 612 const props = { style }; 613 const instance = await mountAndWaitFor(<Video {...props} />, 'ref'); 614 await instance.loadAsync(source); 615 await retryForStatus(instance, { isLoaded: true }); 616 }); 617 618 // better positionmillis check 619 unreliablyIt('sets the initial status', async () => { 620 const props = { style }; 621 const instance = await mountAndWaitFor(<Video {...props} />, 'ref'); 622 const initialStatus = { volume: 0.5, isLooping: true, rate: 0.5 }; 623 await instance.loadAsync(source, { ...initialStatus, positionMillis: 1000 }); 624 await retryForStatus(instance, { isLoaded: true, ...initialStatus }); 625 const status = await instance.getStatusAsync(); 626 t.expect(status.positionMillis).toBeLessThan(1100); 627 t.expect(status.positionMillis).toBeGreaterThan(900); 628 }); 629 630 unreliablyIt('keeps the video instance after load when using poster', async () => { 631 const instance = await mountAndWaitFor(<Video style={style} usePoster />, 'ref'); 632 await instance.loadAsync(source, { shouldPlay: true }); 633 await waitFor(500); 634 await retryForStatus(instance, { isPlaying: true }); 635 }); 636 }); 637 638 t.describe('Video.unloadAsync', () => { 639 t.it('unloads the video', async () => { 640 const props = { style, source, ref: refSetter }; 641 await mountAndWaitFor(<Video {...props} />); 642 await retryForStatus(instance, { isLoaded: true }); 643 await instance.unloadAsync(); 644 await retryForStatus(instance, { isLoaded: false }); 645 }); 646 }); 647 648 t.describe('Video.pauseAsync', () => { 649 t.it('pauses the video', async () => { 650 const props = { style, source, shouldPlay: true, ref: refSetter }; 651 await mountAndWaitFor(<Video {...props} />); 652 await retryForStatus(instance, { isPlaying: true }); 653 await new Promise(r => setTimeout(r, 500)); 654 await instance.pauseAsync(); 655 await retryForStatus(instance, { isPlaying: false }); 656 const { positionMillis } = await instance.getStatusAsync(); 657 t.expect(positionMillis).toBeGreaterThan(0); 658 }); 659 }); 660 661 t.describe('Video.playAsync', () => { 662 // NOTE(2018-03-08): Some of these tests are failing on iOS 663 const unreliablyIt = Platform.OS === 'ios' ? t.xit : t.it; 664 665 t.it('plays the stopped video', async () => { 666 const props = { style, source, ref: refSetter }; 667 await mountAndWaitFor(<Video {...props} />); 668 await retryForStatus(instance, { isLoaded: true }); 669 await instance.playAsync(); 670 await retryForStatus(instance, { isPlaying: true }); 671 }); 672 673 t.it('plays the paused video', async () => { 674 const props = { style, source, ref: refSetter }; 675 await mountAndWaitFor(<Video {...props} />); 676 await retryForStatus(instance, { isLoaded: true }); 677 await instance.playAsync(); 678 await retryForStatus(instance, { isPlaying: true }); 679 await instance.pauseAsync(); 680 await retryForStatus(instance, { isPlaying: false }); 681 await instance.playAsync(); 682 await retryForStatus(instance, { isPlaying: true }); 683 }); 684 685 unreliablyIt('does not play video that played to an end', async () => { 686 const onPlaybackStatusUpdate = t.jasmine.createSpy('onPlaybackStatusUpdate'); 687 const props = { 688 onPlaybackStatusUpdate, 689 source, 690 style, 691 ref: refSetter, 692 }; 693 await mountAndWaitFor(<Video {...props} />); 694 await retryForStatus(instance, { isBuffering: false, isLoaded: true }); 695 const status = await instance.getStatusAsync(); 696 await instance.setStatusAsync({ 697 shouldPlay: true, 698 positionMillis: status.durationMillis - 500, 699 }); 700 await new Promise(resolve => { 701 setTimeout(() => { 702 t.expect(onPlaybackStatusUpdate).toHaveBeenCalledWith( 703 t.jasmine.objectContaining({ didJustFinish: true }) 704 ); 705 resolve(); 706 }, 1000); 707 }); 708 await instance.playAsync(); 709 t.expect((await instance.getStatusAsync()).isPlaying).toBe(false); 710 }); 711 }); 712 713 t.describe('Video.playFromPositionAsync', () => { 714 t.it('plays a video that played to an end', async () => { 715 const onPlaybackStatusUpdate = t.jasmine.createSpy('onPlaybackStatusUpdate'); 716 const props = { onPlaybackStatusUpdate, source, style, ref: refSetter }; 717 await mountAndWaitFor(<Video {...props} />); 718 await retryForStatus(instance, { isBuffering: false, isLoaded: true }); 719 const status = await instance.getStatusAsync(); 720 await instance.setStatusAsync({ 721 shouldPlay: true, 722 positionMillis: status.durationMillis - 500, 723 }); 724 await new Promise(resolve => { 725 setTimeout(() => { 726 t.expect(onPlaybackStatusUpdate).toHaveBeenCalledWith( 727 t.jasmine.objectContaining({ didJustFinish: true }) 728 ); 729 resolve(); 730 }, 1000); 731 }); 732 await instance.playFromPositionAsync(0); 733 await retryForStatus(instance, { isPlaying: true }); 734 }); 735 }); 736 737 t.describe('Video.replayAsync', () => { 738 t.it('replays the video', async () => { 739 await mountAndWaitFor(<Video source={source} ref={refSetter} style={style} shouldPlay />); 740 await retryForStatus(instance, { isPlaying: true }); 741 await waitFor(500); 742 const statusBefore = await instance.getStatusAsync(); 743 await instance.replayAsync(); 744 await retryForStatus(instance, { isPlaying: true }); 745 const statusAfter = await instance.getStatusAsync(); 746 t.expect(statusAfter.positionMillis).toBeLessThan(statusBefore.positionMillis); 747 }); 748 749 t.it('plays a video that played to an end', async () => { 750 const onPlaybackStatusUpdate = t.jasmine.createSpy('onPlaybackStatusUpdate'); 751 const props = { onPlaybackStatusUpdate, source, style, ref: refSetter }; 752 await mountAndWaitFor(<Video {...props} />); 753 await retryForStatus(instance, { isBuffering: false, isLoaded: true }); 754 const status = await instance.getStatusAsync(); 755 await instance.setStatusAsync({ 756 shouldPlay: true, 757 positionMillis: status.durationMillis - 500, 758 }); 759 await new Promise(resolve => { 760 setTimeout(() => { 761 t.expect(onPlaybackStatusUpdate).toHaveBeenCalledWith( 762 t.jasmine.objectContaining({ didJustFinish: true }) 763 ); 764 resolve(); 765 }, 1000); 766 }); 767 await instance.replayAsync(); 768 await retryForStatus(instance, { isPlaying: true }); 769 }); 770 771 /*t.it('calls the onPlaybackStatusUpdate with hasJustBeenInterrupted = true', async () => { 772 const onPlaybackStatusUpdate = t.jasmine.createSpy('onPlaybackStatusUpdate'); 773 const props = { 774 style, 775 source, 776 ref: refSetter, 777 shouldPlay: true, 778 onPlaybackStatusUpdate, 779 }; 780 await mountAndWaitFor(<Video {...props} />); 781 await retryForStatus(instance, { isPlaying: true }); 782 await waitFor(500); 783 await instance.replayAsync(); 784 t 785 .expect(onPlaybackStatusUpdate) 786 .toHaveBeenCalledWith(t.jasmine.objectContaining({ hasJustBeenInterrupted: true })); 787 });*/ 788 }); 789 790 t.describe('Video.stopAsync', () => { 791 t.it('stops a playing video', async () => { 792 const props = { style, source, shouldPlay: true, ref: refSetter }; 793 await mountAndWaitFor(<Video {...props} />); 794 await retryForStatus(instance, { isPlaying: true }); 795 await instance.stopAsync(); 796 await retryForStatus(instance, { isPlaying: false, positionMillis: 0 }); 797 }); 798 799 t.it('stops a paused video', async () => { 800 const props = { style, source, shouldPlay: true, ref: refSetter }; 801 await mountAndWaitFor(<Video {...props} />); 802 await retryForStatus(instance, { isPlaying: true }); 803 await waitFor(500); 804 await instance.pauseAsync(); 805 await retryForStatus(instance, { isPlaying: false }); 806 await instance.stopAsync(); 807 await retryForStatus(instance, { isPlaying: false, positionMillis: 0 }); 808 }); 809 }); 810 }); 811} 812