@amazon/vinyl 4.0.0 → 4.0.1-next.20261016184530
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +40 -3
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +41 -3
- package/dist/index.js.map +2 -2
- package/dist/test/benchmark.es5.js +5 -4
- package/dist/test/benchmark.js +11 -2
- package/dist/test/benchmark.js.map +3 -3
- package/dist/test/errata/mse-changetype-sequence.html +65 -0
- package/dist/test/errata/mse-changetype-sequence.js +179 -0
- package/dist/test/index.html +2 -2
- package/dist/test/integ.es5.js +287 -276
- package/dist/test/integ.js +629 -245
- package/dist/test/integ.js.map +4 -4
- package/dist/test/styles.css +23 -13
- package/dist/types/current/playback/PlaybackControllerImpl.d.ts +12 -0
- package/dist/types/current/playback/PlaybackControllerImpl.d.ts.map +1 -1
- package/dist/types/current/playback/PlaybackControllerImpl.js +42 -1
- package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
- package/dist/types/current/streaming/buffering/BufferingController.js +9 -2
- package/dist/types/current/streaming/buffering/MediaSourceController.d.ts.map +1 -1
- package/dist/types/current/streaming/buffering/MediaSourceController.js +4 -2
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +12 -0
- package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +12 -0
- package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +12 -0
- package/dist/unit/types/current/playback/PlaybackControllerImpl.test.js +86 -0
- package/dist/unit/types/current/setup.js +6 -5
- package/dist/unit/types/current/streaming/SegmentControllerImpl.test.js +44 -1
- package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +14 -0
- package/dist/unit/types/current/streaming/buffering/MediaSourceController.test.js +13 -0
- package/dist/unit/types/current/track/dash/createDashFactories.test.js +15 -5
- package/dist/unit/types/current/track/hls/createHlsFactories.test.js +36 -12
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/unit/types/current/vinyl/VinylPlayerAdBreaks.test.js +8 -5
- package/dist/vinylTestUtil/index.js +92 -60
- package/dist/vinylTestUtil/index.js.map +4 -4
- package/dist/vinylTestUtil/types/current/media/FrequencyAnalyzer.d.ts +8 -5
- package/dist/vinylTestUtil/types/current/media/FrequencyAnalyzer.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/media/FrequencyAnalyzer.js +23 -29
- package/dist/vinylTestUtil/types/current/media/unlockMediaElement.d.ts +10 -1
- package/dist/vinylTestUtil/types/current/media/unlockMediaElement.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/media/unlockMediaElement.js +34 -6
- package/dist/vinylTestUtil/types/current/player/createVinylSuite.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/player/createVinylSuite.js +6 -1
- package/dist/vinylTestUtil/types/current/player/mediaRef.d.ts +7 -0
- package/dist/vinylTestUtil/types/current/player/mediaRef.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/player/mediaRef.js +8 -1
- package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/vinylTestUtil/types/current/ui/requireUserInteraction.d.ts +6 -2
- package/dist/vinylTestUtil/types/current/ui/requireUserInteraction.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/ui/requireUserInteraction.js +31 -15
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackPlays.d.ts +9 -2
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackPlays.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackPlays.js +10 -7
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackPlaysUntil.d.ts +4 -1
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackPlaysUntil.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackPlaysUntil.js +3 -2
- package/dist/vinylTestUtil/types/ts3.7/media/FrequencyAnalyzer.d.ts +8 -5
- package/dist/vinylTestUtil/types/ts3.7/media/unlockMediaElement.d.ts +10 -1
- package/dist/vinylTestUtil/types/ts3.7/player/mediaRef.d.ts +7 -0
- package/dist/vinylTestUtil/types/ts3.7/ui/requireUserInteraction.d.ts +6 -2
- package/dist/vinylTestUtil/types/ts3.7/util/playback/expectTrackPlays.d.ts +9 -2
- package/dist/vinylTestUtil/types/ts3.7/util/playback/expectTrackPlaysUntil.d.ts +4 -1
- package/dist/vinylTestUtil/types/ts4.1/media/FrequencyAnalyzer.d.ts +8 -5
- package/dist/vinylTestUtil/types/ts4.1/media/unlockMediaElement.d.ts +10 -1
- package/dist/vinylTestUtil/types/ts4.1/player/mediaRef.d.ts +7 -0
- package/dist/vinylTestUtil/types/ts4.1/ui/requireUserInteraction.d.ts +6 -2
- package/dist/vinylTestUtil/types/ts4.1/util/playback/expectTrackPlays.d.ts +9 -2
- package/dist/vinylTestUtil/types/ts4.1/util/playback/expectTrackPlaysUntil.d.ts +4 -1
- package/dist/vinylTestUtil/types/ts4.7/media/FrequencyAnalyzer.d.ts +8 -5
- package/dist/vinylTestUtil/types/ts4.7/media/unlockMediaElement.d.ts +10 -1
- package/dist/vinylTestUtil/types/ts4.7/player/mediaRef.d.ts +7 -0
- package/dist/vinylTestUtil/types/ts4.7/ui/requireUserInteraction.d.ts +6 -2
- package/dist/vinylTestUtil/types/ts4.7/util/playback/expectTrackPlays.d.ts +9 -2
- package/dist/vinylTestUtil/types/ts4.7/util/playback/expectTrackPlaysUntil.d.ts +4 -1
- package/package.json +11 -10
|
@@ -180,6 +180,18 @@ export declare class PlaybackControllerImpl extends EventHostImpl<PlaybackContro
|
|
|
180
180
|
* @private
|
|
181
181
|
*/
|
|
182
182
|
private doSeek;
|
|
183
|
+
/**
|
|
184
|
+
* Dispatches `waiting` when a seek while playing targets an unbuffered position and the media element did not.
|
|
185
|
+
* Safari 27 keeps its readyState while seeking, so it never fires `waiting` for the seek.
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
private initializeSeekWaitingEvent;
|
|
189
|
+
/**
|
|
190
|
+
* Dispatches `playing` when a seek while playing completes and the media element did not. A seek ends `playing`,
|
|
191
|
+
* but WebKit only fires `playing` again if its readyState dropped, which it doesn't for a buffered position.
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
private initializeSeekPlayingEvent;
|
|
183
195
|
/**
|
|
184
196
|
* Dispatches a readyStateChange event when {@link readyState} changes.
|
|
185
197
|
* @private
|
|
@@ -182,6 +182,18 @@ export declare class PlaybackControllerImpl extends EventHostImpl<PlaybackContro
|
|
|
182
182
|
* @private
|
|
183
183
|
*/
|
|
184
184
|
private doSeek;
|
|
185
|
+
/**
|
|
186
|
+
* Dispatches `waiting` when a seek while playing targets an unbuffered position and the media element did not.
|
|
187
|
+
* Safari 27 keeps its readyState while seeking, so it never fires `waiting` for the seek.
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
private initializeSeekWaitingEvent;
|
|
191
|
+
/**
|
|
192
|
+
* Dispatches `playing` when a seek while playing completes and the media element did not. A seek ends `playing`,
|
|
193
|
+
* but WebKit only fires `playing` again if its readyState dropped, which it doesn't for a buffered position.
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
private initializeSeekPlayingEvent;
|
|
185
197
|
/**
|
|
186
198
|
* Dispatches a readyStateChange event when {@link readyState} changes.
|
|
187
199
|
* @private
|
|
@@ -180,6 +180,18 @@ export declare class PlaybackControllerImpl extends EventHostImpl<PlaybackContro
|
|
|
180
180
|
* @private
|
|
181
181
|
*/
|
|
182
182
|
private doSeek;
|
|
183
|
+
/**
|
|
184
|
+
* Dispatches `waiting` when a seek while playing targets an unbuffered position and the media element did not.
|
|
185
|
+
* Safari 27 keeps its readyState while seeking, so it never fires `waiting` for the seek.
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
private initializeSeekWaitingEvent;
|
|
189
|
+
/**
|
|
190
|
+
* Dispatches `playing` when a seek while playing completes and the media element did not. A seek ends `playing`,
|
|
191
|
+
* but WebKit only fires `playing` again if its readyState dropped, which it doesn't for a buffered position.
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
private initializeSeekPlayingEvent;
|
|
183
195
|
/**
|
|
184
196
|
* Dispatches a readyStateChange event when {@link readyState} changes.
|
|
185
197
|
* @private
|
|
@@ -1024,6 +1024,92 @@ describe('PlaybackController', () => {
|
|
|
1024
1024
|
expect(waitedSpy).not.toHaveBeenCalled();
|
|
1025
1025
|
});
|
|
1026
1026
|
});
|
|
1027
|
+
describe('when a seek while playing targets an unbuffered position', () => {
|
|
1028
|
+
let waitingSpy;
|
|
1029
|
+
beforeEach(() => {
|
|
1030
|
+
waitingSpy = createSpy('waiting');
|
|
1031
|
+
controller.on('waiting', waitingSpy);
|
|
1032
|
+
media.buffered = new MockTimeRanges([[0, 20]]);
|
|
1033
|
+
media.currentTime = 22;
|
|
1034
|
+
media.seeking = true;
|
|
1035
|
+
});
|
|
1036
|
+
it('emits waiting if the media element does not', async () => {
|
|
1037
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
1038
|
+
expect(waitingSpy).not.toHaveBeenCalled();
|
|
1039
|
+
await clock.tick();
|
|
1040
|
+
expect(waitingSpy).toHaveBeenCalledTimes(1);
|
|
1041
|
+
expect(controller.waiting).toBeTrue();
|
|
1042
|
+
media.dispatchEvent(mockEvent('waiting'));
|
|
1043
|
+
expect(waitingSpy).toHaveBeenCalledTimes(1);
|
|
1044
|
+
media.seeking = false;
|
|
1045
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
1046
|
+
expect(controller.waiting).toBeFalse();
|
|
1047
|
+
});
|
|
1048
|
+
it('emits waiting once if the media element does', async () => {
|
|
1049
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
1050
|
+
media.dispatchEvent(mockEvent('waiting'));
|
|
1051
|
+
await clock.tick();
|
|
1052
|
+
expect(waitingSpy).toHaveBeenCalledTimes(1);
|
|
1053
|
+
});
|
|
1054
|
+
it('does not emit waiting when paused', async () => {
|
|
1055
|
+
media.paused = true;
|
|
1056
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
1057
|
+
await clock.tick();
|
|
1058
|
+
expect(waitingSpy).not.toHaveBeenCalled();
|
|
1059
|
+
});
|
|
1060
|
+
it('does not emit waiting when the seek completes first', async () => {
|
|
1061
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
1062
|
+
media.seeking = false;
|
|
1063
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
1064
|
+
await clock.tick();
|
|
1065
|
+
expect(waitingSpy).not.toHaveBeenCalled();
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
describe('when a seek while playing completes', () => {
|
|
1069
|
+
let playingSpy;
|
|
1070
|
+
beforeEach(() => {
|
|
1071
|
+
media.readyState = PlaybackReadyState.HAVE_ENOUGH_DATA;
|
|
1072
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
1073
|
+
playingSpy = createSpy('playing');
|
|
1074
|
+
controller.on('playing', playingSpy);
|
|
1075
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
1076
|
+
expect(controller.playing).toBeFalse();
|
|
1077
|
+
});
|
|
1078
|
+
it('emits playing if the media element does not', () => {
|
|
1079
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
1080
|
+
expect(playingSpy).toHaveBeenCalledTimes(1);
|
|
1081
|
+
expect(controller.playing).toBeTrue();
|
|
1082
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
1083
|
+
expect(playingSpy).toHaveBeenCalledTimes(1);
|
|
1084
|
+
});
|
|
1085
|
+
it('emits playing once if the media element does', () => {
|
|
1086
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
1087
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
1088
|
+
expect(playingSpy).toHaveBeenCalledTimes(1);
|
|
1089
|
+
});
|
|
1090
|
+
it('does not emit playing when paused', () => {
|
|
1091
|
+
media.paused = true;
|
|
1092
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
1093
|
+
expect(playingSpy).not.toHaveBeenCalled();
|
|
1094
|
+
});
|
|
1095
|
+
it('does not emit playing without future data', () => {
|
|
1096
|
+
media.readyState = PlaybackReadyState.HAVE_CURRENT_DATA;
|
|
1097
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
1098
|
+
expect(playingSpy).not.toHaveBeenCalled();
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
describe('when a seek while playing targets a buffered position', () => {
|
|
1102
|
+
it('does not emit waiting', async () => {
|
|
1103
|
+
const waitingSpy = createSpy('waiting');
|
|
1104
|
+
controller.on('waiting', waitingSpy);
|
|
1105
|
+
media.buffered = new MockTimeRanges([[0, 30]]);
|
|
1106
|
+
media.currentTime = 22;
|
|
1107
|
+
media.seeking = true;
|
|
1108
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
1109
|
+
await clock.tick();
|
|
1110
|
+
expect(waitingSpy).not.toHaveBeenCalled();
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1027
1113
|
});
|
|
1028
1114
|
describe('pause', () => {
|
|
1029
1115
|
it('invokes pause on the element', () => {
|
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { getGlobalRegistry, setGlobalRefDebug } from '@amazon/vinyl-util';
|
|
5
|
+
import { getGlobalRegistry, getSearchParams, setGlobalRefDebug, } from '@amazon/vinyl-util';
|
|
6
6
|
import { addCustomMatchers, ConsoleReporter, flushPromises, } from '@amazon/vinyl-util/browserTestUtil';
|
|
7
7
|
import { RestApiReporter } from '@amazon/vinyl-util/testUtil';
|
|
8
|
-
import { initializeFrequencyAnalyzer } from '@amazon/vinyl/vinylTestUtil';
|
|
8
|
+
import { initializeFrequencyAnalyzer, initializeMediaUnlock, } from '@amazon/vinyl/vinylTestUtil';
|
|
9
9
|
addCustomMatchers();
|
|
10
10
|
// Diagnostic: GlobalRefImpl records the stack
|
|
11
11
|
setGlobalRefDebug(true);
|
|
12
12
|
// Configured here instead of the jasmine.config.json to work with both ts-node and the html
|
|
13
|
-
// test runner.
|
|
13
|
+
// test runner. `?stopOnSpecFailure=false` runs every spec, as test:integ does.
|
|
14
14
|
jasmine.getEnv().configure({
|
|
15
|
-
stopOnSpecFailure:
|
|
15
|
+
stopOnSpecFailure: getSearchParams().get('stopOnSpecFailure') !== 'false',
|
|
16
16
|
stopSpecOnExpectationFailure: true,
|
|
17
17
|
failSpecWithNoExpectations: true,
|
|
18
18
|
});
|
|
19
19
|
// Register the reporters
|
|
20
20
|
jasmine.getEnv().addReporter(new ConsoleReporter());
|
|
21
21
|
RestApiReporter.installFromSearchParam();
|
|
22
|
-
//
|
|
22
|
+
// In a browser, prompts for a click to unlock media playback, then attaches the analyzer if checkAudio is set.
|
|
23
|
+
initializeMediaUnlock();
|
|
23
24
|
initializeFrequencyAnalyzer();
|
|
24
25
|
afterEach(async () => {
|
|
25
26
|
await flushPromises();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { createEmptyMediaQualityMetadata, defaultSegmentControllerBaseOptions, PREFETCH_POLL_THROTTLE, prefetchPriorityQueuesRef, SegmentControllerImpl, } from '@amazon/vinyl';
|
|
5
|
+
import { clearCodecDenylist, createEmptyMediaQualityMetadata, denylistCodecsFromMimeType, defaultSegmentControllerBaseOptions, PREFETCH_POLL_THROTTLE, prefetchPriorityQueuesRef, SegmentControllerImpl, } from '@amazon/vinyl';
|
|
6
6
|
import { Abort, AbortError, Deferred, floorToNearest, mergeDeep, networkState, noop, sleep, } from '@amazon/vinyl-util';
|
|
7
7
|
import { MockPlaybackController } from '@amazon/vinyl/vinylTestUtil';
|
|
8
8
|
import { data } from '@amazon/vinyl-observable';
|
|
@@ -362,6 +362,49 @@ describe('SegmentControllerImpl', () => {
|
|
|
362
362
|
// segmentController is for 'audio', but only 'video' qualities exist
|
|
363
363
|
expect(await segmentController.getSegment(5)).toBeNull();
|
|
364
364
|
});
|
|
365
|
+
describe('when a codec has been denylisted', () => {
|
|
366
|
+
const OPUS = 'audio/mp4; codecs="opus"';
|
|
367
|
+
const AAC = 'audio/mp4; codecs="mp4a.40.2"';
|
|
368
|
+
let selectQuality;
|
|
369
|
+
beforeEach(() => {
|
|
370
|
+
timelineData.value = Promise.resolve({
|
|
371
|
+
periods: [
|
|
372
|
+
{
|
|
373
|
+
startTime: 0,
|
|
374
|
+
endTime: 100,
|
|
375
|
+
qualities: [OPUS, AAC].map((mimeType, i) => ({
|
|
376
|
+
metadata: {
|
|
377
|
+
...createEmptyMediaQualityMetadata(),
|
|
378
|
+
contentType: 'audio',
|
|
379
|
+
mimeType,
|
|
380
|
+
decoderId: String(i),
|
|
381
|
+
},
|
|
382
|
+
getSegment: (time) => segmentProvider.getSegment(time),
|
|
383
|
+
})),
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
minBufferTime: 10,
|
|
387
|
+
getAdBreaks: () => Promise.resolve([]),
|
|
388
|
+
getDuration: () => Promise.resolve(100),
|
|
389
|
+
});
|
|
390
|
+
selectQuality = spyOn(mockDeps.qualitySelector, 'selectQuality').and.returnValue(0);
|
|
391
|
+
});
|
|
392
|
+
afterEach(() => clearCodecDenylist());
|
|
393
|
+
function selectedFrom() {
|
|
394
|
+
return selectQuality.calls.mostRecent().args[0].map((metadata) => metadata.mimeType);
|
|
395
|
+
}
|
|
396
|
+
it('selects only from qualities whose codecs are not denylisted', async () => {
|
|
397
|
+
denylistCodecsFromMimeType(OPUS);
|
|
398
|
+
await segmentController.getSegment(5);
|
|
399
|
+
expect(selectedFrom()).toEqual([AAC]);
|
|
400
|
+
});
|
|
401
|
+
it('selects from every quality if all of their codecs are denylisted', async () => {
|
|
402
|
+
denylistCodecsFromMimeType(OPUS);
|
|
403
|
+
denylistCodecsFromMimeType(AAC);
|
|
404
|
+
await segmentController.getSegment(5);
|
|
405
|
+
expect(selectedFrom()).toEqual([OPUS, AAC]);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
365
408
|
it('returns null when quality getSegment returns null within period', async () => {
|
|
366
409
|
await fillPrefetch();
|
|
367
410
|
segmentController.clear();
|
|
@@ -447,6 +447,20 @@ describe('BufferingControllerImpl', () => {
|
|
|
447
447
|
await nextPollImmediate();
|
|
448
448
|
expect(sourceBufferController.appendInit).toHaveBeenCalledOnceWith(any(ArrayBuffer), 'opus');
|
|
449
449
|
});
|
|
450
|
+
it('continues after the buffered segment once the new initialization segment is appended', async () => {
|
|
451
|
+
await open();
|
|
452
|
+
sourceBufferController.setTimestampOffset.calls.reset();
|
|
453
|
+
segmentController.getSegment.and.callFake((time) => {
|
|
454
|
+
const mockSegment = createMockSegment(time);
|
|
455
|
+
if (mockSegment.startTime >= 10) {
|
|
456
|
+
mockSegment.quality.decoderId = 'next period';
|
|
457
|
+
mockSegment.timestampOffset = mockSegment.startTime - 4;
|
|
458
|
+
}
|
|
459
|
+
return Promise.resolve(mockSegment);
|
|
460
|
+
});
|
|
461
|
+
await setTime(9);
|
|
462
|
+
expect(sourceBufferController.setTimestampOffset).toHaveBeenCalledOnceWith(10);
|
|
463
|
+
});
|
|
450
464
|
});
|
|
451
465
|
describe('when segment controller returns null', () => {
|
|
452
466
|
beforeEach(() => {
|
|
@@ -256,6 +256,11 @@ describe('MediaSourceControllerImpl', () => {
|
|
|
256
256
|
expect(() => controller.createSourceBuffer('audio', 'invalid/type')).toThrowError(MediaSourceError, 'error creating source buffer');
|
|
257
257
|
});
|
|
258
258
|
describe('SourceBufferRef disposal', () => {
|
|
259
|
+
beforeEach(() => {
|
|
260
|
+
mockMediaSource.sourceBuffers = [
|
|
261
|
+
mockSourceBuffer,
|
|
262
|
+
];
|
|
263
|
+
});
|
|
259
264
|
it('removes source buffer from media source', () => {
|
|
260
265
|
mockMediaSource.readyState = 'open';
|
|
261
266
|
const ref = controller.createSourceBuffer('audio', 'audio/mp4');
|
|
@@ -269,6 +274,14 @@ describe('MediaSourceControllerImpl', () => {
|
|
|
269
274
|
ref.dispose();
|
|
270
275
|
expect(mockMediaSource.removeSourceBuffer).not.toHaveBeenCalled();
|
|
271
276
|
});
|
|
277
|
+
it('does not remove a source buffer the media source already removed', () => {
|
|
278
|
+
mockMediaSource.readyState = 'open';
|
|
279
|
+
const ref = controller.createSourceBuffer('audio', 'audio/mp4');
|
|
280
|
+
mockMediaSource.sourceBuffers =
|
|
281
|
+
[];
|
|
282
|
+
ref.dispose();
|
|
283
|
+
expect(mockMediaSource.removeSourceBuffer).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
272
285
|
});
|
|
273
286
|
});
|
|
274
287
|
describe('endOfStream', () => {
|
|
@@ -46,7 +46,8 @@ describe('createDashFactories', () => {
|
|
|
46
46
|
type: 'dash',
|
|
47
47
|
manifestProvider,
|
|
48
48
|
});
|
|
49
|
-
const
|
|
49
|
+
const container = createContainer(factories);
|
|
50
|
+
const deps = container.dependencies;
|
|
50
51
|
expect(deps).toEqual(objectContaining({
|
|
51
52
|
baseUrlSelector: pickFirstBaseUrlSelector,
|
|
52
53
|
segmentRequestInit: undefined,
|
|
@@ -59,6 +60,7 @@ describe('createDashFactories', () => {
|
|
|
59
60
|
contentStreamFactory: any(Function),
|
|
60
61
|
qualitySelector: any(QualitySelectorImpl),
|
|
61
62
|
}));
|
|
63
|
+
container.dispose();
|
|
62
64
|
});
|
|
63
65
|
it('wires per-track option observables from the player options', () => {
|
|
64
66
|
const factoryCreator = createDashFactories(null);
|
|
@@ -67,11 +69,13 @@ describe('createDashFactories', () => {
|
|
|
67
69
|
type: 'dash',
|
|
68
70
|
manifestProvider,
|
|
69
71
|
});
|
|
70
|
-
const
|
|
72
|
+
const container = createContainer(factories);
|
|
73
|
+
const deps = container.dependencies;
|
|
71
74
|
const options = dashFactoryDeps.options.value;
|
|
72
75
|
expect(deps.audio.value).toBe(options.audio);
|
|
73
76
|
expect(deps.allowedContentTypes.value).toBe(options.allowedContentTypes);
|
|
74
77
|
expect(deps.abr.value).toBe(options.abr);
|
|
78
|
+
container.dispose();
|
|
75
79
|
});
|
|
76
80
|
it('provides streaming options to createContentStreamFactories', () => {
|
|
77
81
|
const factoryCreator = createDashFactories({
|
|
@@ -86,8 +90,10 @@ describe('createDashFactories', () => {
|
|
|
86
90
|
type: 'dash',
|
|
87
91
|
manifestProvider,
|
|
88
92
|
});
|
|
89
|
-
const
|
|
93
|
+
const container = createContainer(factories);
|
|
94
|
+
const deps = container.dependencies;
|
|
90
95
|
expect(deps.contentStreamFactory('audio')).toEqual(any(Object));
|
|
96
|
+
container.dispose();
|
|
91
97
|
});
|
|
92
98
|
it('creates a sidecar text track controller and populates from manifest', async () => {
|
|
93
99
|
const factoryCreator = createDashFactories(null);
|
|
@@ -114,12 +120,14 @@ describe('createDashFactories', () => {
|
|
|
114
120
|
type: 'dash',
|
|
115
121
|
manifestProvider: provider,
|
|
116
122
|
});
|
|
117
|
-
const
|
|
123
|
+
const container = createContainer(factories);
|
|
124
|
+
const deps = container.dependencies;
|
|
118
125
|
const ttc = deps.textTrackController;
|
|
119
126
|
await deps.manifestTransformed.value;
|
|
120
127
|
await new Promise((r) => setTimeout(r, 0));
|
|
121
128
|
expect(ttc).toBeDefined();
|
|
122
129
|
expect(ttc.textTracks.length).toBeGreaterThan(0);
|
|
130
|
+
container.dispose();
|
|
123
131
|
});
|
|
124
132
|
it('text track controller swallows manifest fetch errors', async () => {
|
|
125
133
|
const factoryCreator = createDashFactories(null);
|
|
@@ -129,10 +137,12 @@ describe('createDashFactories', () => {
|
|
|
129
137
|
type: 'dash',
|
|
130
138
|
manifestProvider: provider,
|
|
131
139
|
});
|
|
132
|
-
const
|
|
140
|
+
const container = createContainer(factories);
|
|
141
|
+
const deps = container.dependencies;
|
|
133
142
|
const controller = deps.textTrackController;
|
|
134
143
|
await deps.manifestTransformed.value.catch(() => undefined);
|
|
135
144
|
await new Promise((r) => setTimeout(r, 0));
|
|
136
145
|
expect(controller.textTracks).toEqual([]);
|
|
146
|
+
container.dispose();
|
|
137
147
|
});
|
|
138
148
|
});
|
|
@@ -36,7 +36,8 @@ describe('createHlsFactories', () => {
|
|
|
36
36
|
type: 'hls',
|
|
37
37
|
manifestProvider,
|
|
38
38
|
});
|
|
39
|
-
const
|
|
39
|
+
const container = createContainer(factories);
|
|
40
|
+
const deps = container.dependencies;
|
|
40
41
|
expect(deps).toEqual(objectContaining({
|
|
41
42
|
segmentRequestInit: undefined,
|
|
42
43
|
manifestController: any(ManifestControllerImpl),
|
|
@@ -47,6 +48,7 @@ describe('createHlsFactories', () => {
|
|
|
47
48
|
contentStreamFactory: any(Function),
|
|
48
49
|
qualitySelector: any(QualitySelectorImpl),
|
|
49
50
|
}));
|
|
51
|
+
container.dispose();
|
|
50
52
|
});
|
|
51
53
|
it('wires per-track option observables from the player options', () => {
|
|
52
54
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -55,11 +57,13 @@ describe('createHlsFactories', () => {
|
|
|
55
57
|
type: 'hls',
|
|
56
58
|
manifestProvider,
|
|
57
59
|
});
|
|
58
|
-
const
|
|
60
|
+
const container = createContainer(factories);
|
|
61
|
+
const deps = container.dependencies;
|
|
59
62
|
const options = hlsFactoryDeps.options.value;
|
|
60
63
|
expect(deps.audio.value).toBe(options.audio);
|
|
61
64
|
expect(deps.allowedContentTypes.value).toBe(options.allowedContentTypes);
|
|
62
65
|
expect(deps.abr.value).toBe(options.abr);
|
|
66
|
+
container.dispose();
|
|
63
67
|
});
|
|
64
68
|
it('exposes the shared adController without owning it', () => {
|
|
65
69
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -93,8 +97,10 @@ describe('createHlsFactories', () => {
|
|
|
93
97
|
type: 'hls',
|
|
94
98
|
manifestProvider,
|
|
95
99
|
});
|
|
96
|
-
const
|
|
100
|
+
const container = createContainer(factories);
|
|
101
|
+
const deps = container.dependencies;
|
|
97
102
|
expect(deps.contentStreamFactory('audio')).toEqual(any(Object));
|
|
103
|
+
container.dispose();
|
|
98
104
|
});
|
|
99
105
|
it('uses createUrlHlsManifestProvider when no manifestProvider given', () => {
|
|
100
106
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -102,8 +108,10 @@ describe('createHlsFactories', () => {
|
|
|
102
108
|
uri: 'https://example.com/main.m3u8',
|
|
103
109
|
type: 'hls',
|
|
104
110
|
});
|
|
105
|
-
const
|
|
111
|
+
const container = createContainer(factories);
|
|
112
|
+
const deps = container.dependencies;
|
|
106
113
|
expect(deps.manifestController).toEqual(any(ManifestControllerImpl));
|
|
114
|
+
container.dispose();
|
|
107
115
|
});
|
|
108
116
|
it('creates a sidecar text track controller and populates from manifest', async () => {
|
|
109
117
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -128,7 +136,8 @@ describe('createHlsFactories', () => {
|
|
|
128
136
|
type: 'hls',
|
|
129
137
|
manifestProvider: provider,
|
|
130
138
|
});
|
|
131
|
-
const
|
|
139
|
+
const container = createContainer(factories);
|
|
140
|
+
const deps = container.dependencies;
|
|
132
141
|
// Resolve textTrackController first so it subscribes to
|
|
133
142
|
// manifestTransformed before the manifest promise resolves.
|
|
134
143
|
const controller = deps.textTrackController;
|
|
@@ -137,6 +146,7 @@ describe('createHlsFactories', () => {
|
|
|
137
146
|
expect(controller).toBeDefined();
|
|
138
147
|
expect(controller.textTracks.length).toBe(1);
|
|
139
148
|
expect(controller.textTracks[0].language).toBe('en');
|
|
149
|
+
container.dispose();
|
|
140
150
|
});
|
|
141
151
|
it('text track controller swallows manifest fetch errors', async () => {
|
|
142
152
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -146,13 +156,15 @@ describe('createHlsFactories', () => {
|
|
|
146
156
|
type: 'hls',
|
|
147
157
|
manifestProvider: provider,
|
|
148
158
|
});
|
|
149
|
-
const
|
|
159
|
+
const container = createContainer(factories);
|
|
160
|
+
const deps = container.dependencies;
|
|
150
161
|
const controller = deps.textTrackController;
|
|
151
162
|
// Trigger the transform; the promise will reject but the
|
|
152
163
|
// textTrackController must not throw.
|
|
153
164
|
await deps.manifestTransformed.value.catch(() => undefined);
|
|
154
165
|
await new Promise((r) => setTimeout(r, 0));
|
|
155
166
|
expect(controller.textTracks).toEqual([]);
|
|
167
|
+
container.dispose();
|
|
156
168
|
});
|
|
157
169
|
describe('ad interstitial discovery', () => {
|
|
158
170
|
function interstitialManifest(overrides = {}) {
|
|
@@ -184,11 +196,13 @@ describe('createHlsFactories', () => {
|
|
|
184
196
|
type: 'hls',
|
|
185
197
|
manifestProvider: provider,
|
|
186
198
|
});
|
|
187
|
-
const
|
|
199
|
+
const container = createContainer(factories);
|
|
200
|
+
const deps = container.dependencies;
|
|
188
201
|
const timeline = await deps.mediaTimeline.value;
|
|
189
202
|
const adBreaks = await timeline.getAdBreaks();
|
|
190
203
|
expect(adBreaks.length).toBe(1);
|
|
191
204
|
expect(adBreaks[0].id).toBe('ad1');
|
|
205
|
+
container.dispose();
|
|
192
206
|
});
|
|
193
207
|
it('merges stitched SCTE-35 breaks with interstitials by start time', async () => {
|
|
194
208
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -230,13 +244,15 @@ describe('createHlsFactories', () => {
|
|
|
230
244
|
type: 'hls',
|
|
231
245
|
manifestProvider: provider,
|
|
232
246
|
});
|
|
233
|
-
const
|
|
247
|
+
const container = createContainer(factories);
|
|
248
|
+
const deps = container.dependencies;
|
|
234
249
|
const timeline = await deps.mediaTimeline.value;
|
|
235
250
|
const adBreaks = await timeline.getAdBreaks();
|
|
236
251
|
expect(adBreaks.map((b) => [b.id, b.startTime, b.insertion])).toEqual([
|
|
237
252
|
['splice-1', 2, 'stitched'],
|
|
238
253
|
['ad1', 11, 'interstitial'],
|
|
239
254
|
]);
|
|
255
|
+
container.dispose();
|
|
240
256
|
});
|
|
241
257
|
it('omits ad breaks when the playlist has none', async () => {
|
|
242
258
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -245,9 +261,11 @@ describe('createHlsFactories', () => {
|
|
|
245
261
|
type: 'hls',
|
|
246
262
|
manifestProvider,
|
|
247
263
|
});
|
|
248
|
-
const
|
|
264
|
+
const container = createContainer(factories);
|
|
265
|
+
const deps = container.dependencies;
|
|
249
266
|
const timeline = await deps.mediaTimeline.value;
|
|
250
267
|
expect(await timeline.getAdBreaks()).toEqual([]);
|
|
268
|
+
container.dispose();
|
|
251
269
|
});
|
|
252
270
|
it('discovers ads against a live (non-ended) playlist', async () => {
|
|
253
271
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -257,9 +275,11 @@ describe('createHlsFactories', () => {
|
|
|
257
275
|
type: 'hls',
|
|
258
276
|
manifestProvider: provider,
|
|
259
277
|
});
|
|
260
|
-
const
|
|
278
|
+
const container = createContainer(factories);
|
|
279
|
+
const deps = container.dependencies;
|
|
261
280
|
const timeline = await deps.mediaTimeline.value;
|
|
262
281
|
expect((await timeline.getAdBreaks()).length).toBe(1);
|
|
282
|
+
container.dispose();
|
|
263
283
|
});
|
|
264
284
|
it('returns no ad breaks when there are no variants', async () => {
|
|
265
285
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -275,9 +295,11 @@ describe('createHlsFactories', () => {
|
|
|
275
295
|
type: 'hls',
|
|
276
296
|
manifestProvider: provider,
|
|
277
297
|
});
|
|
278
|
-
const
|
|
298
|
+
const container = createContainer(factories);
|
|
299
|
+
const deps = container.dependencies;
|
|
279
300
|
const timeline = await deps.mediaTimeline.value;
|
|
280
301
|
expect(await timeline.getAdBreaks()).toEqual([]);
|
|
302
|
+
container.dispose();
|
|
281
303
|
});
|
|
282
304
|
it('surfaces media playlist fetch errors from getAdBreaks', async () => {
|
|
283
305
|
const factoryCreator = createHlsFactories(null);
|
|
@@ -290,12 +312,14 @@ describe('createHlsFactories', () => {
|
|
|
290
312
|
type: 'hls',
|
|
291
313
|
manifestProvider: provider,
|
|
292
314
|
});
|
|
293
|
-
const
|
|
315
|
+
const container = createContainer(factories);
|
|
316
|
+
const deps = container.dependencies;
|
|
294
317
|
const timeline = await deps.mediaTimeline.value;
|
|
295
318
|
// Discovery is now lazy: getAdBreaks() rejects on a failed media
|
|
296
319
|
// playlist fetch, leaving error handling to the caller (e.g.
|
|
297
320
|
// MseTrack swallows it via its errorHandler).
|
|
298
321
|
await expectAsync(timeline.getAdBreaks()).toBeRejectedWithError('media down');
|
|
322
|
+
container.dispose();
|
|
299
323
|
});
|
|
300
324
|
});
|
|
301
325
|
});
|