@amazon/vinyl 3.0.1 → 3.2.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 +1103 -1042
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +810 -739
- package/dist/index.js.map +4 -4
- package/dist/test/benchmark.js.map +2 -2
- package/dist/test/integ.es5.js +394 -388
- package/dist/test/integ.js +2626 -2032
- 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 +19 -1
- package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
- package/dist/types/current/drm/DrmControllerImpl.js +101 -58
- 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 +15 -1
- 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/streaming/mediaTimelineFilter.d.ts +4 -2
- package/dist/types/current/streaming/mediaTimelineFilter.d.ts.map +1 -1
- package/dist/types/current/streaming/mediaTimelineFilter.js +23 -8
- 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 +8 -2
- 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 +25 -7
- package/dist/types/current/track/filters/sampleRateFilter.d.ts.map +1 -1
- package/dist/types/current/track/filters/sampleRateFilter.js +52 -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 +0 -17
- package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
- package/dist/types/current/track/mse/MseTrack.js +1 -45
- 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/VinylPlayer.d.ts +5 -0
- package/dist/types/current/vinyl/VinylPlayer.d.ts.map +1 -1
- package/dist/types/current/vinyl/VinylPlayer.js +23 -1
- 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 +19 -1
- 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/LoadMetric.d.ts +15 -1
- 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/streaming/mediaTimelineFilter.d.ts +4 -2
- 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 +25 -7
- package/dist/types/ts3.7/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -17
- package/dist/types/ts3.7/util/media/mimeType.d.ts +8 -0
- package/dist/types/ts3.7/vinyl/VinylPlayer.d.ts +5 -0
- package/dist/types/ts3.7/vinyl/createVinylFactories.d.ts +7 -16
- package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +19 -1
- 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/LoadMetric.d.ts +15 -1
- 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/streaming/mediaTimelineFilter.d.ts +4 -2
- 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 +25 -7
- package/dist/types/ts4.1/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -17
- package/dist/types/ts4.1/util/media/mimeType.d.ts +8 -0
- package/dist/types/ts4.1/vinyl/VinylPlayer.d.ts +5 -0
- package/dist/types/ts4.1/vinyl/createVinylFactories.d.ts +7 -16
- package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +19 -1
- 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/LoadMetric.d.ts +15 -1
- 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/streaming/mediaTimelineFilter.d.ts +4 -2
- 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 +25 -7
- package/dist/types/ts4.7/track/hls/createHlsFactories.d.ts +1 -1
- package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -17
- package/dist/types/ts4.7/util/media/mimeType.d.ts +8 -0
- package/dist/types/ts4.7/vinyl/VinylPlayer.d.ts +5 -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 +146 -9
- 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 +49 -73
- 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/streaming/mediaTimelineFilter.test.js +60 -54
- package/dist/unit/types/current/track/TrackController.test.js +61 -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 +211 -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/VinylPlayer.test.js +33 -2
- 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
|
@@ -389,6 +389,58 @@ describe('DrmControllerImpl', () => {
|
|
|
389
389
|
expect(errorSpy).not.toHaveBeenCalled();
|
|
390
390
|
expect(mediaKeys.createSession).toHaveBeenCalledTimes(1); // reused
|
|
391
391
|
});
|
|
392
|
+
it('reuses the session when a transformer alters the CDM init data', async () => {
|
|
393
|
+
// A non-identity initDataTransformer changes the bytes handed to the
|
|
394
|
+
// CDM (always the case for FairPlay, or any custom transformer). The
|
|
395
|
+
// reuse check must compare the ORIGINAL init data, not the
|
|
396
|
+
// transformed bytes — otherwise every re-appended init segment fires
|
|
397
|
+
// `encrypted` and spins up a brand-new session instead of recycling.
|
|
398
|
+
const transformed = new Uint8Array([9, 9, 9]);
|
|
399
|
+
const controller = new DrmControllerImpl(deps, {
|
|
400
|
+
licenseProvider,
|
|
401
|
+
keySystems: {
|
|
402
|
+
[DrmKeySystem.WIDEVINE]: {
|
|
403
|
+
initDataTransformer: () => transformed,
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
const localErrors = createEventSpy(controller, 'error');
|
|
408
|
+
const encryptedCb = commonEme.addEncryptedListener.calls.mostRecent().args[1];
|
|
409
|
+
controller.setBufferingDrmInfo(drmInfo);
|
|
410
|
+
encryptedCb({
|
|
411
|
+
initData: new Uint8Array([1, 2, 3]),
|
|
412
|
+
initDataType: 'cenc',
|
|
413
|
+
});
|
|
414
|
+
await flushPromises();
|
|
415
|
+
expect(mediaKeys.createSession).toHaveBeenCalledTimes(1);
|
|
416
|
+
// The session stored the transformed bytes handed to the CDM.
|
|
417
|
+
expect(getSession(0).initData).toEqual(transformed);
|
|
418
|
+
// The same raw init data fires again (e.g. a quality switch
|
|
419
|
+
// re-appends the init segment) — the session must be reused.
|
|
420
|
+
encryptedCb({
|
|
421
|
+
initData: new Uint8Array([1, 2, 3]),
|
|
422
|
+
initDataType: 'cenc',
|
|
423
|
+
});
|
|
424
|
+
await flushPromises();
|
|
425
|
+
expect(localErrors).not.toHaveBeenCalled();
|
|
426
|
+
expect(mediaKeys.createSession).toHaveBeenCalledTimes(1); // reused
|
|
427
|
+
controller.dispose();
|
|
428
|
+
});
|
|
429
|
+
it('ignores encrypted events when the manifest supplies pssh', async () => {
|
|
430
|
+
// The proactive manifest-pssh path owns session creation, so in-band
|
|
431
|
+
// `encrypted` events (e.g. a re-appended init segment on an ABR
|
|
432
|
+
// switch) must not spawn a duplicate session.
|
|
433
|
+
const drmInfoWithPssh = {
|
|
434
|
+
...drmInfo,
|
|
435
|
+
contentProtections: [
|
|
436
|
+
{ keySystem: DrmKeySystem.WIDEVINE, pssh: 'AAAA' },
|
|
437
|
+
],
|
|
438
|
+
};
|
|
439
|
+
drmController.setBufferingDrmInfo(drmInfoWithPssh);
|
|
440
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]));
|
|
441
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
442
|
+
expect(mediaKeys.createSession).not.toHaveBeenCalled();
|
|
443
|
+
});
|
|
392
444
|
describe('and the drm controller is disposed', () => {
|
|
393
445
|
it('does nothing', async () => {
|
|
394
446
|
drmController.dispose();
|
|
@@ -613,6 +665,29 @@ describe('DrmControllerImpl', () => {
|
|
|
613
665
|
});
|
|
614
666
|
});
|
|
615
667
|
describe('when a session message event is observed', () => {
|
|
668
|
+
it('does not update the session when disposed before the license resolves', async () => {
|
|
669
|
+
let resolveLicense = () => { };
|
|
670
|
+
licenseProvider.and.returnValue(new Promise((res) => {
|
|
671
|
+
resolveLicense = res;
|
|
672
|
+
}));
|
|
673
|
+
drmController = new DrmControllerImpl(deps, {
|
|
674
|
+
keySystems: {
|
|
675
|
+
[DrmKeySystem.WIDEVINE]: {
|
|
676
|
+
licenseServer: { url: 'http://example.com/widevine' },
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
licenseProvider,
|
|
680
|
+
});
|
|
681
|
+
drmController.setBufferingDrmInfo(drmInfo);
|
|
682
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
683
|
+
getSession(0).dispatch('message', { message: new ArrayBuffer(1) });
|
|
684
|
+
await flushPromises();
|
|
685
|
+
// License in flight — dispose, then let it resolve.
|
|
686
|
+
drmController.dispose();
|
|
687
|
+
resolveLicense(new ArrayBuffer(2));
|
|
688
|
+
await flushPromises();
|
|
689
|
+
expect(getSession(0).update).not.toHaveBeenCalled();
|
|
690
|
+
});
|
|
616
691
|
it('calls the licenseProvider with resolved licenseServer options', async () => {
|
|
617
692
|
drmController = new DrmControllerImpl(deps, {
|
|
618
693
|
keySystems: {
|
|
@@ -666,6 +741,24 @@ describe('DrmControllerImpl', () => {
|
|
|
666
741
|
}, message);
|
|
667
742
|
}
|
|
668
743
|
});
|
|
744
|
+
it('falls back to the wildcard key system license server', async () => {
|
|
745
|
+
drmController = new DrmControllerImpl(deps, {
|
|
746
|
+
keySystems: {
|
|
747
|
+
// Only a wildcard entry — no per-key-system configuration.
|
|
748
|
+
'*': {
|
|
749
|
+
licenseServer: () => ({
|
|
750
|
+
url: 'http://example.com/wildcard',
|
|
751
|
+
}),
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
licenseProvider,
|
|
755
|
+
});
|
|
756
|
+
drmController.setBufferingDrmInfo(drmInfo);
|
|
757
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
758
|
+
const message = new ArrayBuffer(1);
|
|
759
|
+
await emitMessage(0, message);
|
|
760
|
+
expect(licenseProvider).toHaveBeenCalledOnceWith(DrmKeySystem.WIDEVINE, { url: 'http://example.com/wildcard' }, message);
|
|
761
|
+
});
|
|
669
762
|
describe('when license takes too long to resolve', () => {
|
|
670
763
|
it('emits an error event', async () => {
|
|
671
764
|
drmController = new DrmControllerImpl(deps, {
|
|
@@ -857,28 +950,70 @@ describe('DrmControllerImpl', () => {
|
|
|
857
950
|
drmController.setBufferingDrmInfo(drmInfo);
|
|
858
951
|
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
859
952
|
await emitMessage(0, new ArrayBuffer(1));
|
|
860
|
-
expect(spy).
|
|
953
|
+
expect(spy).toHaveBeenCalledWith(objectContaining({
|
|
861
954
|
kind: 'license',
|
|
862
955
|
startTime: any(Number),
|
|
863
956
|
endTime: any(Number),
|
|
864
957
|
}));
|
|
865
958
|
});
|
|
959
|
+
it('measures the key setup, contiguous with and before the license', async () => {
|
|
960
|
+
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
961
|
+
const spy = createEventSpy(drmController, 'loadSpanMeasured');
|
|
962
|
+
drmController.setBufferingDrmInfo(drmInfo, { trackUri: 'track-a' });
|
|
963
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
964
|
+
await emitMessage(0, new ArrayBuffer(1));
|
|
965
|
+
const spans = spy.calls.all().map((call) => call.args[0]);
|
|
966
|
+
expect(spans.map((span) => span.kind)).toEqual([
|
|
967
|
+
'keySetup',
|
|
968
|
+
'license',
|
|
969
|
+
]);
|
|
970
|
+
const [keySetup, license] = spans;
|
|
971
|
+
expect(keySetup.trackUri).toEqual('track-a');
|
|
972
|
+
expect(keySetup.endTime).toEqual(license.startTime);
|
|
973
|
+
});
|
|
974
|
+
it('carries the content mime and content type on the license span', async () => {
|
|
975
|
+
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
976
|
+
const spy = createEventSpy(drmController, 'loadSpanMeasured');
|
|
977
|
+
drmController.setBufferingDrmInfo(drmInfo);
|
|
978
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
979
|
+
await emitMessage(0, new ArrayBuffer(1));
|
|
980
|
+
expect(spy).toHaveBeenCalledWith(objectContaining({
|
|
981
|
+
kind: 'license',
|
|
982
|
+
mimeType: 'audio/mp4',
|
|
983
|
+
contentType: 'audio',
|
|
984
|
+
}));
|
|
985
|
+
});
|
|
866
986
|
it('does not measure a failed license exchange', async () => {
|
|
867
987
|
licenseProvider.and.rejectWith(new Error('license failed'));
|
|
868
988
|
const spy = createEventSpy(drmController, 'loadSpanMeasured');
|
|
869
989
|
drmController.setBufferingDrmInfo(drmInfo);
|
|
870
990
|
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
871
991
|
await emitMessage(0, new ArrayBuffer(1));
|
|
872
|
-
|
|
992
|
+
// Setup completed at the message, so keySetup is still measured.
|
|
993
|
+
expect(spy).not.toHaveBeenCalledWith(objectContaining({ kind: 'license' }));
|
|
873
994
|
errorSpy.calls.reset();
|
|
874
995
|
});
|
|
996
|
+
it('measures key setup only for the first session of a track load', async () => {
|
|
997
|
+
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
998
|
+
const spy = createEventSpy(drmController, 'loadSpanMeasured');
|
|
999
|
+
drmController.setBufferingDrmInfo(drmInfo, { trackUri: 'track-a' });
|
|
1000
|
+
await emitEncrypted(new Uint8Array([1]), 'cenc');
|
|
1001
|
+
await emitEncrypted(new Uint8Array([2]), 'cenc');
|
|
1002
|
+
expect(mediaKeys.createSession).toHaveBeenCalledTimes(2);
|
|
1003
|
+
await emitMessage(0, new ArrayBuffer(1));
|
|
1004
|
+
await emitMessage(1, new ArrayBuffer(1));
|
|
1005
|
+
const keySetupSpans = spy.calls
|
|
1006
|
+
.all()
|
|
1007
|
+
.filter((call) => call.args[0].kind === 'keySetup');
|
|
1008
|
+
expect(keySetupSpans.length).toEqual(1);
|
|
1009
|
+
});
|
|
875
1010
|
it('attributes the license span to the buffering track uri', async () => {
|
|
876
1011
|
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
877
1012
|
const spy = createEventSpy(drmController, 'loadSpanMeasured');
|
|
878
1013
|
drmController.setBufferingDrmInfo(drmInfo, { trackUri: 'track-a' });
|
|
879
1014
|
await emitEncrypted(new Uint8Array([1, 2, 3]), 'cenc');
|
|
880
1015
|
await emitMessage(0, new ArrayBuffer(1));
|
|
881
|
-
expect(spy).
|
|
1016
|
+
expect(spy).toHaveBeenCalledWith(objectContaining({
|
|
882
1017
|
kind: 'license',
|
|
883
1018
|
trackUri: 'track-a',
|
|
884
1019
|
}));
|
|
@@ -912,12 +1047,14 @@ describe('DrmControllerImpl', () => {
|
|
|
912
1047
|
// on the controller's current buffering track.
|
|
913
1048
|
await emitMessage(1, new ArrayBuffer(1));
|
|
914
1049
|
await emitMessage(0, new ArrayBuffer(1));
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
expect(
|
|
1050
|
+
const licenseTrackUris = spy.calls
|
|
1051
|
+
.all()
|
|
1052
|
+
.map((call) => call.args[0])
|
|
1053
|
+
.filter((span) => span.kind === 'license')
|
|
1054
|
+
.map((span) => span.trackUri);
|
|
1055
|
+
expect(licenseTrackUris.length).toEqual(2);
|
|
1056
|
+
expect(licenseTrackUris).toContain('track-preloaded');
|
|
1057
|
+
expect(licenseTrackUris).toContain('track-active');
|
|
921
1058
|
});
|
|
922
1059
|
});
|
|
923
1060
|
describe('serverCertificate', () => {
|
|
@@ -25,10 +25,8 @@ import './global/configureVinylGlobal.test';
|
|
|
25
25
|
import './global/environment.test';
|
|
26
26
|
import './global/globalRef.test';
|
|
27
27
|
import './global/PlayerRegistry.test';
|
|
28
|
-
import './
|
|
29
|
-
import './
|
|
30
|
-
import './patch/media/patches/createUnreliablePlaybackEventsPatch.test';
|
|
31
|
-
import './patch/media/patchMediaElement.test';
|
|
28
|
+
import './playback/createStallDetector.test';
|
|
29
|
+
import './playback/createStallMonitor.test';
|
|
32
30
|
import './playback/error/InvalidSeekError.test';
|
|
33
31
|
import './playback/error/ReportableMediaError.test';
|
|
34
32
|
import './playback/logging/mediaErrorToJson.test';
|
|
@@ -116,6 +114,7 @@ import './util/media/codecOverrides.test';
|
|
|
116
114
|
import './util/media/codecSupport.test';
|
|
117
115
|
import './util/media/getAudioContext.test';
|
|
118
116
|
import './util/media/mediaSource.test';
|
|
117
|
+
import './util/media/mimeType.test';
|
|
119
118
|
import './util/media/sourceBuffer.test';
|
|
120
119
|
import './vinyl/createVinylFactories.test';
|
|
121
120
|
import './vinyl/playerConfigFactory.test';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../test/unit/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,wBAAwB,CAAA;AAC/B,OAAO,4BAA4B,CAAA;AACnC,OAAO,wCAAwC,CAAA;AAC/C,OAAO,oCAAoC,CAAA;AAC3C,OAAO,gCAAgC,CAAA;AACvC,cAAc,0CAA0C,CAAA;AACxD,OAAO,iCAAiC,CAAA;AACxC,OAAO,gCAAgC,CAAA;AACvC,OAAO,4CAA4C,CAAA;AACnD,OAAO,kCAAkC,CAAA;AACzC,OAAO,wCAAwC,CAAA;AAC/C,OAAO,sCAAsC,CAAA;AAC7C,OAAO,6BAA6B,CAAA;AACpC,OAAO,8BAA8B,CAAA;AACrC,OAAO,yBAAyB,CAAA;AAChC,OAAO,iCAAiC,CAAA;AACxC,OAAO,2BAA2B,CAAA;AAClC,OAAO,mDAAmD,CAAA;AAC1D,OAAO,+CAA+C,CAAA;AACtD,OAAO,kCAAkC,CAAA;AACzC,OAAO,0CAA0C,CAAA;AACjD,OAAO,0BAA0B,CAAA;AACjC,OAAO,oCAAoC,CAAA;AAC3C,OAAO,2BAA2B,CAAA;AAClC,OAAO,yBAAyB,CAAA;AAChC,OAAO,8BAA8B,CAAA;AACrC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../test/unit/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,10 +25,8 @@ import './global/configureVinylGlobal.test';
|
|
|
25
25
|
import './global/environment.test';
|
|
26
26
|
import './global/globalRef.test';
|
|
27
27
|
import './global/PlayerRegistry.test';
|
|
28
|
-
import './
|
|
29
|
-
import './
|
|
30
|
-
import './patch/media/patches/createUnreliablePlaybackEventsPatch.test';
|
|
31
|
-
import './patch/media/patchMediaElement.test';
|
|
28
|
+
import './playback/createStallDetector.test';
|
|
29
|
+
import './playback/createStallMonitor.test';
|
|
32
30
|
import './playback/error/InvalidSeekError.test';
|
|
33
31
|
import './playback/error/ReportableMediaError.test';
|
|
34
32
|
import './playback/logging/mediaErrorToJson.test';
|
|
@@ -116,6 +114,7 @@ import './util/media/codecOverrides.test';
|
|
|
116
114
|
import './util/media/codecSupport.test';
|
|
117
115
|
import './util/media/getAudioContext.test';
|
|
118
116
|
import './util/media/mediaSource.test';
|
|
117
|
+
import './util/media/mimeType.test';
|
|
119
118
|
import './util/media/sourceBuffer.test';
|
|
120
119
|
import './vinyl/createVinylFactories.test';
|
|
121
120
|
import './vinyl/playerConfigFactory.test';
|
|
@@ -78,6 +78,29 @@ describe('PlaybackController', () => {
|
|
|
78
78
|
if (!controller.disposed)
|
|
79
79
|
controller.dispose();
|
|
80
80
|
});
|
|
81
|
+
describe('stall detection', () => {
|
|
82
|
+
it('does not emit stallEntered before playback is observed', async () => {
|
|
83
|
+
const enteredSpy = createEventSpy(controller, 'stallEntered');
|
|
84
|
+
await clock.tick(2);
|
|
85
|
+
expect(enteredSpy).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
it('emits stallEntered when the play head freezes while playing and stallEnded on resume', async () => {
|
|
88
|
+
const enteredSpy = createEventSpy(controller, 'stallEntered');
|
|
89
|
+
const endedSpy = createEventSpy(controller, 'stallEnded');
|
|
90
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
91
|
+
media.currentTime = 1;
|
|
92
|
+
media.dispatchEvent(mockEvent('timeupdate'));
|
|
93
|
+
// Frozen for the 1s default threshold.
|
|
94
|
+
await clock.tick(1);
|
|
95
|
+
expect(enteredSpy).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(endedSpy).not.toHaveBeenCalled();
|
|
97
|
+
// Play head advances -> stall ends with reason 'playing'.
|
|
98
|
+
media.currentTime = 2;
|
|
99
|
+
media.dispatchEvent(mockEvent('timeupdate'));
|
|
100
|
+
expect(endedSpy).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(endedSpy.calls.mostRecent().args[0].reason).toBe('playing');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
81
104
|
it('assigns properties, methods, and events directly to a media object', () => {
|
|
82
105
|
expect(controller.buffered).toBeInstanceOf(ReadonlyRangesImpl);
|
|
83
106
|
testDelegate(controller, 'currentTime', 2);
|
|
@@ -191,6 +214,135 @@ describe('PlaybackController', () => {
|
|
|
191
214
|
},
|
|
192
215
|
});
|
|
193
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Simulates WebKit reverting a seek: the first `revertCount`
|
|
219
|
+
* seeks land at `revertTo` instead of the requested time; later
|
|
220
|
+
* seeks land on the requested time.
|
|
221
|
+
*/
|
|
222
|
+
function enableSeekWithReverts(revertTo, revertCount) {
|
|
223
|
+
let currentTime = 0;
|
|
224
|
+
let reverts = revertCount;
|
|
225
|
+
Object.defineProperty(media, 'currentTime', {
|
|
226
|
+
get() {
|
|
227
|
+
return currentTime;
|
|
228
|
+
},
|
|
229
|
+
set(value) {
|
|
230
|
+
currentTime = reverts-- > 0 ? revertTo : value;
|
|
231
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
232
|
+
void (async () => {
|
|
233
|
+
await flushPromises();
|
|
234
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
235
|
+
})();
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Simulates a WebKit cold-seek stall: `seeking` fires but
|
|
241
|
+
* `seeked` does not for the first `stuckAttempts` attempts, then
|
|
242
|
+
* completes.
|
|
243
|
+
*/
|
|
244
|
+
function enableStuckSeek(stuckAttempts) {
|
|
245
|
+
let currentTime = 0;
|
|
246
|
+
let attempt = 0;
|
|
247
|
+
Object.defineProperty(media, 'currentTime', {
|
|
248
|
+
get() {
|
|
249
|
+
return currentTime;
|
|
250
|
+
},
|
|
251
|
+
set(value) {
|
|
252
|
+
currentTime = value;
|
|
253
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
254
|
+
if (attempt++ >= stuckAttempts) {
|
|
255
|
+
void (async () => {
|
|
256
|
+
await flushPromises();
|
|
257
|
+
media.dispatchEvent(mockEvent('seeked'));
|
|
258
|
+
})();
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
it('warms the decoder and retries when a seek stalls', async () => {
|
|
264
|
+
enableStuckSeek(1); // first attempt hangs, retry completes
|
|
265
|
+
setSeekable([[0, 30]]);
|
|
266
|
+
media.paused = false;
|
|
267
|
+
const seek = controller.seekTo(5);
|
|
268
|
+
// Flush to arm the first attempt's seeked timeout, then
|
|
269
|
+
// advance to fire it; the retry lands via its seeked event.
|
|
270
|
+
await clock.tick(0, 4);
|
|
271
|
+
await seek;
|
|
272
|
+
// Warmed the decoder with play(), then landed on retry.
|
|
273
|
+
expect(media.play).toHaveBeenCalled();
|
|
274
|
+
expect(media.currentTime).toBe(5);
|
|
275
|
+
});
|
|
276
|
+
it('gives up after the retry limit when a seek never completes', async () => {
|
|
277
|
+
enableStuckSeek(99); // never completes
|
|
278
|
+
setSeekable([[0, 30]]);
|
|
279
|
+
media.paused = false;
|
|
280
|
+
const seek = controller.seekTo(5);
|
|
281
|
+
// Leading flush arms the initial attempt's seeked timeout;
|
|
282
|
+
// each subsequent tick fires one and arms the next retry
|
|
283
|
+
// (initial + SEEK_RETRY_LIMIT attempts all time out).
|
|
284
|
+
await clock.tick(0, 4, 4, 4);
|
|
285
|
+
await seek;
|
|
286
|
+
expect(media.play).toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
it('stops retrying when a re-seek never re-enters seeking', async () => {
|
|
289
|
+
let sets = 0;
|
|
290
|
+
let currentTime = 0;
|
|
291
|
+
Object.defineProperty(media, 'currentTime', {
|
|
292
|
+
get() {
|
|
293
|
+
return currentTime;
|
|
294
|
+
},
|
|
295
|
+
set(value) {
|
|
296
|
+
currentTime = value;
|
|
297
|
+
// Only the first attempt emits `seeking`; re-seeks to
|
|
298
|
+
// the same position stay silent (WebKit), so their
|
|
299
|
+
// seeking wait times out and returns null.
|
|
300
|
+
if (sets++ === 0) {
|
|
301
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
setSeekable([[0, 30]]);
|
|
306
|
+
media.paused = false;
|
|
307
|
+
const seek = controller.seekTo(5);
|
|
308
|
+
await clock.tick(0, 4, 4, 4);
|
|
309
|
+
await seek;
|
|
310
|
+
expect(media.currentTime).toBe(5);
|
|
311
|
+
});
|
|
312
|
+
it('rejects a seek disposed while awaiting seeked', async () => {
|
|
313
|
+
setSeekable([[0, 30]]);
|
|
314
|
+
media.paused = false;
|
|
315
|
+
const seek = controller.seekTo(5);
|
|
316
|
+
media.dispatchEvent(mockEvent('seeking'));
|
|
317
|
+
await flushPromises();
|
|
318
|
+
controller.dispose();
|
|
319
|
+
await expectAsync(seek).toBeRejectedWithError(AbortError);
|
|
320
|
+
});
|
|
321
|
+
it('re-seeks when WebKit lands the play head off target', async () => {
|
|
322
|
+
enableSeekWithReverts(20, 1);
|
|
323
|
+
setSeekable([[0, 30]]);
|
|
324
|
+
media.paused = true;
|
|
325
|
+
await controller.seekTo(5);
|
|
326
|
+
expect(media.currentTime).toBeCloseTo(5);
|
|
327
|
+
});
|
|
328
|
+
it('gives up re-seeking after the retry limit', async () => {
|
|
329
|
+
enableSeekWithReverts(20, 99);
|
|
330
|
+
setSeekable([[0, 30]]);
|
|
331
|
+
media.paused = true;
|
|
332
|
+
await controller.seekTo(5);
|
|
333
|
+
expect(media.currentTime).toBe(20);
|
|
334
|
+
});
|
|
335
|
+
it('stops re-seeking when a newer seek supersedes', async () => {
|
|
336
|
+
enableSeekWithReverts(20, 99);
|
|
337
|
+
setSeekable([[0, 30]]);
|
|
338
|
+
media.paused = true;
|
|
339
|
+
const first = controller.seekTo(5);
|
|
340
|
+
const second = controller.seekTo(9);
|
|
341
|
+
await Promise.allSettled([first, second]);
|
|
342
|
+
// Both landed at the reverted position; the first stopped
|
|
343
|
+
// re-seeking once superseded rather than fighting the newer.
|
|
344
|
+
expect(media.currentTime).toBe(20);
|
|
345
|
+
});
|
|
194
346
|
it('waits for metadata before initiating seek', async () => {
|
|
195
347
|
enableSeekAutoComplete();
|
|
196
348
|
const seeked = controller.seekTo(3);
|
|
@@ -199,6 +351,19 @@ describe('PlaybackController', () => {
|
|
|
199
351
|
await seeked;
|
|
200
352
|
expect(media.currentTime).toBe(3);
|
|
201
353
|
});
|
|
354
|
+
it('fabricates a playing event if playback resumes after a seek without one', async () => {
|
|
355
|
+
enableSeekAutoComplete();
|
|
356
|
+
setSeekable([[0, 10]]);
|
|
357
|
+
media.paused = false;
|
|
358
|
+
const playingSpy = createSpy('playing');
|
|
359
|
+
controller.on('playing', playingSpy);
|
|
360
|
+
await controller.seekTo(3);
|
|
361
|
+
// The play head advances but no 'playing' event fires; the
|
|
362
|
+
// post-seek stall detector fabricates one.
|
|
363
|
+
media.currentTime = 3.5;
|
|
364
|
+
await clock.tick(0.25);
|
|
365
|
+
expect(playingSpy).toHaveBeenCalled();
|
|
366
|
+
});
|
|
202
367
|
it('does not let a metadata-deferred seek clobber a newer seek', async () => {
|
|
203
368
|
// Reproduces the real race: the activate-time seek to the
|
|
204
369
|
// track start parks waiting for the `loadedmetadata` EVENT.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createStallDetector.test.d.ts","sourceRoot":"","sources":["../../../../../test/unit/playback/createStallDetector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { createStallDetector } from '@amazon/vinyl';
|
|
6
|
+
import { implementEventFakes, MockHTMLAudioElement, mockEvent, MockTimeRanges, useMockTime, } from '@amazon/vinyl-util/browserTestUtil';
|
|
7
|
+
describe('createStallDetector', () => {
|
|
8
|
+
const clock = useMockTime();
|
|
9
|
+
let media;
|
|
10
|
+
let fabricatePlaying;
|
|
11
|
+
let stop;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
media = new MockHTMLAudioElement();
|
|
14
|
+
implementEventFakes(media);
|
|
15
|
+
media.play.and.returnValue(Promise.resolve());
|
|
16
|
+
media.paused = false;
|
|
17
|
+
media.ended = false;
|
|
18
|
+
media.readyState = media.HAVE_ENOUGH_DATA;
|
|
19
|
+
media.currentTime = 10;
|
|
20
|
+
media.buffered = new MockTimeRanges([[0, 100]]);
|
|
21
|
+
fabricatePlaying = jasmine.createSpy('fabricatePlaying');
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
stop?.();
|
|
25
|
+
});
|
|
26
|
+
function detect(overrides = {}) {
|
|
27
|
+
stop = createStallDetector(media, { fabricatePlaying, ...overrides });
|
|
28
|
+
}
|
|
29
|
+
/** Advances the mock clock by `n` default poll intervals. */
|
|
30
|
+
async function poll(n) {
|
|
31
|
+
for (let i = 0; i < n; i++) {
|
|
32
|
+
await clock.tick(0.25);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
describe('a stall inside ready buffered data', () => {
|
|
36
|
+
it('nudges the play head once frozen past the stall threshold', async () => {
|
|
37
|
+
detect();
|
|
38
|
+
// Threshold is 0.75s = three polls; the third nudges by stallSkip.
|
|
39
|
+
await poll(3);
|
|
40
|
+
expect(media.currentTime).toBeCloseTo(10.1);
|
|
41
|
+
});
|
|
42
|
+
it('does not recover before the stall threshold', async () => {
|
|
43
|
+
detect();
|
|
44
|
+
await poll(2);
|
|
45
|
+
expect(media.currentTime).toBe(10);
|
|
46
|
+
expect(media.play).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
it('nudges even when the ready state is low (iOS reports a stale readyState)', async () => {
|
|
49
|
+
media.readyState = media.HAVE_METADATA;
|
|
50
|
+
detect();
|
|
51
|
+
await poll(3);
|
|
52
|
+
expect(media.currentTime).toBeCloseTo(10.1);
|
|
53
|
+
expect(media.play).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
it('keeps nudging, then gives up after maxAttempts', async () => {
|
|
56
|
+
detect();
|
|
57
|
+
// Default maxAttempts is 3, so at most three nudges (one every 3 polls).
|
|
58
|
+
await poll(12);
|
|
59
|
+
expect(media.currentTime).toBeCloseTo(10.3);
|
|
60
|
+
// Further polls do nothing; the detector has stopped.
|
|
61
|
+
await poll(6);
|
|
62
|
+
expect(media.currentTime).toBeCloseTo(10.3);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('a stall with no data at the playhead', () => {
|
|
66
|
+
it('does not recover when the playhead is outside all buffered ranges', async () => {
|
|
67
|
+
media.buffered = new MockTimeRanges([[50, 100]]);
|
|
68
|
+
detect();
|
|
69
|
+
await poll(6);
|
|
70
|
+
expect(media.currentTime).toBe(10);
|
|
71
|
+
expect(fabricatePlaying).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
it('does not recover at the end of buffered data', async () => {
|
|
74
|
+
// Within the default end margin (0.5s) of the buffer end.
|
|
75
|
+
media.buffered = new MockTimeRanges([[0, 10.3]]);
|
|
76
|
+
detect();
|
|
77
|
+
await poll(6);
|
|
78
|
+
expect(media.currentTime).toBe(10);
|
|
79
|
+
});
|
|
80
|
+
it('nudges at the end of the media (stuck end-of-track on src)', async () => {
|
|
81
|
+
// Buffer ends at the media duration: no more data is coming, so a
|
|
82
|
+
// freeze here is a stuck end-of-track and a nudge drives it to end.
|
|
83
|
+
media.buffered = new MockTimeRanges([[0, 10.3]]);
|
|
84
|
+
media.duration = 10.3;
|
|
85
|
+
detect();
|
|
86
|
+
await poll(3);
|
|
87
|
+
expect(media.currentTime).toBeCloseTo(10.1);
|
|
88
|
+
});
|
|
89
|
+
it('nudges outside buffered ranges when nudgeUnbuffered is set', async () => {
|
|
90
|
+
// WebKit path: re-issue the seek even though buffered looks empty.
|
|
91
|
+
media.buffered = new MockTimeRanges([[50, 100]]);
|
|
92
|
+
detect({ nudgeUnbuffered: true });
|
|
93
|
+
await poll(3);
|
|
94
|
+
expect(media.currentTime).toBeCloseTo(10.1);
|
|
95
|
+
});
|
|
96
|
+
it('stops when the source is emptied', async () => {
|
|
97
|
+
detect();
|
|
98
|
+
media.dispatchEvent(mockEvent('emptied'));
|
|
99
|
+
// Stopped: a later freeze in buffered data is not nudged.
|
|
100
|
+
await poll(6);
|
|
101
|
+
expect(media.currentTime).toBe(10);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('fabricating a playing event', () => {
|
|
105
|
+
it('fabricates when the head progresses without a playing event', async () => {
|
|
106
|
+
detect();
|
|
107
|
+
media.currentTime = 20;
|
|
108
|
+
await poll(1);
|
|
109
|
+
expect(fabricatePlaying).toHaveBeenCalledTimes(1);
|
|
110
|
+
// Detector stopped; a later freeze is not recovered.
|
|
111
|
+
await poll(6);
|
|
112
|
+
expect(media.play).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
it('does not fabricate when a real playing event fired and it keeps progressing', async () => {
|
|
115
|
+
detect();
|
|
116
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
117
|
+
media.currentTime = 20;
|
|
118
|
+
await poll(6);
|
|
119
|
+
expect(fabricatePlaying).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
it('fabricates after reviving a stall even if a playing event fired earlier', async () => {
|
|
122
|
+
// iOS pattern: a playing event fires, then playback stalls.
|
|
123
|
+
media.readyState = media.HAVE_METADATA;
|
|
124
|
+
detect();
|
|
125
|
+
media.dispatchEvent(mockEvent('playing'));
|
|
126
|
+
await poll(3);
|
|
127
|
+
expect(media.currentTime).toBeCloseTo(10.1); // nudged (recovered)
|
|
128
|
+
// The recovery revives playback.
|
|
129
|
+
media.currentTime = 11;
|
|
130
|
+
await poll(1);
|
|
131
|
+
expect(fabricatePlaying).toHaveBeenCalledTimes(1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('when the element is not advancing by design', () => {
|
|
135
|
+
it('does not recover while paused', async () => {
|
|
136
|
+
media.paused = true;
|
|
137
|
+
detect();
|
|
138
|
+
await poll(6);
|
|
139
|
+
expect(media.play).not.toHaveBeenCalled();
|
|
140
|
+
expect(media.currentTime).toBe(10);
|
|
141
|
+
});
|
|
142
|
+
it('does not recover once ended', async () => {
|
|
143
|
+
media.ended = true;
|
|
144
|
+
detect();
|
|
145
|
+
await poll(6);
|
|
146
|
+
expect(media.play).not.toHaveBeenCalled();
|
|
147
|
+
expect(media.currentTime).toBe(10);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
it('honors provided options', async () => {
|
|
151
|
+
detect({
|
|
152
|
+
pollInterval: 0.5,
|
|
153
|
+
stallThreshold: 1,
|
|
154
|
+
bufferEndMargin: 0.2,
|
|
155
|
+
stallSkip: 0.25,
|
|
156
|
+
maxAttempts: 1,
|
|
157
|
+
});
|
|
158
|
+
// Threshold 1s = two 0.5s polls; one nudge of 0.25, then it gives up.
|
|
159
|
+
for (let i = 0; i < 6; i++)
|
|
160
|
+
await clock.tick(0.5);
|
|
161
|
+
expect(media.currentTime).toBeCloseTo(10.25);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createStallMonitor.test.d.ts","sourceRoot":"","sources":["../../../../../test/unit/playback/createStallMonitor.test.ts"],"names":[],"mappings":""}
|