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