@amazon/vinyl 3.0.0 → 3.1.0
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 +987 -969
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +665 -638
- package/dist/index.js.map +4 -4
- package/dist/test/benchmark.js.map +2 -2
- package/dist/test/integ.es5.js +357 -353
- package/dist/test/integ.js +2361 -1870
- package/dist/test/integ.js.map +4 -4
- package/dist/test/jasmine.css +50 -3
- package/dist/test/jasmineHtml.es5.js +195 -155
- package/dist/test/jasmineHtml.js +2938 -1833
- package/dist/test/jasmineHtml.js.map +3 -3
- package/dist/types/current/ad/AdControllerImpl.d.ts.map +1 -1
- package/dist/types/current/ad/AdControllerImpl.js +4 -1
- package/dist/types/current/ad/discoverHlsInterstitials.d.ts.map +1 -1
- package/dist/types/current/ad/discoverHlsInterstitials.js +2 -2
- package/dist/types/current/drm/DrmControllerImpl.d.ts +2 -0
- package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
- package/dist/types/current/drm/DrmControllerImpl.js +56 -48
- package/dist/types/current/drm/licenseProvider/defaultLicenseProvider.d.ts.map +1 -1
- package/dist/types/current/drm/licenseProvider/defaultLicenseProvider.js +2 -2
- package/dist/types/current/index.d.ts +3 -6
- package/dist/types/current/index.d.ts.map +1 -1
- package/dist/types/current/index.js +3 -6
- package/dist/types/current/playback/PlaybackControllerImpl.d.ts +15 -0
- package/dist/types/current/playback/PlaybackControllerImpl.d.ts.map +1 -1
- package/dist/types/current/playback/PlaybackControllerImpl.js +100 -9
- package/dist/types/current/playback/ReadonlyPlaybackController.d.ts +27 -1
- package/dist/types/current/playback/ReadonlyPlaybackController.d.ts.map +1 -1
- package/dist/types/current/playback/ReadonlyPlaybackController.js +16 -0
- package/dist/types/current/playback/createStallDetector.d.ts +43 -0
- package/dist/types/current/playback/createStallDetector.d.ts.map +1 -0
- package/dist/types/current/playback/createStallDetector.js +101 -0
- package/dist/types/current/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/current/playback/createStallMonitor.d.ts.map +1 -0
- package/dist/types/current/playback/createStallMonitor.js +94 -0
- package/dist/types/current/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/current/streaming/AudioOptions.d.ts.map +1 -1
- package/dist/types/current/streaming/AudioOptions.js +5 -1
- package/dist/types/current/streaming/ContentStream.d.ts +0 -5
- package/dist/types/current/streaming/ContentStream.d.ts.map +1 -1
- package/dist/types/current/streaming/ContentStream.js +0 -4
- package/dist/types/current/streaming/LoadMetric.d.ts.map +1 -1
- package/dist/types/current/streaming/StreamingEventMap.d.ts +1 -5
- package/dist/types/current/streaming/StreamingEventMap.d.ts.map +1 -1
- package/dist/types/current/streaming/StreamingEventMap.js +0 -1
- package/dist/types/current/streaming/buffering/BufferingController.d.ts +0 -11
- package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
- package/dist/types/current/streaming/buffering/BufferingController.js +0 -13
- package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts.map +1 -1
- package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.js +13 -4
- package/dist/types/current/text/SidecarTextTrackLoader.d.ts.map +1 -1
- package/dist/types/current/text/SidecarTextTrackLoader.js +4 -4
- package/dist/types/current/text/TextTrack.d.ts.map +1 -1
- package/dist/types/current/track/TrackController.d.ts.map +1 -1
- package/dist/types/current/track/TrackController.js +38 -32
- package/dist/types/current/track/createSegmentDataProvider.d.ts.map +1 -1
- package/dist/types/current/track/createSegmentDataProvider.js +6 -3
- package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
- package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.d.ts.map +1 -1
- package/dist/types/current/track/dash/DashMediaQualityMetadataResolver.js +13 -8
- package/dist/types/current/track/dash/createDashFactories.d.ts +1 -1
- package/dist/types/current/track/dash/timeline/DashRepresentationSegmentProvider.d.ts.map +1 -1
- package/dist/types/current/track/dash/timeline/DashRepresentationSegmentProvider.js +71 -3
- package/dist/types/current/track/dash/urlDashManifestProvider.js +2 -2
- package/dist/types/current/track/dash/util/mpd.d.ts +6 -0
- package/dist/types/current/track/dash/util/mpd.d.ts.map +1 -1
- package/dist/types/current/track/dash/util/mpd.js +24 -3
- package/dist/types/current/track/filters/resourceTypeFilter.d.ts +4 -0
- package/dist/types/current/track/filters/resourceTypeFilter.d.ts.map +1 -1
- package/dist/types/current/track/filters/resourceTypeFilter.js +4 -0
- package/dist/types/current/track/filters/sampleRateFilter.d.ts +21 -7
- package/dist/types/current/track/filters/sampleRateFilter.d.ts.map +1 -1
- package/dist/types/current/track/filters/sampleRateFilter.js +42 -24
- package/dist/types/current/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts.map +1 -1
- package/dist/types/current/track/hls/createUrlHlsManifestProvider.js +3 -3
- package/dist/types/current/track/mse/MseTrack.d.ts +9 -17
- package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
- package/dist/types/current/track/mse/MseTrack.js +29 -46
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/current/util/media/mimeType.d.ts +8 -0
- package/dist/types/current/util/media/mimeType.d.ts.map +1 -0
- package/dist/types/current/util/media/mimeType.js +13 -0
- package/dist/types/current/vinyl/createVinylFactories.d.ts +7 -16
- package/dist/types/current/vinyl/createVinylFactories.d.ts.map +1 -1
- package/dist/types/current/vinyl/createVinylFactories.js +2 -12
- package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +2 -0
- package/dist/types/ts3.7/index.d.ts +3 -6
- package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +15 -0
- package/dist/types/ts3.7/playback/ReadonlyPlaybackController.d.ts +28 -0
- package/dist/types/ts3.7/playback/createStallDetector.d.ts +43 -0
- package/dist/types/ts3.7/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/ts3.7/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/ts3.7/streaming/ContentStream.d.ts +0 -5
- package/dist/types/ts3.7/streaming/StreamingEventMap.d.ts +0 -5
- package/dist/types/ts3.7/streaming/buffering/BufferingController.d.ts +0 -11
- package/dist/types/ts3.7/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
- package/dist/types/ts3.7/track/dash/createDashFactories.d.ts +1 -1
- package/dist/types/ts3.7/track/dash/util/mpd.d.ts +6 -0
- package/dist/types/ts3.7/track/filters/resourceTypeFilter.d.ts +4 -0
- package/dist/types/ts3.7/track/filters/sampleRateFilter.d.ts +21 -7
- package/dist/types/ts3.7/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/ts3.7/track/mse/MseTrack.d.ts +9 -17
- package/dist/types/ts3.7/util/media/mimeType.d.ts +8 -0
- package/dist/types/ts3.7/vinyl/createVinylFactories.d.ts +7 -16
- package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +2 -0
- package/dist/types/ts4.1/index.d.ts +3 -6
- package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +15 -0
- package/dist/types/ts4.1/playback/ReadonlyPlaybackController.d.ts +28 -0
- package/dist/types/ts4.1/playback/createStallDetector.d.ts +43 -0
- package/dist/types/ts4.1/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/ts4.1/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/ts4.1/streaming/ContentStream.d.ts +0 -5
- package/dist/types/ts4.1/streaming/StreamingEventMap.d.ts +0 -5
- package/dist/types/ts4.1/streaming/buffering/BufferingController.d.ts +0 -11
- package/dist/types/ts4.1/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
- package/dist/types/ts4.1/track/dash/createDashFactories.d.ts +1 -1
- package/dist/types/ts4.1/track/dash/util/mpd.d.ts +6 -0
- package/dist/types/ts4.1/track/filters/resourceTypeFilter.d.ts +4 -0
- package/dist/types/ts4.1/track/filters/sampleRateFilter.d.ts +21 -7
- package/dist/types/ts4.1/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/ts4.1/track/mse/MseTrack.d.ts +9 -17
- package/dist/types/ts4.1/util/media/mimeType.d.ts +8 -0
- package/dist/types/ts4.1/vinyl/createVinylFactories.d.ts +7 -16
- package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +2 -0
- package/dist/types/ts4.7/index.d.ts +3 -6
- package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +15 -0
- package/dist/types/ts4.7/playback/ReadonlyPlaybackController.d.ts +28 -0
- package/dist/types/ts4.7/playback/createStallDetector.d.ts +43 -0
- package/dist/types/ts4.7/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/ts4.7/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/ts4.7/streaming/ContentStream.d.ts +0 -5
- package/dist/types/ts4.7/streaming/StreamingEventMap.d.ts +0 -5
- package/dist/types/ts4.7/streaming/buffering/BufferingController.d.ts +0 -11
- package/dist/types/ts4.7/track/dash/DashMediaQualityMetadataResolver.d.ts +10 -6
- package/dist/types/ts4.7/track/dash/createDashFactories.d.ts +1 -1
- package/dist/types/ts4.7/track/dash/util/mpd.d.ts +6 -0
- package/dist/types/ts4.7/track/filters/resourceTypeFilter.d.ts +4 -0
- package/dist/types/ts4.7/track/filters/sampleRateFilter.d.ts +21 -7
- package/dist/types/ts4.7/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/ts4.7/track/mse/MseTrack.d.ts +9 -17
- package/dist/types/ts4.7/util/media/mimeType.d.ts +8 -0
- package/dist/types/ts4.7/vinyl/createVinylFactories.d.ts +7 -16
- package/dist/unit/types/current/ad/AdControllerImpl.test.js +27 -1
- package/dist/unit/types/current/drm/DrmControllerImpl.test.js +93 -0
- package/dist/unit/types/current/index.d.ts +3 -4
- package/dist/unit/types/current/index.d.ts.map +1 -1
- package/dist/unit/types/current/index.js +3 -4
- package/dist/unit/types/current/playback/PlaybackControllerImpl.test.js +165 -0
- package/dist/unit/types/current/playback/createStallDetector.test.d.ts +2 -0
- package/dist/unit/types/current/playback/createStallDetector.test.d.ts.map +1 -0
- package/dist/unit/types/current/playback/createStallDetector.test.js +163 -0
- package/dist/unit/types/current/playback/createStallMonitor.test.d.ts +2 -0
- package/dist/unit/types/current/playback/createStallMonitor.test.d.ts.map +1 -0
- package/dist/unit/types/current/playback/createStallMonitor.test.js +125 -0
- package/dist/unit/types/current/streaming/ContentStream.test.js +0 -11
- package/dist/unit/types/current/streaming/MseTrack.test.js +47 -74
- package/dist/unit/types/current/streaming/abr/QualitySelectorImpl.test.js +1 -1
- package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +0 -21
- package/dist/unit/types/current/streaming/createDefaultMediaTimelineTransformer.test.js +133 -0
- package/dist/unit/types/current/track/TrackController.test.js +97 -1
- package/dist/unit/types/current/track/createSegmentDataProvider.test.js +18 -0
- package/dist/unit/types/current/track/dash/DashMediaQualityMetadataResolver.test.js +46 -14
- package/dist/unit/types/current/track/dash/createSegmentControllerFactory.test.js +1 -0
- package/dist/unit/types/current/track/dash/timeline/DashRepresentationSegmentProvider.test.js +114 -0
- package/dist/unit/types/current/track/dash/util/mpd.test.js +37 -0
- package/dist/unit/types/current/track/filters/resourceTypeFilter.test.js +7 -0
- package/dist/unit/types/current/track/filters/sampleRateFilter.test.js +136 -34
- package/dist/unit/types/current/track/source/SourceObjectTrack.test.js +1 -1
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/unit/types/current/util/media/mimeType.test.d.ts +2 -0
- package/dist/unit/types/current/util/media/mimeType.test.d.ts.map +1 -0
- package/dist/unit/types/current/util/media/mimeType.test.js +22 -0
- package/dist/unit/types/current/vinyl/createVinylFactories.test.js +2 -36
- package/dist/vinylTestUtil/index.js +6 -4
- package/dist/vinylTestUtil/index.js.map +2 -2
- package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts +1 -0
- package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.js +1 -0
- package/dist/vinylTestUtil/types/current/media/FrequencyAnalyzer.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.d.ts +0 -1
- package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/streaming/MockContentStream.js +0 -1
- package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
- package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/streaming/buffering/MockBufferingController.js +0 -1
- package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/vinylTestUtil/types/current/util/playback/eventPromises.js +1 -1
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackCanSeek.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/util/playback/expectTrackCanSeek.js +9 -1
- package/dist/vinylTestUtil/types/ts3.7/assets/vinylTestAssets.d.ts +1 -0
- package/dist/vinylTestUtil/types/ts3.7/mock/streaming/MockContentStream.d.ts +0 -1
- package/dist/vinylTestUtil/types/ts3.7/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
- package/dist/vinylTestUtil/types/ts4.1/assets/vinylTestAssets.d.ts +1 -0
- package/dist/vinylTestUtil/types/ts4.1/mock/streaming/MockContentStream.d.ts +0 -1
- package/dist/vinylTestUtil/types/ts4.1/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
- package/dist/vinylTestUtil/types/ts4.7/assets/vinylTestAssets.d.ts +1 -0
- package/dist/vinylTestUtil/types/ts4.7/mock/streaming/MockContentStream.d.ts +0 -1
- package/dist/vinylTestUtil/types/ts4.7/mock/streaming/buffering/MockBufferingController.d.ts +0 -1
- package/docs/USAGE.md +29 -0
- package/package.json +11 -10
- package/dist/types/current/patch/media/HtmlMediaElementPatch.d.ts +0 -3
- package/dist/types/current/patch/media/HtmlMediaElementPatch.d.ts.map +0 -1
- package/dist/types/current/patch/media/HtmlMediaElementPatch.js +0 -5
- package/dist/types/current/patch/media/MediaElementPatchOptions.d.ts +0 -19
- package/dist/types/current/patch/media/MediaElementPatchOptions.d.ts.map +0 -1
- package/dist/types/current/patch/media/MediaElementPatchOptions.js +0 -16
- package/dist/types/current/patch/media/patchMediaElement.d.ts +0 -9
- package/dist/types/current/patch/media/patchMediaElement.d.ts.map +0 -1
- package/dist/types/current/patch/media/patchMediaElement.js +0 -15
- package/dist/types/current/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
- package/dist/types/current/patch/media/patches/createPreventStallsPatch.d.ts.map +0 -1
- package/dist/types/current/patch/media/patches/createPreventStallsPatch.js +0 -55
- package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
- package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts.map +0 -1
- package/dist/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.js +0 -218
- package/dist/types/current/vinyl/VinylPatchOptions.d.ts +0 -10
- package/dist/types/current/vinyl/VinylPatchOptions.d.ts.map +0 -1
- package/dist/types/current/vinyl/VinylPatchOptions.js +0 -11
- package/dist/types/ts3.7/patch/media/HtmlMediaElementPatch.d.ts +0 -3
- package/dist/types/ts3.7/patch/media/MediaElementPatchOptions.d.ts +0 -19
- package/dist/types/ts3.7/patch/media/patchMediaElement.d.ts +0 -9
- package/dist/types/ts3.7/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
- package/dist/types/ts3.7/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
- package/dist/types/ts3.7/vinyl/VinylPatchOptions.d.ts +0 -10
- package/dist/types/ts4.1/patch/media/HtmlMediaElementPatch.d.ts +0 -3
- package/dist/types/ts4.1/patch/media/MediaElementPatchOptions.d.ts +0 -19
- package/dist/types/ts4.1/patch/media/patchMediaElement.d.ts +0 -9
- package/dist/types/ts4.1/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
- package/dist/types/ts4.1/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
- package/dist/types/ts4.1/vinyl/VinylPatchOptions.d.ts +0 -10
- package/dist/types/ts4.7/patch/media/HtmlMediaElementPatch.d.ts +0 -3
- package/dist/types/ts4.7/patch/media/MediaElementPatchOptions.d.ts +0 -19
- package/dist/types/ts4.7/patch/media/patchMediaElement.d.ts +0 -9
- package/dist/types/ts4.7/patch/media/patches/createPreventStallsPatch.d.ts +0 -17
- package/dist/types/ts4.7/patch/media/patches/createUnreliablePlaybackEventsPatch.d.ts +0 -26
- package/dist/types/ts4.7/vinyl/VinylPatchOptions.d.ts +0 -10
- package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.d.ts +0 -2
- package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.d.ts.map +0 -1
- package/dist/unit/types/current/patch/media/MediaElementPatchOptions.test.js +0 -17
- package/dist/unit/types/current/patch/media/patchMediaElement.test.d.ts +0 -2
- package/dist/unit/types/current/patch/media/patchMediaElement.test.d.ts.map +0 -1
- package/dist/unit/types/current/patch/media/patchMediaElement.test.js +0 -36
- package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.d.ts +0 -2
- package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.d.ts.map +0 -1
- package/dist/unit/types/current/patch/media/patches/createPreventStallsPatch.test.js +0 -93
- package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.d.ts +0 -2
- package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.d.ts.map +0 -1
- package/dist/unit/types/current/patch/media/patches/createUnreliablePlaybackEventsPatch.test.js +0 -423
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { createStallMonitor, StallEndedReason, } from '@amazon/vinyl';
|
|
6
|
+
import { EventHostImpl } from '@amazon/vinyl-util';
|
|
7
|
+
import { useMockTime } from '@amazon/vinyl-util/browserTestUtil';
|
|
8
|
+
describe('createStallMonitor', () => {
|
|
9
|
+
const clock = useMockTime();
|
|
10
|
+
let host;
|
|
11
|
+
let onStallEntered;
|
|
12
|
+
let onStallEnded;
|
|
13
|
+
let stop;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
host = new EventHostImpl();
|
|
16
|
+
onStallEntered = jasmine.createSpy('onStallEntered');
|
|
17
|
+
onStallEnded = jasmine.createSpy('onStallEnded');
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => stop?.());
|
|
20
|
+
function monitor(overrides = {}) {
|
|
21
|
+
stop = createStallMonitor(host, { onStallEntered, onStallEnded }, overrides);
|
|
22
|
+
}
|
|
23
|
+
/** Advances the mock clock by `n` default (0.25s) poll intervals. */
|
|
24
|
+
async function poll(n) {
|
|
25
|
+
for (let i = 0; i < n; i++)
|
|
26
|
+
await clock.tick(0.25);
|
|
27
|
+
}
|
|
28
|
+
it('does not report a stall before playback is observed', async () => {
|
|
29
|
+
monitor();
|
|
30
|
+
// No 'playing' has been observed (e.g. initial load / awaiting a seek).
|
|
31
|
+
await poll(8);
|
|
32
|
+
expect(onStallEntered).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
34
|
+
it('reports a stall once the play head is frozen past the threshold', async () => {
|
|
35
|
+
monitor();
|
|
36
|
+
host.dispatch('playing', {});
|
|
37
|
+
await poll(3); // 0.75s < 1s threshold
|
|
38
|
+
expect(onStallEntered).not.toHaveBeenCalled();
|
|
39
|
+
await poll(1); // 1s
|
|
40
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
41
|
+
});
|
|
42
|
+
it('honors a configurable threshold', async () => {
|
|
43
|
+
monitor({ stallThreshold: 2 });
|
|
44
|
+
host.dispatch('playing', {});
|
|
45
|
+
await poll(4); // 1s < 2s
|
|
46
|
+
expect(onStallEntered).not.toHaveBeenCalled();
|
|
47
|
+
await poll(4); // 2s
|
|
48
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
49
|
+
});
|
|
50
|
+
it('does not report a stall while timeUpdates keep arriving', async () => {
|
|
51
|
+
monitor();
|
|
52
|
+
host.dispatch('playing', {});
|
|
53
|
+
for (let i = 0; i < 10; i++) {
|
|
54
|
+
await poll(2); // 0.5s, under the threshold
|
|
55
|
+
host.dispatch('timeUpdate', { previous: i, current: i + 1 });
|
|
56
|
+
}
|
|
57
|
+
expect(onStallEntered).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
it('ends a stall with reason "playing" when the play head advances, measuring from the last timeUpdate', async () => {
|
|
60
|
+
monitor();
|
|
61
|
+
host.dispatch('playing', {});
|
|
62
|
+
host.dispatch('timeUpdate', { previous: 0, current: 1 });
|
|
63
|
+
await poll(4); // freeze 1s -> stallEntered
|
|
64
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
65
|
+
await poll(4); // frozen another 1s (total 2s)
|
|
66
|
+
host.dispatch('timeUpdate', { previous: 1, current: 2 }); // resume
|
|
67
|
+
expect(onStallEnded).toHaveBeenCalledTimes(1);
|
|
68
|
+
const info = onStallEnded.calls.mostRecent().args[0];
|
|
69
|
+
expect(info.reason).toBe(StallEndedReason.PLAYING);
|
|
70
|
+
expect(info.duration).toBeCloseTo(2, 1);
|
|
71
|
+
});
|
|
72
|
+
for (const [event, reason] of [
|
|
73
|
+
['pause', StallEndedReason.PAUSE],
|
|
74
|
+
['seeking', StallEndedReason.SEEKING],
|
|
75
|
+
['emptied', StallEndedReason.EMPTIED],
|
|
76
|
+
]) {
|
|
77
|
+
it(`ends a stall with reason "${reason}" on ${event}`, async () => {
|
|
78
|
+
monitor();
|
|
79
|
+
host.dispatch('playing', {});
|
|
80
|
+
await poll(4); // stallEntered
|
|
81
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
82
|
+
host.dispatch(event, {});
|
|
83
|
+
expect(onStallEnded).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(onStallEnded.calls.mostRecent().args[0].reason).toBe(reason);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
it('ends a stall with reason "playing" when a playing event fires (resume without a timeUpdate)', async () => {
|
|
88
|
+
monitor();
|
|
89
|
+
host.dispatch('playing', {});
|
|
90
|
+
await poll(4); // stallEntered
|
|
91
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
92
|
+
// A 'playing' event (rather than a timeUpdate) signals the resume.
|
|
93
|
+
host.dispatch('playing', {});
|
|
94
|
+
expect(onStallEnded).toHaveBeenCalledTimes(1);
|
|
95
|
+
expect(onStallEnded.calls.mostRecent().args[0].reason).toBe(StallEndedReason.PLAYING);
|
|
96
|
+
});
|
|
97
|
+
it('suspends detection after a seek until playback is observed again', async () => {
|
|
98
|
+
monitor();
|
|
99
|
+
host.dispatch('playing', {});
|
|
100
|
+
host.dispatch('seeking', {}); // suspends; observedPlaying = false
|
|
101
|
+
await poll(8);
|
|
102
|
+
expect(onStallEntered).not.toHaveBeenCalled();
|
|
103
|
+
// Once playback is observed again, detection resumes.
|
|
104
|
+
host.dispatch('playing', {});
|
|
105
|
+
await poll(4);
|
|
106
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
107
|
+
});
|
|
108
|
+
it('reports each stall/recovery cycle', async () => {
|
|
109
|
+
monitor();
|
|
110
|
+
host.dispatch('playing', {});
|
|
111
|
+
await poll(4);
|
|
112
|
+
host.dispatch('timeUpdate', { previous: 0, current: 1 }); // resume 1
|
|
113
|
+
await poll(4);
|
|
114
|
+
host.dispatch('timeUpdate', { previous: 1, current: 2 }); // resume 2
|
|
115
|
+
expect(onStallEntered).toHaveBeenCalledTimes(2);
|
|
116
|
+
expect(onStallEnded).toHaveBeenCalledTimes(2);
|
|
117
|
+
});
|
|
118
|
+
it('stops reporting after disposal', async () => {
|
|
119
|
+
monitor();
|
|
120
|
+
host.dispatch('playing', {});
|
|
121
|
+
stop();
|
|
122
|
+
await poll(8);
|
|
123
|
+
expect(onStallEntered).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -58,17 +58,6 @@ describe('ContentStreamImpl', () => {
|
|
|
58
58
|
bufferingController.dispatch('error', { error, target });
|
|
59
59
|
expect(spy).toHaveBeenCalledWith({ error, target });
|
|
60
60
|
});
|
|
61
|
-
it('redispatches hasDataChange from bufferingController', () => {
|
|
62
|
-
const spy = createEventSpy(contentStream, 'hasDataChange');
|
|
63
|
-
const event = { previous: false, current: true };
|
|
64
|
-
bufferingController.dispatch('hasDataChange', event);
|
|
65
|
-
expect(spy).toHaveBeenCalledWith(event);
|
|
66
|
-
});
|
|
67
|
-
it('exposes hasData from the bufferingController', () => {
|
|
68
|
-
expect(contentStream.hasData).toBeFalse();
|
|
69
|
-
bufferingController.hasData = true;
|
|
70
|
-
expect(contentStream.hasData).toBeTrue();
|
|
71
|
-
});
|
|
72
61
|
});
|
|
73
62
|
describe('preload', () => {
|
|
74
63
|
it('configures segmentController with startTime and prefetchPriority', () => {
|
|
@@ -2,12 +2,12 @@
|
|
|
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, MseTrack,
|
|
5
|
+
import { createEmptyMediaQualityMetadata, MseTrack, } from '@amazon/vinyl';
|
|
6
6
|
import { createMockDashDependencies, } from '@amazon/vinyl/vinylTestUtil';
|
|
7
7
|
import { createEventSpy, useMockLogger } from '@amazon/vinyl-util/testUtil';
|
|
8
|
-
import { flushPromises
|
|
8
|
+
import { flushPromises } from '@amazon/vinyl-util/browserTestUtil';
|
|
9
9
|
import { externalDependencies } from '@amazon/vinyl-di';
|
|
10
|
-
import { Abort } from '@amazon/vinyl-util';
|
|
10
|
+
import { Abort, MediaUnsupportedError } from '@amazon/vinyl-util';
|
|
11
11
|
var any = jasmine.any;
|
|
12
12
|
describe('MseTrack', () => {
|
|
13
13
|
useMockLogger();
|
|
@@ -413,73 +413,6 @@ describe('MseTrack', () => {
|
|
|
413
413
|
expect(track.bufferingEnded).toBe(false);
|
|
414
414
|
});
|
|
415
415
|
});
|
|
416
|
-
describe('stalled-seek recovery nudge', () => {
|
|
417
|
-
const clock = useMockTime();
|
|
418
|
-
function fillStream(stream) {
|
|
419
|
-
stream.hasData = true;
|
|
420
|
-
stream.dispatch('hasDataChange', { previous: false, current: true });
|
|
421
|
-
}
|
|
422
|
-
async function setup() {
|
|
423
|
-
deps.playbackController.seekTo.and.resolveTo(void 0);
|
|
424
|
-
track = createTrack();
|
|
425
|
-
await awaitContentTypes();
|
|
426
|
-
return { audio: getAudioStream(), video: getVideoStream() };
|
|
427
|
-
}
|
|
428
|
-
it('nudges the playhead when all streams have data but the seek is still stuck', async () => {
|
|
429
|
-
const { audio, video } = await setup();
|
|
430
|
-
deps.playbackController.seeking = true;
|
|
431
|
-
deps.playbackController.currentTime = 30;
|
|
432
|
-
fillStream(audio);
|
|
433
|
-
// Only one stream has data — nothing scheduled yet.
|
|
434
|
-
fillStream(video);
|
|
435
|
-
expect(deps.playbackController.seekTo).not.toHaveBeenCalled();
|
|
436
|
-
await clock.tick(SEEKING_STALL_TIME_CHECK * 1000);
|
|
437
|
-
expect(deps.playbackController.seekTo).toHaveBeenCalledOnceWith(30 + PLAYHEAD_NUDGE, 0);
|
|
438
|
-
});
|
|
439
|
-
it('does not nudge if the seek completes before the check fires', async () => {
|
|
440
|
-
const { audio, video } = await setup();
|
|
441
|
-
deps.playbackController.seeking = true;
|
|
442
|
-
fillStream(audio);
|
|
443
|
-
fillStream(video);
|
|
444
|
-
deps.playbackController.seeking = false;
|
|
445
|
-
await clock.tick(SEEKING_STALL_TIME_CHECK * 1000);
|
|
446
|
-
expect(deps.playbackController.seekTo).not.toHaveBeenCalled();
|
|
447
|
-
});
|
|
448
|
-
it('does not nudge until every stream has data', async () => {
|
|
449
|
-
const { audio } = await setup();
|
|
450
|
-
deps.playbackController.seeking = true;
|
|
451
|
-
fillStream(audio);
|
|
452
|
-
await clock.tick(SEEKING_STALL_TIME_CHECK * 1000);
|
|
453
|
-
expect(deps.playbackController.seekTo).not.toHaveBeenCalled();
|
|
454
|
-
});
|
|
455
|
-
it('does not nudge if a stream loses its data before the check fires', async () => {
|
|
456
|
-
const { audio, video } = await setup();
|
|
457
|
-
deps.playbackController.seeking = true;
|
|
458
|
-
fillStream(audio);
|
|
459
|
-
fillStream(video);
|
|
460
|
-
// A stream's buffer was cleared during the wait window.
|
|
461
|
-
video.hasData = false;
|
|
462
|
-
await clock.tick(SEEKING_STALL_TIME_CHECK * 1000);
|
|
463
|
-
expect(deps.playbackController.seekTo).not.toHaveBeenCalled();
|
|
464
|
-
});
|
|
465
|
-
it('does not nudge when not seeking', async () => {
|
|
466
|
-
const { audio, video } = await setup();
|
|
467
|
-
deps.playbackController.seeking = false;
|
|
468
|
-
fillStream(audio);
|
|
469
|
-
fillStream(video);
|
|
470
|
-
await clock.tick(SEEKING_STALL_TIME_CHECK * 1000);
|
|
471
|
-
expect(deps.playbackController.seekTo).not.toHaveBeenCalled();
|
|
472
|
-
});
|
|
473
|
-
it('does not nudge after the track is disposed', async () => {
|
|
474
|
-
const { audio, video } = await setup();
|
|
475
|
-
deps.playbackController.seeking = true;
|
|
476
|
-
fillStream(audio);
|
|
477
|
-
fillStream(video);
|
|
478
|
-
track.dispose();
|
|
479
|
-
await clock.tick(SEEKING_STALL_TIME_CHECK * 1000);
|
|
480
|
-
expect(deps.playbackController.seekTo).not.toHaveBeenCalled();
|
|
481
|
-
});
|
|
482
|
-
});
|
|
483
416
|
describe('checkBufferingEnded', () => {
|
|
484
417
|
it('calls endOfStream and dispatches bufferingEnded when all streams are done', async () => {
|
|
485
418
|
track = createTrack();
|
|
@@ -652,6 +585,38 @@ describe('MseTrack', () => {
|
|
|
652
585
|
expect(track.qualities).toEqual([qualityMetadata]);
|
|
653
586
|
expect(track.qualitiesUnfiltered).toEqual([qualityMetadata]);
|
|
654
587
|
});
|
|
588
|
+
it('fails fast when a required content type is entirely filtered out', async () => {
|
|
589
|
+
// contentTypes (from the manifest) is {audio, video}, but the
|
|
590
|
+
// transformed timeline has only a video quality — audio was fully
|
|
591
|
+
// filtered. The audio stream could never create a source buffer, so
|
|
592
|
+
// the track must fail rather than stall on the readyToAppend wait.
|
|
593
|
+
const videoQuality = {
|
|
594
|
+
...createEmptyMediaQualityMetadata(),
|
|
595
|
+
contentType: 'video',
|
|
596
|
+
qualityId: 'v1',
|
|
597
|
+
};
|
|
598
|
+
setTimeline({
|
|
599
|
+
periods: [
|
|
600
|
+
{
|
|
601
|
+
startTime: 0,
|
|
602
|
+
endTime: 100,
|
|
603
|
+
qualities: [
|
|
604
|
+
{
|
|
605
|
+
metadata: videoQuality,
|
|
606
|
+
getSegment: () => Promise.resolve(null),
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
},
|
|
610
|
+
],
|
|
611
|
+
minBufferTime: 2,
|
|
612
|
+
getAdBreaks: () => Promise.resolve([]),
|
|
613
|
+
getDuration: () => Promise.resolve(Infinity),
|
|
614
|
+
});
|
|
615
|
+
track = createTrack();
|
|
616
|
+
await flushPromises();
|
|
617
|
+
expect(track.error).toEqual(any(MediaUnsupportedError));
|
|
618
|
+
expect(track.error.code).toBe('no-playable-audio');
|
|
619
|
+
});
|
|
655
620
|
it('reads qualities from the filtered timeline but qualitiesUnfiltered from the raw one', async () => {
|
|
656
621
|
// The transformed timeline is filtered (e.g. by preferred audio
|
|
657
622
|
// language) down to one quality; the raw timeline keeps them all.
|
|
@@ -1025,10 +990,6 @@ describe('MseTrack', () => {
|
|
|
1025
990
|
name: 'audio language',
|
|
1026
991
|
apply: () => (deps.audio.value = jaAudio),
|
|
1027
992
|
},
|
|
1028
|
-
{
|
|
1029
|
-
name: 'allowedContentTypes',
|
|
1030
|
-
apply: () => (deps.allowedContentTypes.value = allowed),
|
|
1031
|
-
},
|
|
1032
993
|
{
|
|
1033
994
|
name: 'audio descriptive',
|
|
1034
995
|
apply: () => (deps.audio.value = descriptiveAudio),
|
|
@@ -1053,6 +1014,18 @@ describe('MseTrack', () => {
|
|
|
1053
1014
|
});
|
|
1054
1015
|
});
|
|
1055
1016
|
}
|
|
1017
|
+
describe('when allowedContentTypes changes', () => {
|
|
1018
|
+
// It only affects the content-type set, which flows through
|
|
1019
|
+
// contentTypesValue -> setContentTypes. Reloading directly here too
|
|
1020
|
+
// would double hard-reset and seek to 0.
|
|
1021
|
+
it('does not reload directly', () => {
|
|
1022
|
+
const clearSpy = spyOn(track, 'clearPrefetch');
|
|
1023
|
+
const resetSpy = spyOn(track, 'reset');
|
|
1024
|
+
deps.allowedContentTypes.value = allowed;
|
|
1025
|
+
expect(clearSpy).not.toHaveBeenCalled();
|
|
1026
|
+
expect(resetSpy).not.toHaveBeenCalled();
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1056
1029
|
describe('when abr resolution restrictions change', () => {
|
|
1057
1030
|
it('clears prefetch (without a rebuild) when maxHeight changes', () => {
|
|
1058
1031
|
const clearSpy = spyOn(track, 'clearPrefetch');
|
|
@@ -333,7 +333,7 @@ describe('QualitySelectorImpl', () => {
|
|
|
333
333
|
});
|
|
334
334
|
});
|
|
335
335
|
});
|
|
336
|
-
describe('when options change', () => {
|
|
336
|
+
describe('when options change after construction', () => {
|
|
337
337
|
it('merges set options with defaults', () => {
|
|
338
338
|
options.value = {
|
|
339
339
|
highBufferThreshold: 32,
|
|
@@ -226,27 +226,6 @@ describe('BufferingControllerImpl', () => {
|
|
|
226
226
|
expect(sourceBufferController.buffered.ranges).toEqual([[40, 60]]);
|
|
227
227
|
});
|
|
228
228
|
});
|
|
229
|
-
describe('hasData', () => {
|
|
230
|
-
beforeEach(() => {
|
|
231
|
-
bufferingController = createBufferingController();
|
|
232
|
-
bufferingController.activate();
|
|
233
|
-
});
|
|
234
|
-
it('flips true when the buffer fills and false when cleared', async () => {
|
|
235
|
-
const spy = createEventSpy(bufferingController, 'hasDataChange');
|
|
236
|
-
expect(bufferingController.hasData).toBeFalse();
|
|
237
|
-
setSegmentList([1]);
|
|
238
|
-
await open();
|
|
239
|
-
expect(bufferingController.hasData).toBeTrue();
|
|
240
|
-
expect(spy).toHaveBeenCalledWith({ previous: false, current: true });
|
|
241
|
-
// Stop the segment provider so the post-clear re-poll can't re-fill.
|
|
242
|
-
segmentController.getSegment.and.returnValue(Promise.resolve(null));
|
|
243
|
-
spy.calls.reset();
|
|
244
|
-
bufferingController.clear();
|
|
245
|
-
await nextPollImmediate();
|
|
246
|
-
expect(bufferingController.hasData).toBeFalse();
|
|
247
|
-
expect(spy).toHaveBeenCalledWith({ previous: true, current: false });
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
229
|
describe('when segments exceed max append size', () => {
|
|
251
230
|
beforeEach(() => {
|
|
252
231
|
bufferingController = createBufferingController({
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createDefaultMediaTimelineTransformer, createEmptyMediaQualityMetadata, } from '@amazon/vinyl';
|
|
6
6
|
import { data } from '@amazon/vinyl-observable';
|
|
7
|
+
import { setUserAgent } from '@amazon/vinyl-util';
|
|
7
8
|
import { MockCapabilities, MockDrmController, } from '@amazon/vinyl/vinylTestUtil';
|
|
8
9
|
function createQuality(overrides) {
|
|
9
10
|
return {
|
|
@@ -332,6 +333,138 @@ describe('createDefaultMediaTimelineTransformer', () => {
|
|
|
332
333
|
expect(result.periods[0].qualities[0].metadata.qualityId).toBe('ja');
|
|
333
334
|
unsub();
|
|
334
335
|
});
|
|
336
|
+
it('disables the sampling-rate filter via audio options at runtime', async () => {
|
|
337
|
+
// Output device at 44.1kHz: the 96kHz rendition is normally dropped
|
|
338
|
+
// (a 44.1kHz alternative exists, so the keep-lowest fallback declines).
|
|
339
|
+
capabilities.sampleRate = 44_100;
|
|
340
|
+
const timeline = {
|
|
341
|
+
periods: [
|
|
342
|
+
{
|
|
343
|
+
startTime: 0,
|
|
344
|
+
endTime: 100,
|
|
345
|
+
qualities: [
|
|
346
|
+
createQuality({
|
|
347
|
+
contentType: 'audio',
|
|
348
|
+
mimeType: 'audio/mp4',
|
|
349
|
+
qualityId: 'a44',
|
|
350
|
+
audioSamplingRate: [44_100],
|
|
351
|
+
}),
|
|
352
|
+
createQuality({
|
|
353
|
+
contentType: 'audio',
|
|
354
|
+
mimeType: 'audio/mp4',
|
|
355
|
+
qualityId: 'a96',
|
|
356
|
+
audioSamplingRate: [96_000],
|
|
357
|
+
}),
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
minBufferTime: 2,
|
|
362
|
+
getAdBreaks: () => Promise.resolve([]),
|
|
363
|
+
getDuration: () => Promise.resolve(Infinity),
|
|
364
|
+
};
|
|
365
|
+
const options = data({ audio: { disableSampleRateFilter: false } });
|
|
366
|
+
const transformed = createDefaultMediaTimelineTransformer({
|
|
367
|
+
capabilities,
|
|
368
|
+
drmController,
|
|
369
|
+
mediaTimeline: data(Promise.resolve(timeline)),
|
|
370
|
+
options,
|
|
371
|
+
});
|
|
372
|
+
const unsub = transformed.onData(() => { });
|
|
373
|
+
const ids = (t) => t.periods[0].qualities.map((q) => q.metadata.qualityId);
|
|
374
|
+
// Filter on: the 96kHz rendition is dropped.
|
|
375
|
+
expect(ids(await transformed.value)).toEqual(['a44']);
|
|
376
|
+
// Filter off: both renditions are kept.
|
|
377
|
+
options.value = { audio: { disableSampleRateFilter: true } };
|
|
378
|
+
expect(ids(await transformed.value)).toEqual(['a44', 'a96']);
|
|
379
|
+
unsub();
|
|
380
|
+
});
|
|
381
|
+
it('honors audio.maxSampleRate over the reported rate at runtime', async () => {
|
|
382
|
+
// Non-Firefox: Firefox hard-caps at 48kHz regardless of maxSampleRate.
|
|
383
|
+
setUserAgent('Chrome');
|
|
384
|
+
// AudioContext reports 48kHz, so the 96kHz rendition is dropped.
|
|
385
|
+
capabilities.sampleRate = 48_000;
|
|
386
|
+
const timeline = {
|
|
387
|
+
periods: [
|
|
388
|
+
{
|
|
389
|
+
startTime: 0,
|
|
390
|
+
endTime: 100,
|
|
391
|
+
qualities: [
|
|
392
|
+
createQuality({
|
|
393
|
+
contentType: 'audio',
|
|
394
|
+
mimeType: 'audio/mp4',
|
|
395
|
+
qualityId: 'a48',
|
|
396
|
+
audioSamplingRate: [48_000],
|
|
397
|
+
}),
|
|
398
|
+
createQuality({
|
|
399
|
+
contentType: 'audio',
|
|
400
|
+
mimeType: 'audio/mp4',
|
|
401
|
+
qualityId: 'a96',
|
|
402
|
+
audioSamplingRate: [96_000],
|
|
403
|
+
}),
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
minBufferTime: 2,
|
|
408
|
+
getAdBreaks: () => Promise.resolve([]),
|
|
409
|
+
getDuration: () => Promise.resolve(Infinity),
|
|
410
|
+
};
|
|
411
|
+
const options = data({ audio: {} });
|
|
412
|
+
const transformed = createDefaultMediaTimelineTransformer({
|
|
413
|
+
capabilities,
|
|
414
|
+
drmController,
|
|
415
|
+
mediaTimeline: data(Promise.resolve(timeline)),
|
|
416
|
+
options,
|
|
417
|
+
});
|
|
418
|
+
const unsub = transformed.onData(() => { });
|
|
419
|
+
const ids = (t) => t.periods[0].qualities.map((q) => q.metadata.qualityId);
|
|
420
|
+
expect(ids(await transformed.value)).toEqual(['a48']);
|
|
421
|
+
// Raise the cap to 96kHz: the 96kHz rendition is now kept.
|
|
422
|
+
options.value = { audio: { maxSampleRate: 96_000 } };
|
|
423
|
+
expect(ids(await transformed.value)).toEqual(['a48', 'a96']);
|
|
424
|
+
unsub();
|
|
425
|
+
});
|
|
426
|
+
it('applies audio.minSampleRate as a soft floor at runtime', async () => {
|
|
427
|
+
capabilities.sampleRate = 192_000; // max cap drops nothing
|
|
428
|
+
const timeline = {
|
|
429
|
+
periods: [
|
|
430
|
+
{
|
|
431
|
+
startTime: 0,
|
|
432
|
+
endTime: 100,
|
|
433
|
+
qualities: [
|
|
434
|
+
createQuality({
|
|
435
|
+
contentType: 'audio',
|
|
436
|
+
mimeType: 'audio/mp4',
|
|
437
|
+
qualityId: 'a48',
|
|
438
|
+
audioSamplingRate: [48_000],
|
|
439
|
+
}),
|
|
440
|
+
createQuality({
|
|
441
|
+
contentType: 'audio',
|
|
442
|
+
mimeType: 'audio/mp4',
|
|
443
|
+
qualityId: 'a24',
|
|
444
|
+
audioSamplingRate: [24_000],
|
|
445
|
+
}),
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
minBufferTime: 2,
|
|
450
|
+
getAdBreaks: () => Promise.resolve([]),
|
|
451
|
+
getDuration: () => Promise.resolve(Infinity),
|
|
452
|
+
};
|
|
453
|
+
const options = data({ audio: {} });
|
|
454
|
+
const transformed = createDefaultMediaTimelineTransformer({
|
|
455
|
+
capabilities,
|
|
456
|
+
drmController,
|
|
457
|
+
mediaTimeline: data(Promise.resolve(timeline)),
|
|
458
|
+
options,
|
|
459
|
+
});
|
|
460
|
+
const unsub = transformed.onData(() => { });
|
|
461
|
+
const ids = (t) => t.periods[0].qualities.map((q) => q.metadata.qualityId);
|
|
462
|
+
expect(ids(await transformed.value)).toEqual(['a48', 'a24']);
|
|
463
|
+
// Floor at 48kHz: the 24kHz rendition is dropped.
|
|
464
|
+
options.value = { audio: { minSampleRate: 48_000 } };
|
|
465
|
+
expect(ids(await transformed.value)).toEqual(['a48']);
|
|
466
|
+
unsub();
|
|
467
|
+
});
|
|
335
468
|
it('preserves minBufferTime', async () => {
|
|
336
469
|
const timeline = {
|
|
337
470
|
periods: [
|
|
@@ -480,6 +480,66 @@ describe('TrackControllerImpl', () => {
|
|
|
480
480
|
});
|
|
481
481
|
});
|
|
482
482
|
});
|
|
483
|
+
describe('guards async ad continuations after dispose', () => {
|
|
484
|
+
it('does not dispatch trackEnded when disposed before the postroll resolves', async () => {
|
|
485
|
+
let resolvePostroll = () => { };
|
|
486
|
+
deps.adController.enterPostroll.and.returnValue(new Promise((res) => {
|
|
487
|
+
resolvePostroll = res;
|
|
488
|
+
}));
|
|
489
|
+
trackController.load(...createLoadOptionsList(2));
|
|
490
|
+
await clock.tick(); // activate the first track
|
|
491
|
+
const endedSpy = jasmine.createSpy('trackEnded');
|
|
492
|
+
trackController.on('trackEnded', endedSpy);
|
|
493
|
+
deps.playbackController.dispatch('ended', {}); // onEnded → enterPostroll pending
|
|
494
|
+
trackController.dispose();
|
|
495
|
+
disposed = true;
|
|
496
|
+
resolvePostroll(null); // .then guarded by interrupted()
|
|
497
|
+
await clock.tick();
|
|
498
|
+
expect(endedSpy).not.toHaveBeenCalled();
|
|
499
|
+
});
|
|
500
|
+
it('does not advance the queue when disposed within the trackEnded handler', async () => {
|
|
501
|
+
trackController.load(...createLoadOptionsList(2));
|
|
502
|
+
await clock.tick(); // activate the first track
|
|
503
|
+
trackController.on('trackEnded', () => {
|
|
504
|
+
trackController.dispose(); // interrupt before the queued next()
|
|
505
|
+
disposed = true;
|
|
506
|
+
});
|
|
507
|
+
const activatedSpy = jasmine.createSpy('trackActivated');
|
|
508
|
+
trackController.on('trackActivated', activatedSpy);
|
|
509
|
+
deps.playbackController.dispatch('ended', {});
|
|
510
|
+
await clock.tick();
|
|
511
|
+
// The queued next() is skipped: interrupted() is true in the microtask.
|
|
512
|
+
expect(activatedSpy).not.toHaveBeenCalled();
|
|
513
|
+
});
|
|
514
|
+
it('swallows a postroll rejection after dispose', async () => {
|
|
515
|
+
let rejectPostroll = () => { };
|
|
516
|
+
deps.adController.enterPostroll.and.returnValue(new Promise((_res, rej) => {
|
|
517
|
+
rejectPostroll = rej;
|
|
518
|
+
}));
|
|
519
|
+
trackController.load(...createLoadOptionsList(1));
|
|
520
|
+
await clock.tick();
|
|
521
|
+
deps.playbackController.dispatch('ended', {});
|
|
522
|
+
trackController.dispose();
|
|
523
|
+
disposed = true;
|
|
524
|
+
rejectPostroll(new Error('late')); // .catch guarded by interrupted()
|
|
525
|
+
await clock.tick();
|
|
526
|
+
expect(trackController.disposed).toBeTrue();
|
|
527
|
+
});
|
|
528
|
+
it('does not activate when disposed before the preroll rejects', async () => {
|
|
529
|
+
let rejectPreroll = () => { };
|
|
530
|
+
deps.adController.enterPreroll.and.returnValue(new Promise((_res, rej) => {
|
|
531
|
+
rejectPreroll = rej;
|
|
532
|
+
}));
|
|
533
|
+
const activatedSpy = jasmine.createSpy('trackActivated');
|
|
534
|
+
trackController.on('trackActivated', activatedSpy);
|
|
535
|
+
trackController.load(...createLoadOptionsList(1)); // activation deferred behind enterPreroll
|
|
536
|
+
trackController.dispose();
|
|
537
|
+
disposed = true;
|
|
538
|
+
rejectPreroll(new Error('late')); // .catch guarded by interrupted()
|
|
539
|
+
await clock.tick();
|
|
540
|
+
expect(activatedSpy).not.toHaveBeenCalled();
|
|
541
|
+
});
|
|
542
|
+
});
|
|
483
543
|
describe('unload', () => {
|
|
484
544
|
it('unloads the active track and clears the queue', () => {
|
|
485
545
|
trackController.load(...createLoadOptionsList(3));
|
|
@@ -813,7 +873,7 @@ describe('TrackControllerImpl', () => {
|
|
|
813
873
|
expect(() => trackController.reset()).not.toThrow();
|
|
814
874
|
});
|
|
815
875
|
});
|
|
816
|
-
describe('reset', () => {
|
|
876
|
+
describe('reset (hard)', () => {
|
|
817
877
|
it('delegates a hard reset to the current track', async () => {
|
|
818
878
|
trackController.load(...createLoadOptionsList(1));
|
|
819
879
|
await clock.tick();
|
|
@@ -1308,6 +1368,42 @@ describe('TrackControllerImpl', () => {
|
|
|
1308
1368
|
expect(trackActivated).toHaveBeenCalledWith(objectContaining({ track: objectContaining({ uri: main.uri }) }));
|
|
1309
1369
|
expect(trackActivated).toHaveBeenCalledBefore(queueEnded);
|
|
1310
1370
|
});
|
|
1371
|
+
it('ends the track after an empty postroll where no ad ever began', async () => {
|
|
1372
|
+
// An empty postroll (a break that resolves to no ads) completes
|
|
1373
|
+
// without any adEntered, so the content track is never deactivated
|
|
1374
|
+
// and stays active. onEnded has already deferred the trackEnded to
|
|
1375
|
+
// adBreakCompleted, so this must still end the track and the queue
|
|
1376
|
+
// rather than hang.
|
|
1377
|
+
const [main] = createLoadOptionsList(1);
|
|
1378
|
+
trackController.load(main);
|
|
1379
|
+
await clock.tick(); // content activates (stays active — no ad plays)
|
|
1380
|
+
expect(trackController.activeTrack.active).toBeTrue();
|
|
1381
|
+
const trackEnded = createEventSpy(trackController, 'trackEnded');
|
|
1382
|
+
const queueEnded = createEventSpy(trackController, 'queueEnded');
|
|
1383
|
+
deps.adController.dispatch('adBreakCompleted', {
|
|
1384
|
+
adBreak: adBreak('postroll'),
|
|
1385
|
+
resumePosition: 0,
|
|
1386
|
+
});
|
|
1387
|
+
await clock.tick();
|
|
1388
|
+
expect(trackEnded).toHaveBeenCalledWith(objectContaining({
|
|
1389
|
+
track: objectContaining({ uri: main.uri }),
|
|
1390
|
+
}));
|
|
1391
|
+
expect(queueEnded).toHaveBeenCalled();
|
|
1392
|
+
});
|
|
1393
|
+
it('advances to the next track after an empty postroll where no ad ever began', async () => {
|
|
1394
|
+
const list = createLoadOptionsList(2);
|
|
1395
|
+
trackController.load(...list);
|
|
1396
|
+
await clock.tick(); // content activates and stays active
|
|
1397
|
+
expect(trackController.activeTrack.active).toBeTrue();
|
|
1398
|
+
const trackEnded = createEventSpy(trackController, 'trackEnded');
|
|
1399
|
+
deps.adController.dispatch('adBreakCompleted', {
|
|
1400
|
+
adBreak: adBreak('postroll'),
|
|
1401
|
+
resumePosition: 0,
|
|
1402
|
+
});
|
|
1403
|
+
await clock.tick();
|
|
1404
|
+
expect(trackEnded).toHaveBeenCalledWith(objectContaining({ track: any(Object) }));
|
|
1405
|
+
expect(trackController.activeTrack?.uri).toBe(list[1].uri);
|
|
1406
|
+
});
|
|
1311
1407
|
it('does not treat a completed midroll ad as the track ending', async () => {
|
|
1312
1408
|
trackController.load(...createLoadOptionsList(1));
|
|
1313
1409
|
const trackEnded = createEventSpy(trackController, 'trackEnded');
|
|
@@ -208,6 +208,24 @@ describe('createSegmentDataProvider', () => {
|
|
|
208
208
|
serviceId: 'example.com',
|
|
209
209
|
}));
|
|
210
210
|
});
|
|
211
|
+
it('does not report transfer entry when aborted before the metrics read', async () => {
|
|
212
|
+
const abort = new Abort();
|
|
213
|
+
const response = new Response(new ArrayBuffer(10));
|
|
214
|
+
spyOn(response, 'arrayBuffer').and.callFake(() => {
|
|
215
|
+
// Abort after the fetch resolves, before the metrics read.
|
|
216
|
+
abort.abort();
|
|
217
|
+
return Promise.resolve(new ArrayBuffer(10));
|
|
218
|
+
});
|
|
219
|
+
mockRequester.request.and.resolveTo(response);
|
|
220
|
+
const provider = createSegmentDataProvider(deps, {
|
|
221
|
+
url: 'https://example.com/seg0.mp4',
|
|
222
|
+
reportDownlinkMetrics: true,
|
|
223
|
+
});
|
|
224
|
+
const promise = provider(abort);
|
|
225
|
+
await clock.tick(2);
|
|
226
|
+
await promise;
|
|
227
|
+
expect(networkMetricsControllerRef.value.addDownlinkTransferEntry).not.toHaveBeenCalled();
|
|
228
|
+
});
|
|
211
229
|
});
|
|
212
230
|
describe('when reportDownlinkMetrics is false', () => {
|
|
213
231
|
it('does not report transfer entry', async () => {
|