@amazon/vinyl 2.0.0 → 3.0.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.
Files changed (167) hide show
  1. package/dist/index.cjs +603 -317
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +622 -329
  4. package/dist/index.js.map +4 -4
  5. package/dist/test/benchmark.es5.js +8 -5
  6. package/dist/test/benchmark.js +228 -0
  7. package/dist/test/benchmark.js.map +4 -4
  8. package/dist/test/integ.es5.js +182 -156
  9. package/dist/test/integ.js +1853 -796
  10. package/dist/test/integ.js.map +4 -4
  11. package/dist/types/current/index.d.ts +4 -0
  12. package/dist/types/current/index.d.ts.map +1 -1
  13. package/dist/types/current/index.js +4 -0
  14. package/dist/types/current/streaming/AudioOptions.d.ts +39 -0
  15. package/dist/types/current/streaming/AudioOptions.d.ts.map +1 -0
  16. package/dist/types/current/streaming/AudioOptions.js +15 -0
  17. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
  18. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.d.ts.map +1 -1
  19. package/dist/types/current/streaming/createDefaultMediaTimelineTransformer.js +5 -5
  20. package/dist/types/current/text/HtmlTextTrackRenderer.d.ts +60 -0
  21. package/dist/types/current/text/HtmlTextTrackRenderer.d.ts.map +1 -0
  22. package/dist/types/current/text/HtmlTextTrackRenderer.js +231 -0
  23. package/dist/types/current/text/SidecarTextTrackController.d.ts +65 -53
  24. package/dist/types/current/text/SidecarTextTrackController.d.ts.map +1 -1
  25. package/dist/types/current/text/SidecarTextTrackController.js +167 -154
  26. package/dist/types/current/text/SidecarTextTrackLoader.d.ts +5 -0
  27. package/dist/types/current/text/SidecarTextTrackLoader.d.ts.map +1 -1
  28. package/dist/types/current/text/SidecarTextTrackLoader.js +13 -7
  29. package/dist/types/current/text/TextTrack.d.ts +74 -20
  30. package/dist/types/current/text/TextTrack.d.ts.map +1 -1
  31. package/dist/types/current/text/TextTrack.js +20 -0
  32. package/dist/types/current/text/TextTrackRenderer.d.ts +24 -0
  33. package/dist/types/current/text/TextTrackRenderer.d.ts.map +1 -0
  34. package/dist/types/current/text/TextTrackRenderer.js +5 -0
  35. package/dist/types/current/text/mediaTextTrackProvider.d.ts +34 -0
  36. package/dist/types/current/text/mediaTextTrackProvider.d.ts.map +1 -0
  37. package/dist/types/current/text/mediaTextTrackProvider.js +42 -0
  38. package/dist/types/current/text/parseWebVtt.d.ts +16 -2
  39. package/dist/types/current/text/parseWebVtt.d.ts.map +1 -1
  40. package/dist/types/current/text/parseWebVtt.js +176 -48
  41. package/dist/types/current/text/pickPreferredTextTrack.d.ts +2 -1
  42. package/dist/types/current/text/pickPreferredTextTrack.d.ts.map +1 -1
  43. package/dist/types/current/text/pickPreferredTextTrack.js +6 -3
  44. package/dist/types/current/track/Track.d.ts +1 -1
  45. package/dist/types/current/track/dash/createDashFactories.d.ts +21 -10
  46. package/dist/types/current/track/dash/createDashFactories.d.ts.map +1 -1
  47. package/dist/types/current/track/dash/createDashFactories.js +9 -21
  48. package/dist/types/current/track/hls/createHlsFactories.d.ts +21 -10
  49. package/dist/types/current/track/hls/createHlsFactories.d.ts.map +1 -1
  50. package/dist/types/current/track/hls/createHlsFactories.js +9 -21
  51. package/dist/types/current/track/mse/MseTrack.d.ts +1 -2
  52. package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
  53. package/dist/types/current/track/mse/MseTrack.js +3 -7
  54. package/dist/types/current/tsconfig.tsbuildinfo +1 -1
  55. package/dist/types/current/vinyl/VinylOptions.d.ts +18 -40
  56. package/dist/types/current/vinyl/VinylOptions.d.ts.map +1 -1
  57. package/dist/types/current/vinyl/VinylOptions.js +12 -11
  58. package/dist/types/current/vinyl/VinylPlayer.d.ts +0 -6
  59. package/dist/types/current/vinyl/VinylPlayer.d.ts.map +1 -1
  60. package/dist/types/current/vinyl/VinylPlayer.js +2 -23
  61. package/dist/types/current/vinyl/createVinylFactories.d.ts +32 -18
  62. package/dist/types/current/vinyl/createVinylFactories.d.ts.map +1 -1
  63. package/dist/types/current/vinyl/createVinylFactories.js +6 -0
  64. package/dist/types/ts3.7/index.d.ts +4 -0
  65. package/dist/types/ts3.7/streaming/AudioOptions.d.ts +39 -0
  66. package/dist/types/ts3.7/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
  67. package/dist/types/ts3.7/text/HtmlTextTrackRenderer.d.ts +60 -0
  68. package/dist/types/ts3.7/text/SidecarTextTrackController.d.ts +65 -53
  69. package/dist/types/ts3.7/text/SidecarTextTrackLoader.d.ts +5 -0
  70. package/dist/types/ts3.7/text/TextTrack.d.ts +74 -20
  71. package/dist/types/ts3.7/text/TextTrackRenderer.d.ts +24 -0
  72. package/dist/types/ts3.7/text/mediaTextTrackProvider.d.ts +34 -0
  73. package/dist/types/ts3.7/text/parseWebVtt.d.ts +16 -2
  74. package/dist/types/ts3.7/text/pickPreferredTextTrack.d.ts +2 -1
  75. package/dist/types/ts3.7/track/Track.d.ts +1 -1
  76. package/dist/types/ts3.7/track/dash/createDashFactories.d.ts +21 -10
  77. package/dist/types/ts3.7/track/hls/createHlsFactories.d.ts +21 -10
  78. package/dist/types/ts3.7/track/mse/MseTrack.d.ts +1 -2
  79. package/dist/types/ts3.7/vinyl/VinylOptions.d.ts +18 -40
  80. package/dist/types/ts3.7/vinyl/VinylPlayer.d.ts +0 -6
  81. package/dist/types/ts3.7/vinyl/createVinylFactories.d.ts +32 -18
  82. package/dist/types/ts4.1/index.d.ts +4 -0
  83. package/dist/types/ts4.1/streaming/AudioOptions.d.ts +39 -0
  84. package/dist/types/ts4.1/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
  85. package/dist/types/ts4.1/text/HtmlTextTrackRenderer.d.ts +60 -0
  86. package/dist/types/ts4.1/text/SidecarTextTrackController.d.ts +65 -53
  87. package/dist/types/ts4.1/text/SidecarTextTrackLoader.d.ts +5 -0
  88. package/dist/types/ts4.1/text/TextTrack.d.ts +74 -20
  89. package/dist/types/ts4.1/text/TextTrackRenderer.d.ts +24 -0
  90. package/dist/types/ts4.1/text/mediaTextTrackProvider.d.ts +34 -0
  91. package/dist/types/ts4.1/text/parseWebVtt.d.ts +16 -2
  92. package/dist/types/ts4.1/text/pickPreferredTextTrack.d.ts +2 -1
  93. package/dist/types/ts4.1/track/Track.d.ts +1 -1
  94. package/dist/types/ts4.1/track/dash/createDashFactories.d.ts +21 -10
  95. package/dist/types/ts4.1/track/hls/createHlsFactories.d.ts +21 -10
  96. package/dist/types/ts4.1/track/mse/MseTrack.d.ts +1 -2
  97. package/dist/types/ts4.1/vinyl/VinylOptions.d.ts +18 -40
  98. package/dist/types/ts4.1/vinyl/VinylPlayer.d.ts +0 -6
  99. package/dist/types/ts4.1/vinyl/createVinylFactories.d.ts +32 -18
  100. package/dist/types/ts4.7/index.d.ts +4 -0
  101. package/dist/types/ts4.7/streaming/AudioOptions.d.ts +39 -0
  102. package/dist/types/ts4.7/streaming/createDefaultMediaTimelineTransformer.d.ts +2 -2
  103. package/dist/types/ts4.7/text/HtmlTextTrackRenderer.d.ts +60 -0
  104. package/dist/types/ts4.7/text/SidecarTextTrackController.d.ts +65 -53
  105. package/dist/types/ts4.7/text/SidecarTextTrackLoader.d.ts +5 -0
  106. package/dist/types/ts4.7/text/TextTrack.d.ts +74 -20
  107. package/dist/types/ts4.7/text/TextTrackRenderer.d.ts +24 -0
  108. package/dist/types/ts4.7/text/mediaTextTrackProvider.d.ts +34 -0
  109. package/dist/types/ts4.7/text/parseWebVtt.d.ts +16 -2
  110. package/dist/types/ts4.7/text/pickPreferredTextTrack.d.ts +2 -1
  111. package/dist/types/ts4.7/track/Track.d.ts +1 -1
  112. package/dist/types/ts4.7/track/dash/createDashFactories.d.ts +21 -10
  113. package/dist/types/ts4.7/track/hls/createHlsFactories.d.ts +21 -10
  114. package/dist/types/ts4.7/track/mse/MseTrack.d.ts +1 -2
  115. package/dist/types/ts4.7/vinyl/VinylOptions.d.ts +18 -40
  116. package/dist/types/ts4.7/vinyl/VinylPlayer.d.ts +0 -6
  117. package/dist/types/ts4.7/vinyl/createVinylFactories.d.ts +32 -18
  118. package/dist/unit/types/current/index.d.ts +2 -0
  119. package/dist/unit/types/current/index.d.ts.map +1 -1
  120. package/dist/unit/types/current/index.js +2 -0
  121. package/dist/unit/types/current/streaming/MseTrack.test.js +13 -7
  122. package/dist/unit/types/current/streaming/createDefaultMediaTimelineTransformer.test.js +8 -4
  123. package/dist/unit/types/current/text/HtmlTextTrackRenderer.test.d.ts +2 -0
  124. package/dist/unit/types/current/text/HtmlTextTrackRenderer.test.d.ts.map +1 -0
  125. package/dist/unit/types/current/text/HtmlTextTrackRenderer.test.js +231 -0
  126. package/dist/unit/types/current/text/SidecarTextTrackController.test.js +432 -333
  127. package/dist/unit/types/current/text/SidecarTextTrackLoader.test.js +29 -0
  128. package/dist/unit/types/current/text/mediaTextTrackProvider.test.d.ts +2 -0
  129. package/dist/unit/types/current/text/mediaTextTrackProvider.test.d.ts.map +1 -0
  130. package/dist/unit/types/current/text/mediaTextTrackProvider.test.js +86 -0
  131. package/dist/unit/types/current/text/parseWebVtt.test.js +187 -6
  132. package/dist/unit/types/current/text/pickPreferredTextTrack.test.js +12 -1
  133. package/dist/unit/types/current/track/dash/createDashFactories.test.js +1 -2
  134. package/dist/unit/types/current/track/hls/buildHlsMediaTimeline.test.js +1 -2
  135. package/dist/unit/types/current/track/hls/createHlsFactories.test.js +1 -2
  136. package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
  137. package/dist/unit/types/current/vinyl/VinylPlayerTextTracks.test.js +102 -177
  138. package/dist/unit/types/current/vinyl/createVinylFactories.test.js +2 -0
  139. package/dist/vinylTestUtil/index.js +14 -8
  140. package/dist/vinylTestUtil/index.js.map +2 -2
  141. package/dist/vinylTestUtil/types/current/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
  142. package/dist/vinylTestUtil/types/current/mock/player/MockVinylPlayerDependencies.d.ts.map +1 -1
  143. package/dist/vinylTestUtil/types/current/mock/player/MockVinylPlayerDependencies.js +5 -2
  144. package/dist/vinylTestUtil/types/current/mock/text/MockTextTrackController.d.ts +3 -3
  145. package/dist/vinylTestUtil/types/current/mock/text/MockTextTrackController.d.ts.map +1 -1
  146. package/dist/vinylTestUtil/types/current/mock/text/MockTextTrackController.js +3 -3
  147. package/dist/vinylTestUtil/types/current/mock/track/dash/createMockDashDependencies.d.ts +1 -2
  148. package/dist/vinylTestUtil/types/current/mock/track/dash/createMockDashDependencies.d.ts.map +1 -1
  149. package/dist/vinylTestUtil/types/current/mock/track/dash/createMockDashDependencies.js +3 -2
  150. package/dist/vinylTestUtil/types/current/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
  151. package/dist/vinylTestUtil/types/current/mock/track/hls/createMockHlsDependencies.d.ts.map +1 -1
  152. package/dist/vinylTestUtil/types/current/mock/track/hls/createMockHlsDependencies.js +3 -2
  153. package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
  154. package/dist/vinylTestUtil/types/ts3.7/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
  155. package/dist/vinylTestUtil/types/ts3.7/mock/text/MockTextTrackController.d.ts +3 -3
  156. package/dist/vinylTestUtil/types/ts3.7/mock/track/dash/createMockDashDependencies.d.ts +1 -2
  157. package/dist/vinylTestUtil/types/ts3.7/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
  158. package/dist/vinylTestUtil/types/ts4.1/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
  159. package/dist/vinylTestUtil/types/ts4.1/mock/text/MockTextTrackController.d.ts +3 -3
  160. package/dist/vinylTestUtil/types/ts4.1/mock/track/dash/createMockDashDependencies.d.ts +1 -2
  161. package/dist/vinylTestUtil/types/ts4.1/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
  162. package/dist/vinylTestUtil/types/ts4.7/mock/player/MockVinylPlayerDependencies.d.ts +3 -2
  163. package/dist/vinylTestUtil/types/ts4.7/mock/text/MockTextTrackController.d.ts +3 -3
  164. package/dist/vinylTestUtil/types/ts4.7/mock/track/dash/createMockDashDependencies.d.ts +1 -2
  165. package/dist/vinylTestUtil/types/ts4.7/mock/track/hls/createMockHlsDependencies.d.ts +1 -2
  166. package/docs/USAGE.md +82 -31
  167. package/package.json +12 -12
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mediaTextTrackProvider.test.d.ts.map
@@ -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('skips STYLE blocks', () => {
111
+ it('captures STYLE block CSS while still parsing cues', () => {
112
112
  const text = `WEBVTT
113
113
 
114
114
  STYLE
115
- ::cue { color: red; }
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
- expect(parseWebVtt(text).cues.length).toBe(1);
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
- expect(parseWebVtt(text).cues.length).toBe(1);
145
+ const doc = parseWebVtt(text);
146
+ expect(doc.cues.length).toBe(1);
147
+ expect(doc.styles).toEqual([]);
131
148
  });
132
- it('tolerates cue settings on the timing line', () => {
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
- expect(parseWebVtt(text).cues[0].text).toBe('positioned');
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('returns null when the preference is null (captions off)', () => {
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', () => {
@@ -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.preferredAudioLanguage.value).toBe(options.preferredAudioLanguage);
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
- preferredAudioLanguage: null,
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.preferredAudioLanguage.value).toBe(options.preferredAudioLanguage);
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', () => {