@amazon/vinyl 2.0.4 → 3.0.1
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 +624 -338
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +643 -350
- package/dist/index.js.map +4 -4
- package/dist/test/benchmark.es5.js +8 -5
- package/dist/test/benchmark.js +228 -0
- package/dist/test/benchmark.js.map +4 -4
- package/dist/test/integ.es5.js +183 -157
- package/dist/test/integ.js +1966 -817
- package/dist/test/integ.js.map +4 -4
- package/dist/types/current/index.d.ts +4 -0
- package/dist/types/current/index.d.ts.map +1 -1
- package/dist/types/current/index.js +4 -0
- package/dist/types/current/streaming/AudioOptions.d.ts +39 -0
- package/dist/types/current/streaming/AudioOptions.d.ts.map +1 -0
- package/dist/types/current/streaming/AudioOptions.js +15 -0
- package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
- package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts.map +1 -1
- package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.js +5 -5
- package/dist/types/current/text/HtmlTextTrackRenderer.d.ts +60 -0
- package/dist/types/current/text/HtmlTextTrackRenderer.d.ts.map +1 -0
- package/dist/types/current/text/HtmlTextTrackRenderer.js +231 -0
- package/dist/types/current/text/SidecarTextTrackController.d.ts +65 -53
- package/dist/types/current/text/SidecarTextTrackController.d.ts.map +1 -1
- package/dist/types/current/text/SidecarTextTrackController.js +167 -154
- package/dist/types/current/text/SidecarTextTrackLoader.d.ts +5 -0
- package/dist/types/current/text/SidecarTextTrackLoader.d.ts.map +1 -1
- package/dist/types/current/text/SidecarTextTrackLoader.js +13 -7
- package/dist/types/current/text/TextTrack.d.ts +74 -20
- package/dist/types/current/text/TextTrack.d.ts.map +1 -1
- package/dist/types/current/text/TextTrack.js +20 -0
- package/dist/types/current/text/TextTrackRenderer.d.ts +24 -0
- package/dist/types/current/text/TextTrackRenderer.d.ts.map +1 -0
- package/dist/types/current/text/TextTrackRenderer.js +5 -0
- package/dist/types/current/text/mediaTextTrackProvider.d.ts +34 -0
- package/dist/types/current/text/mediaTextTrackProvider.d.ts.map +1 -0
- package/dist/types/current/text/mediaTextTrackProvider.js +42 -0
- package/dist/types/current/text/parseWebVtt.d.ts +16 -2
- package/dist/types/current/text/parseWebVtt.d.ts.map +1 -1
- package/dist/types/current/text/parseWebVtt.js +176 -48
- package/dist/types/current/text/pickPreferredTextTrack.d.ts +2 -1
- package/dist/types/current/text/pickPreferredTextTrack.d.ts.map +1 -1
- package/dist/types/current/text/pickPreferredTextTrack.js +6 -3
- package/dist/types/current/track/Track.d.ts +1 -1
- package/dist/types/current/track/TrackController.d.ts.map +1 -1
- package/dist/types/current/track/TrackController.js +30 -30
- package/dist/types/current/track/dash/createDashFactories.d.ts +21 -10
- package/dist/types/current/track/dash/createDashFactories.d.ts.map +1 -1
- package/dist/types/current/track/dash/createDashFactories.js +9 -21
- package/dist/types/current/track/hls/createHlsFactories.d.ts +21 -10
- package/dist/types/current/track/hls/createHlsFactories.d.ts.map +1 -1
- package/dist/types/current/track/hls/createHlsFactories.js +9 -21
- package/dist/types/current/track/mse/MseTrack.d.ts +1 -2
- package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
- package/dist/types/current/track/mse/MseTrack.js +3 -7
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/current/vinyl/VinylOptions.d.ts +18 -40
- package/dist/types/current/vinyl/VinylOptions.d.ts.map +1 -1
- package/dist/types/current/vinyl/VinylOptions.js +12 -11
- package/dist/types/current/vinyl/VinylPlayer.d.ts +0 -6
- package/dist/types/current/vinyl/VinylPlayer.d.ts.map +1 -1
- package/dist/types/current/vinyl/VinylPlayer.js +2 -23
- package/dist/types/current/vinyl/createVinylFactories.d.ts +32 -18
- package/dist/types/current/vinyl/createVinylFactories.d.ts.map +1 -1
- package/dist/types/current/vinyl/createVinylFactories.js +6 -0
- package/dist/types/ts3.7/index.d.ts +4 -0
- package/dist/types/ts3.7/streaming/AudioOptions.d.ts +39 -0
- package/dist/types/ts3.7/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
- package/dist/types/ts3.7/text/HtmlTextTrackRenderer.d.ts +60 -0
- package/dist/types/ts3.7/text/SidecarTextTrackController.d.ts +65 -53
- package/dist/types/ts3.7/text/SidecarTextTrackLoader.d.ts +5 -0
- package/dist/types/ts3.7/text/TextTrack.d.ts +74 -20
- package/dist/types/ts3.7/text/TextTrackRenderer.d.ts +24 -0
- package/dist/types/ts3.7/text/mediaTextTrackProvider.d.ts +34 -0
- package/dist/types/ts3.7/text/parseWebVtt.d.ts +16 -2
- package/dist/types/ts3.7/text/pickPreferredTextTrack.d.ts +2 -1
- package/dist/types/ts3.7/track/Track.d.ts +1 -1
- package/dist/types/ts3.7/track/dash/createDashFactories.d.ts +21 -10
- package/dist/types/ts3.7/track/hls/createHlsFactories.d.ts +21 -10
- package/dist/types/ts3.7/track/mse/MseTrack.d.ts +1 -2
- package/dist/types/ts3.7/vinyl/VinylOptions.d.ts +18 -40
- package/dist/types/ts3.7/vinyl/VinylPlayer.d.ts +0 -6
- package/dist/types/ts3.7/vinyl/createVinylFactories.d.ts +32 -18
- package/dist/types/ts4.1/index.d.ts +4 -0
- package/dist/types/ts4.1/streaming/AudioOptions.d.ts +39 -0
- package/dist/types/ts4.1/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
- package/dist/types/ts4.1/text/HtmlTextTrackRenderer.d.ts +60 -0
- package/dist/types/ts4.1/text/SidecarTextTrackController.d.ts +65 -53
- package/dist/types/ts4.1/text/SidecarTextTrackLoader.d.ts +5 -0
- package/dist/types/ts4.1/text/TextTrack.d.ts +74 -20
- package/dist/types/ts4.1/text/TextTrackRenderer.d.ts +24 -0
- package/dist/types/ts4.1/text/mediaTextTrackProvider.d.ts +34 -0
- package/dist/types/ts4.1/text/parseWebVtt.d.ts +16 -2
- package/dist/types/ts4.1/text/pickPreferredTextTrack.d.ts +2 -1
- package/dist/types/ts4.1/track/Track.d.ts +1 -1
- package/dist/types/ts4.1/track/dash/createDashFactories.d.ts +21 -10
- package/dist/types/ts4.1/track/hls/createHlsFactories.d.ts +21 -10
- package/dist/types/ts4.1/track/mse/MseTrack.d.ts +1 -2
- package/dist/types/ts4.1/vinyl/VinylOptions.d.ts +18 -40
- package/dist/types/ts4.1/vinyl/VinylPlayer.d.ts +0 -6
- package/dist/types/ts4.1/vinyl/createVinylFactories.d.ts +32 -18
- package/dist/types/ts4.7/index.d.ts +4 -0
- package/dist/types/ts4.7/streaming/AudioOptions.d.ts +39 -0
- package/dist/types/ts4.7/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
- package/dist/types/ts4.7/text/HtmlTextTrackRenderer.d.ts +60 -0
- package/dist/types/ts4.7/text/SidecarTextTrackController.d.ts +65 -53
- package/dist/types/ts4.7/text/SidecarTextTrackLoader.d.ts +5 -0
- package/dist/types/ts4.7/text/TextTrack.d.ts +74 -20
- package/dist/types/ts4.7/text/TextTrackRenderer.d.ts +24 -0
- package/dist/types/ts4.7/text/mediaTextTrackProvider.d.ts +34 -0
- package/dist/types/ts4.7/text/parseWebVtt.d.ts +16 -2
- package/dist/types/ts4.7/text/pickPreferredTextTrack.d.ts +2 -1
- package/dist/types/ts4.7/track/Track.d.ts +1 -1
- package/dist/types/ts4.7/track/dash/createDashFactories.d.ts +21 -10
- package/dist/types/ts4.7/track/hls/createHlsFactories.d.ts +21 -10
- package/dist/types/ts4.7/track/mse/MseTrack.d.ts +1 -2
- package/dist/types/ts4.7/vinyl/VinylOptions.d.ts +18 -40
- package/dist/types/ts4.7/vinyl/VinylPlayer.d.ts +0 -6
- package/dist/types/ts4.7/vinyl/createVinylFactories.d.ts +32 -18
- package/dist/unit/types/current/index.d.ts +2 -0
- package/dist/unit/types/current/index.d.ts.map +1 -1
- package/dist/unit/types/current/index.js +2 -0
- package/dist/unit/types/current/streaming/MseTrack.test.js +13 -7
- package/dist/unit/types/current/streaming/createDefaultMediaTimelineTransformer.test.js +8 -4
- package/dist/unit/types/current/text/HtmlTextTrackRenderer.test.d.ts +2 -0
- package/dist/unit/types/current/text/HtmlTextTrackRenderer.test.d.ts.map +1 -0
- package/dist/unit/types/current/text/HtmlTextTrackRenderer.test.js +231 -0
- package/dist/unit/types/current/text/SidecarTextTrackController.test.js +432 -333
- package/dist/unit/types/current/text/SidecarTextTrackLoader.test.js +29 -0
- package/dist/unit/types/current/text/mediaTextTrackProvider.test.d.ts +2 -0
- package/dist/unit/types/current/text/mediaTextTrackProvider.test.d.ts.map +1 -0
- package/dist/unit/types/current/text/mediaTextTrackProvider.test.js +86 -0
- package/dist/unit/types/current/text/parseWebVtt.test.js +187 -6
- package/dist/unit/types/current/text/pickPreferredTextTrack.test.js +12 -1
- package/dist/unit/types/current/track/TrackController.test.js +36 -0
- package/dist/unit/types/current/track/dash/createDashFactories.test.js +1 -2
- package/dist/unit/types/current/track/hls/buildHlsMediaTimeline.test.js +1 -2
- package/dist/unit/types/current/track/hls/createHlsFactories.test.js +1 -2
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/unit/types/current/vinyl/VinylPlayerTextTracks.test.js +102 -177
- package/dist/unit/types/current/vinyl/createVinylFactories.test.js +2 -0
- package/dist/vinylTestUtil/index.js +14 -8
- package/dist/vinylTestUtil/index.js.map +2 -2
- package/dist/vinylTestUtil/types/current/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
- package/dist/vinylTestUtil/types/current/mock/player/MockVinylPlayerDependencies.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/player/MockVinylPlayerDependencies.js +5 -2
- package/dist/vinylTestUtil/types/current/mock/text/MockTextTrackController.d.ts +3 -3
- package/dist/vinylTestUtil/types/current/mock/text/MockTextTrackController.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/text/MockTextTrackController.js +3 -3
- package/dist/vinylTestUtil/types/current/mock/track/dash/createMockDashDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/current/mock/track/dash/createMockDashDependencies.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/track/dash/createMockDashDependencies.js +3 -2
- package/dist/vinylTestUtil/types/current/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/current/mock/track/hls/createMockHlsDependencies.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/mock/track/hls/createMockHlsDependencies.js +3 -2
- package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/vinylTestUtil/types/ts3.7/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
- package/dist/vinylTestUtil/types/ts3.7/mock/text/MockTextTrackController.d.ts +3 -3
- package/dist/vinylTestUtil/types/ts3.7/mock/track/dash/createMockDashDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/ts3.7/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/ts4.1/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
- package/dist/vinylTestUtil/types/ts4.1/mock/text/MockTextTrackController.d.ts +3 -3
- package/dist/vinylTestUtil/types/ts4.1/mock/track/dash/createMockDashDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/ts4.1/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/ts4.7/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
- package/dist/vinylTestUtil/types/ts4.7/mock/text/MockTextTrackController.d.ts +3 -3
- package/dist/vinylTestUtil/types/ts4.7/mock/track/dash/createMockDashDependencies.d.ts +1 -2
- package/dist/vinylTestUtil/types/ts4.7/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
- package/docs/USAGE.md +82 -31
- package/package.json +9 -9
|
@@ -74,6 +74,35 @@ seg.vtt
|
|
|
74
74
|
await loadWebVttCues('https://x.test/sub.txt', options);
|
|
75
75
|
expect(batches.flat().map((c) => c.text)).toEqual(['from-segment']);
|
|
76
76
|
});
|
|
77
|
+
it('forwards STYLE blocks parsed from a segment via onStyles', async () => {
|
|
78
|
+
const playlistText = `#EXTM3U
|
|
79
|
+
#EXT-X-TARGETDURATION:10
|
|
80
|
+
#EXTINF:10.0,
|
|
81
|
+
seg.vtt
|
|
82
|
+
#EXT-X-ENDLIST
|
|
83
|
+
`;
|
|
84
|
+
const segmentText = `WEBVTT
|
|
85
|
+
|
|
86
|
+
STYLE
|
|
87
|
+
::cue { color: red }
|
|
88
|
+
|
|
89
|
+
00:00:01.000 --> 00:00:02.000
|
|
90
|
+
from-segment`;
|
|
91
|
+
let call = 0;
|
|
92
|
+
requester.request.and.callFake(() => {
|
|
93
|
+
call++;
|
|
94
|
+
if (call === 1) {
|
|
95
|
+
return respondText(playlistText, 'application/vnd.apple.mpegurl', 'https://x.test/sub.m3u8');
|
|
96
|
+
}
|
|
97
|
+
return respondText(segmentText, 'text/vtt');
|
|
98
|
+
});
|
|
99
|
+
const styleBatches = [];
|
|
100
|
+
const { options } = streamingOpts({
|
|
101
|
+
onStyles: (styles) => styleBatches.push([...styles]),
|
|
102
|
+
});
|
|
103
|
+
await loadWebVttCues('https://x.test/sub.m3u8', options);
|
|
104
|
+
expect(styleBatches).toEqual([['::cue { color: red }']]);
|
|
105
|
+
});
|
|
77
106
|
it('falls back to the request URI when response.url is empty (body sniffed)', async () => {
|
|
78
107
|
const playlistText = `#EXTM3U
|
|
79
108
|
#EXT-X-VERSION:3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mediaTextTrackProvider.test.d.ts","sourceRoot":"","sources":["../../../../../test/unit/text/mediaTextTrackProvider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { createMediaTextTrackProvider } from '@amazon/vinyl';
|
|
6
|
+
/** Minimal media element: a live `textTracks` list and a spied `addTextTrack`. */
|
|
7
|
+
class FakeMediaElement {
|
|
8
|
+
tracks = [];
|
|
9
|
+
addTextTrack = jasmine
|
|
10
|
+
.createSpy('addTextTrack')
|
|
11
|
+
.and.callFake((kind, label, language) => {
|
|
12
|
+
const track = {
|
|
13
|
+
kind,
|
|
14
|
+
label,
|
|
15
|
+
language,
|
|
16
|
+
mode: 'disabled',
|
|
17
|
+
cues: [],
|
|
18
|
+
addCue(cue) {
|
|
19
|
+
this.cues.push(cue);
|
|
20
|
+
},
|
|
21
|
+
removeCue(cue) {
|
|
22
|
+
const i = this.cues.indexOf(cue);
|
|
23
|
+
if (i >= 0)
|
|
24
|
+
this.cues.splice(i, 1);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
this.tracks.push(track);
|
|
28
|
+
return track;
|
|
29
|
+
});
|
|
30
|
+
// ArrayLike view over the live tracks (what `isAttached` scans).
|
|
31
|
+
get textTracks() {
|
|
32
|
+
return this.tracks;
|
|
33
|
+
}
|
|
34
|
+
asMedia() {
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
cue() {
|
|
38
|
+
return { id: '' };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
describe('createMediaTextTrackProvider', () => {
|
|
42
|
+
let media;
|
|
43
|
+
let provider;
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
media = new FakeMediaElement();
|
|
46
|
+
provider = createMediaTextTrackProvider({ media: media.asMedia() });
|
|
47
|
+
});
|
|
48
|
+
it('creates a track from the given kind/label/language', () => {
|
|
49
|
+
const ref = provider.getOrCreate('subtitles', 'English', 'en');
|
|
50
|
+
expect(media.addTextTrack).toHaveBeenCalledOnceWith('subtitles', 'English', 'en');
|
|
51
|
+
expect(media.tracks).toContain(ref.track);
|
|
52
|
+
});
|
|
53
|
+
it('memoizes by (kind,label,language) — same key returns the same track', () => {
|
|
54
|
+
const a = provider.getOrCreate('subtitles', 'English', 'en');
|
|
55
|
+
const b = provider.getOrCreate('subtitles', 'English', 'en');
|
|
56
|
+
expect(b).toBe(a);
|
|
57
|
+
expect(media.addTextTrack).toHaveBeenCalledTimes(1);
|
|
58
|
+
});
|
|
59
|
+
it('creates a distinct track per key', () => {
|
|
60
|
+
provider.getOrCreate('subtitles', 'English', 'en');
|
|
61
|
+
provider.getOrCreate('subtitles', 'Espanol', 'es');
|
|
62
|
+
expect(media.addTextTrack).toHaveBeenCalledTimes(2);
|
|
63
|
+
});
|
|
64
|
+
it('recreates a track that was detached from the media element', () => {
|
|
65
|
+
const first = provider.getOrCreate('subtitles', 'English', 'en');
|
|
66
|
+
// Simulate a media-source reset dropping added tracks.
|
|
67
|
+
media.tracks.length = 0;
|
|
68
|
+
const second = provider.getOrCreate('subtitles', 'English', 'en');
|
|
69
|
+
expect(second).not.toBe(first);
|
|
70
|
+
expect(media.addTextTrack).toHaveBeenCalledTimes(2);
|
|
71
|
+
expect(media.tracks).toContain(second.track);
|
|
72
|
+
});
|
|
73
|
+
it('clear() removes all cues from the track', () => {
|
|
74
|
+
const ref = provider.getOrCreate('subtitles', 'English', 'en');
|
|
75
|
+
ref.track.addCue(media.cue());
|
|
76
|
+
ref.track.addCue(media.cue());
|
|
77
|
+
expect(ref.track.cues?.length).toBe(2);
|
|
78
|
+
ref.clear();
|
|
79
|
+
expect(ref.track.cues?.length).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
it('clear() is a no-op when the track has no cues (cues null)', () => {
|
|
82
|
+
const ref = provider.getOrCreate('subtitles', 'English', 'en');
|
|
83
|
+
ref.track.cues = null;
|
|
84
|
+
expect(() => ref.clear()).not.toThrow();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -108,15 +108,30 @@ ok`;
|
|
|
108
108
|
const doc = parseWebVtt(text);
|
|
109
109
|
expect(doc.cues.length).toBe(1);
|
|
110
110
|
});
|
|
111
|
-
it('
|
|
111
|
+
it('captures STYLE block CSS while still parsing cues', () => {
|
|
112
112
|
const text = `WEBVTT
|
|
113
113
|
|
|
114
114
|
STYLE
|
|
115
|
-
::cue {
|
|
115
|
+
::cue(.loud) { font-weight: bold }
|
|
116
|
+
|
|
117
|
+
STYLE
|
|
118
|
+
::cue { color: red }
|
|
116
119
|
|
|
117
120
|
00:00:01.000 --> 00:00:02.000
|
|
118
121
|
styled`;
|
|
119
|
-
|
|
122
|
+
const doc = parseWebVtt(text);
|
|
123
|
+
expect(doc.cues.length).toBe(1);
|
|
124
|
+
expect(doc.styles).toEqual([
|
|
125
|
+
'::cue(.loud) { font-weight: bold }',
|
|
126
|
+
'::cue { color: red }',
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
it('reports no styles when there are no STYLE blocks', () => {
|
|
130
|
+
const text = `WEBVTT
|
|
131
|
+
|
|
132
|
+
00:00:01.000 --> 00:00:02.000
|
|
133
|
+
x`;
|
|
134
|
+
expect(parseWebVtt(text).styles).toEqual([]);
|
|
120
135
|
});
|
|
121
136
|
it('skips REGION blocks', () => {
|
|
122
137
|
const text = `WEBVTT
|
|
@@ -127,14 +142,98 @@ width:40%
|
|
|
127
142
|
|
|
128
143
|
00:00:01.000 --> 00:00:02.000
|
|
129
144
|
regional`;
|
|
130
|
-
|
|
145
|
+
const doc = parseWebVtt(text);
|
|
146
|
+
expect(doc.cues.length).toBe(1);
|
|
147
|
+
expect(doc.styles).toEqual([]);
|
|
131
148
|
});
|
|
132
|
-
it('
|
|
149
|
+
it('parses cue settings on the timing line', () => {
|
|
133
150
|
const text = `WEBVTT
|
|
134
151
|
|
|
135
152
|
00:00:01.000 --> 00:00:02.000 line:50% align:start
|
|
136
153
|
positioned`;
|
|
137
|
-
|
|
154
|
+
const cue = parseWebVtt(text).cues[0];
|
|
155
|
+
expect(cue.text).toBe('positioned');
|
|
156
|
+
expect(cue.settings).toEqual({
|
|
157
|
+
line: 50,
|
|
158
|
+
snapToLines: false,
|
|
159
|
+
align: 'start',
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
it('parses integer/negative lines as snap-to-lines, with lineAlign', () => {
|
|
163
|
+
const text = `WEBVTT
|
|
164
|
+
|
|
165
|
+
00:00:01.000 --> 00:00:02.000 line:-3,end
|
|
166
|
+
a
|
|
167
|
+
|
|
168
|
+
00:00:03.000 --> 00:00:04.000 line:5
|
|
169
|
+
b`;
|
|
170
|
+
const [a, b] = parseWebVtt(text).cues;
|
|
171
|
+
expect(a.settings).toEqual({
|
|
172
|
+
line: -3,
|
|
173
|
+
snapToLines: true,
|
|
174
|
+
lineAlign: 'end',
|
|
175
|
+
});
|
|
176
|
+
expect(b.settings).toEqual({ line: 5, snapToLines: true });
|
|
177
|
+
});
|
|
178
|
+
it("maps the legacy 'middle' keyword to 'center'", () => {
|
|
179
|
+
const text = `WEBVTT
|
|
180
|
+
|
|
181
|
+
00:00:01.000 --> 00:00:02.000 align:middle line:85%,middle position:50%,middle
|
|
182
|
+
a`;
|
|
183
|
+
expect(parseWebVtt(text).cues[0].settings).toEqual({
|
|
184
|
+
align: 'center',
|
|
185
|
+
line: 85,
|
|
186
|
+
snapToLines: false,
|
|
187
|
+
lineAlign: 'center',
|
|
188
|
+
position: 50,
|
|
189
|
+
positionAlign: 'center',
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
it('parses position (with align), size and vertical', () => {
|
|
193
|
+
const text = `WEBVTT
|
|
194
|
+
|
|
195
|
+
00:00:01.000 --> 00:00:02.000 position:20%,line-left size:80% vertical:rl
|
|
196
|
+
a`;
|
|
197
|
+
expect(parseWebVtt(text).cues[0].settings).toEqual({
|
|
198
|
+
position: 20,
|
|
199
|
+
positionAlign: 'line-left',
|
|
200
|
+
size: 80,
|
|
201
|
+
vertical: 'rl',
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
it('omits settings and skips malformed/unknown tokens', () => {
|
|
205
|
+
const text = `WEBVTT
|
|
206
|
+
|
|
207
|
+
00:00:01.000 --> 00:00:02.000
|
|
208
|
+
plain
|
|
209
|
+
|
|
210
|
+
00:00:03.000 --> 00:00:04.000 align:bogus size:oops region:foo bareword
|
|
211
|
+
b`;
|
|
212
|
+
const [plain, b] = parseWebVtt(text).cues;
|
|
213
|
+
expect(plain.settings).toBeUndefined();
|
|
214
|
+
// align invalid, size not a %, unknown key, and a colon-less token.
|
|
215
|
+
expect(b.settings).toBeUndefined();
|
|
216
|
+
});
|
|
217
|
+
it('skips malformed line/position/percent values and invalid aligns', () => {
|
|
218
|
+
const text = `WEBVTT
|
|
219
|
+
|
|
220
|
+
00:00:01.000 --> 00:00:02.000 line:x% size:y%
|
|
221
|
+
a
|
|
222
|
+
|
|
223
|
+
00:00:03.000 --> 00:00:04.000 line:1.5 position:bad
|
|
224
|
+
b
|
|
225
|
+
|
|
226
|
+
00:00:05.000 --> 00:00:06.000 line:5,bogus position:20%,nope
|
|
227
|
+
c`;
|
|
228
|
+
const [a, b, c] = parseWebVtt(text).cues;
|
|
229
|
+
expect(a.settings).toBeUndefined(); // non-numeric percentages → dropped
|
|
230
|
+
expect(b.settings).toBeUndefined(); // non-integer line, non-% position
|
|
231
|
+
// Valid values kept; invalid *Align keywords dropped.
|
|
232
|
+
expect(c.settings).toEqual({
|
|
233
|
+
line: 5,
|
|
234
|
+
snapToLines: true,
|
|
235
|
+
position: 20,
|
|
236
|
+
});
|
|
138
237
|
});
|
|
139
238
|
it('skips a malformed cue and continues', () => {
|
|
140
239
|
const text = `WEBVTT
|
|
@@ -149,6 +248,88 @@ good`;
|
|
|
149
248
|
expect(cues.length).toBe(1);
|
|
150
249
|
expect(cues[0].text).toBe('good');
|
|
151
250
|
});
|
|
251
|
+
it('skips a line with --> but an invalid start timestamp', () => {
|
|
252
|
+
const text = `WEBVTT
|
|
253
|
+
|
|
254
|
+
BForced --> 00:00:02.000
|
|
255
|
+
skipped
|
|
256
|
+
|
|
257
|
+
00:00:02.000 --> 00:00:03.000
|
|
258
|
+
good`;
|
|
259
|
+
const cues = parseWebVtt(text).cues;
|
|
260
|
+
expect(cues.length).toBe(1);
|
|
261
|
+
expect(cues[0].text).toBe('good');
|
|
262
|
+
});
|
|
263
|
+
it('ignores unparseable text glued after the end timestamp', () => {
|
|
264
|
+
// Per the spec, timestamp collection stops after the milliseconds; any
|
|
265
|
+
// trailing text is parsed as (here, invalid, ignored) cue settings.
|
|
266
|
+
const cue = parseWebVtt('WEBVTT\n\n00:00:01.000 --> 00:00:02.000x\ntext').cues[0];
|
|
267
|
+
expect(cue.endTime).toBe(2);
|
|
268
|
+
expect(cue.settings).toBeUndefined();
|
|
269
|
+
expect(cue.text).toBe('text');
|
|
270
|
+
});
|
|
271
|
+
it('parses multi-digit hours (spec allows two or more)', () => {
|
|
272
|
+
const cues = parseWebVtt('WEBVTT\n\n100:00:01.000 --> 100:00:02.500\nhi').cues;
|
|
273
|
+
expect(cues.length).toBe(1);
|
|
274
|
+
expect(cues[0].startTime).toBe(360001);
|
|
275
|
+
expect(cues[0].endTime).toBe(360002.5);
|
|
276
|
+
});
|
|
277
|
+
it('treats consecutive blank lines between cues as separators', () => {
|
|
278
|
+
const text = 'WEBVTT\n\n00:00:01.000 --> 00:00:02.000\none\n\n\n' +
|
|
279
|
+
'00:00:03.000 --> 00:00:04.000\ntwo';
|
|
280
|
+
expect(parseWebVtt(text).cues.map((c) => c.text)).toEqual([
|
|
281
|
+
'one',
|
|
282
|
+
'two',
|
|
283
|
+
]);
|
|
284
|
+
});
|
|
285
|
+
// Each line exercises a distinct timestamp-parser rejection branch; all are
|
|
286
|
+
// treated as a (dropped) identifier so only the trailing valid cue survives.
|
|
287
|
+
for (const timing of [
|
|
288
|
+
'12 --> 00:00:02.000', // two digits then no ':'
|
|
289
|
+
'7 --> 00:00:02.000', // one digit then no second digit
|
|
290
|
+
'00:00:01.000 00:00:02.000', // no --> ⇒ treated as an identifier
|
|
291
|
+
'5:30.000 --> 00:00:02.000', // MM:SS minutes not two digits
|
|
292
|
+
'75:30.000 --> 00:00:02.000', // MM:SS minutes > 59
|
|
293
|
+
'12:34 --> 00:00:02.000', // seconds not terminated by '.'
|
|
294
|
+
'00:99:00.000 --> 00:00:02.000', // HH:MM:SS minutes > 59
|
|
295
|
+
'00:00:99.000 --> 00:00:02.000', // HH:MM:SS seconds > 59
|
|
296
|
+
'00:00:0.400 --> 00:00:02.000', // HH:MM:SS seconds not two digits
|
|
297
|
+
'12:3:45.000 --> 00:00:02.000', // minutes not two digits before ':'
|
|
298
|
+
'00:00:00 --> 00:00:02.000', // HH:MM:SS missing .mmm
|
|
299
|
+
'00:00:00.00 --> 00:00:02.000', // milliseconds only two digits
|
|
300
|
+
'00:00.0 --> 00:00:02.000', // milliseconds only one digit
|
|
301
|
+
'00:00. --> 00:00:02.000', // milliseconds missing
|
|
302
|
+
'12:ab --> 00:00:02.000', // non-digit minute/second
|
|
303
|
+
'00:00:01.0', // truncated timestamp at end of input
|
|
304
|
+
]) {
|
|
305
|
+
it(`skips a line with an invalid timestamp: "${timing}"`, () => {
|
|
306
|
+
const text = `WEBVTT\n\n${timing}\nfiller\n\n00:00:05.000 --> 00:00:06.000\nok`;
|
|
307
|
+
expect(parseWebVtt(text).cues.map((c) => c.text)).toEqual(['ok']);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// Keyword prefixes (NOTES/REGIONAL/STYLES) are ordinary cue identifiers, not
|
|
311
|
+
// NOTE/REGION/STYLE blocks — this also exercises each keyword's fall-through.
|
|
312
|
+
for (const identifier of ['NOTES', 'REGIONAL', 'STYLES']) {
|
|
313
|
+
it(`treats keyword prefix "${identifier}" as an identifier`, () => {
|
|
314
|
+
const cues = parseWebVtt(`WEBVTT\n\n${identifier}\n00:00:01.000 --> 00:00:02.000\nhi`).cues;
|
|
315
|
+
expect(cues.length).toBe(1);
|
|
316
|
+
expect(cues[0].id).toBe(identifier);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
it('normalizes CRLF line endings and splits CRLF-separated cues', () => {
|
|
320
|
+
const text = 'WEBVTT\r\n\r\n00:00:01.000 --> 00:00:02.000\r\nline one\r\nline two' +
|
|
321
|
+
'\r\n\r\n00:00:03.000 --> 00:00:04.000\r\nsecond';
|
|
322
|
+
expect(parseWebVtt(text).cues.map((c) => c.text)).toEqual([
|
|
323
|
+
'line one\nline two',
|
|
324
|
+
'second',
|
|
325
|
+
]);
|
|
326
|
+
});
|
|
327
|
+
it('accepts a NOTE block at end of input', () => {
|
|
328
|
+
expect(parseWebVtt('WEBVTT\n\nNOTE').cues).toEqual([]);
|
|
329
|
+
});
|
|
330
|
+
it('handles input that ends mid-timestamp', () => {
|
|
331
|
+
expect(parseWebVtt('WEBVTT\n\n00:00:00.000 --> 00:00:0').cues).toEqual([]);
|
|
332
|
+
});
|
|
152
333
|
it('returns empty cues on header-only input', () => {
|
|
153
334
|
expect(parseWebVtt('WEBVTT\n').cues).toEqual([]);
|
|
154
335
|
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { pickPreferredTextTrack } from '@amazon/vinyl';
|
|
6
|
+
import { setMockNavigator, spyOnPropertySafe, } from '@amazon/vinyl-util/testUtil';
|
|
6
7
|
let nextId = 0;
|
|
7
8
|
function track(overrides = {}) {
|
|
8
9
|
return {
|
|
@@ -19,7 +20,17 @@ function track(overrides = {}) {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
describe('pickPreferredTextTrack', () => {
|
|
22
|
-
it('
|
|
23
|
+
it('falls back to navigator.languages when the preference is null', () => {
|
|
24
|
+
setMockNavigator().languages = ['en'];
|
|
25
|
+
const en = track({ id: 'en', language: 'en' });
|
|
26
|
+
expect(pickPreferredTextTrack([en], null)).toBe(en);
|
|
27
|
+
});
|
|
28
|
+
it('returns null when no track relates to navigator.languages', () => {
|
|
29
|
+
setMockNavigator().languages = ['en'];
|
|
30
|
+
expect(pickPreferredTextTrack([track({ language: 'ja' })], null)).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it('returns null for a null preference when navigator is unavailable', () => {
|
|
33
|
+
spyOnPropertySafe(global, 'navigator').and.returnValue(undefined);
|
|
23
34
|
expect(pickPreferredTextTrack([track()], null)).toBeNull();
|
|
24
35
|
});
|
|
25
36
|
it('returns null when there are no tracks', () => {
|
|
@@ -1308,6 +1308,42 @@ describe('TrackControllerImpl', () => {
|
|
|
1308
1308
|
expect(trackActivated).toHaveBeenCalledWith(objectContaining({ track: objectContaining({ uri: main.uri }) }));
|
|
1309
1309
|
expect(trackActivated).toHaveBeenCalledBefore(queueEnded);
|
|
1310
1310
|
});
|
|
1311
|
+
it('ends the track after an empty postroll where no ad ever began', async () => {
|
|
1312
|
+
// An empty postroll (a break that resolves to no ads) completes
|
|
1313
|
+
// without any adEntered, so the content track is never deactivated
|
|
1314
|
+
// and stays active. onEnded has already deferred the trackEnded to
|
|
1315
|
+
// adBreakCompleted, so this must still end the track and the queue
|
|
1316
|
+
// rather than hang.
|
|
1317
|
+
const [main] = createLoadOptionsList(1);
|
|
1318
|
+
trackController.load(main);
|
|
1319
|
+
await clock.tick(); // content activates (stays active — no ad plays)
|
|
1320
|
+
expect(trackController.activeTrack.active).toBeTrue();
|
|
1321
|
+
const trackEnded = createEventSpy(trackController, 'trackEnded');
|
|
1322
|
+
const queueEnded = createEventSpy(trackController, 'queueEnded');
|
|
1323
|
+
deps.adController.dispatch('adBreakCompleted', {
|
|
1324
|
+
adBreak: adBreak('postroll'),
|
|
1325
|
+
resumePosition: 0,
|
|
1326
|
+
});
|
|
1327
|
+
await clock.tick();
|
|
1328
|
+
expect(trackEnded).toHaveBeenCalledWith(objectContaining({
|
|
1329
|
+
track: objectContaining({ uri: main.uri }),
|
|
1330
|
+
}));
|
|
1331
|
+
expect(queueEnded).toHaveBeenCalled();
|
|
1332
|
+
});
|
|
1333
|
+
it('advances to the next track after an empty postroll where no ad ever began', async () => {
|
|
1334
|
+
const list = createLoadOptionsList(2);
|
|
1335
|
+
trackController.load(...list);
|
|
1336
|
+
await clock.tick(); // content activates and stays active
|
|
1337
|
+
expect(trackController.activeTrack.active).toBeTrue();
|
|
1338
|
+
const trackEnded = createEventSpy(trackController, 'trackEnded');
|
|
1339
|
+
deps.adController.dispatch('adBreakCompleted', {
|
|
1340
|
+
adBreak: adBreak('postroll'),
|
|
1341
|
+
resumePosition: 0,
|
|
1342
|
+
});
|
|
1343
|
+
await clock.tick();
|
|
1344
|
+
expect(trackEnded).toHaveBeenCalledWith(objectContaining({ track: any(Object) }));
|
|
1345
|
+
expect(trackController.activeTrack?.uri).toBe(list[1].uri);
|
|
1346
|
+
});
|
|
1311
1347
|
it('does not treat a completed midroll ad as the track ending', async () => {
|
|
1312
1348
|
trackController.load(...createLoadOptionsList(1));
|
|
1313
1349
|
const trackEnded = createEventSpy(trackController, 'trackEnded');
|
|
@@ -69,9 +69,8 @@ describe('createDashFactories', () => {
|
|
|
69
69
|
});
|
|
70
70
|
const deps = createContainer(factories).dependencies;
|
|
71
71
|
const options = dashFactoryDeps.options.value;
|
|
72
|
-
expect(deps.
|
|
72
|
+
expect(deps.audio.value).toBe(options.audio);
|
|
73
73
|
expect(deps.allowedContentTypes.value).toBe(options.allowedContentTypes);
|
|
74
|
-
expect(deps.preferDescriptiveAudio.value).toBe(options.preferDescriptiveAudio);
|
|
75
74
|
expect(deps.abr.value).toBe(options.abr);
|
|
76
75
|
});
|
|
77
76
|
it('provides streaming options to createContentStreamFactories', () => {
|
|
@@ -403,9 +403,8 @@ describe('buildHlsMediaTimeline', () => {
|
|
|
403
403
|
drmController,
|
|
404
404
|
mediaTimeline: data(Promise.resolve(timeline)),
|
|
405
405
|
options: data({
|
|
406
|
-
|
|
406
|
+
audio: { selection: { language: null, descriptive: false } },
|
|
407
407
|
codecOverrides: {},
|
|
408
|
-
preferDescriptiveAudio: false,
|
|
409
408
|
}),
|
|
410
409
|
}).value;
|
|
411
410
|
const audio = result.periods[0].qualities.filter((q) => q.metadata.contentType === 'audio');
|
|
@@ -57,9 +57,8 @@ describe('createHlsFactories', () => {
|
|
|
57
57
|
});
|
|
58
58
|
const deps = createContainer(factories).dependencies;
|
|
59
59
|
const options = hlsFactoryDeps.options.value;
|
|
60
|
-
expect(deps.
|
|
60
|
+
expect(deps.audio.value).toBe(options.audio);
|
|
61
61
|
expect(deps.allowedContentTypes.value).toBe(options.allowedContentTypes);
|
|
62
|
-
expect(deps.preferDescriptiveAudio.value).toBe(options.preferDescriptiveAudio);
|
|
63
62
|
expect(deps.abr.value).toBe(options.abr);
|
|
64
63
|
});
|
|
65
64
|
it('exposes the shared adController without owning it', () => {
|