Ad cues (#804)
* Converted the ad-related cues generated from `cue-out` and `cue-in` into a single cue spanning the period between ad-break start and ad-break end
Showing
5 changed files
with
431 additions
and
170 deletions
... | @@ -218,11 +218,8 @@ for more info. | ... | @@ -218,11 +218,8 @@ for more info. |
218 | * can be used as an initialization option | 218 | * can be used as an initialization option |
219 | 219 | ||
220 | When the `useCueTags` property is set to `true,` a text track is created with | 220 | When the `useCueTags` property is set to `true,` a text track is created with |
221 | label 'hls-segment-metadata' and kind 'metadata'. The track is then added to | 221 | label 'ad-cues' and kind 'metadata'. The track is then added to |
222 | `player.textTracks()`. Whenever a segment associated with a cue tag is playing, | 222 | `player.textTracks()`. Changes in active cue may be |
223 | the cue tags will be listed as a properties inside of a stringified JSON object | ||
224 | under its active cue's `text` property. The properties that are currently | ||
225 | supported are cueOut, cueOutCont, and cueIn. Changes in active cue may be | ||
226 | tracked by following the Video.js cue points API for text tracks. For example: | 223 | tracked by following the Video.js cue points API for text tracks. For example: |
227 | 224 | ||
228 | ```javascript | 225 | ```javascript |
... | @@ -230,7 +227,7 @@ let textTracks = player.textTracks(); | ... | @@ -230,7 +227,7 @@ let textTracks = player.textTracks(); |
230 | let cuesTrack; | 227 | let cuesTrack; |
231 | 228 | ||
232 | for (let i = 0; i < textTracks.length; i++) { | 229 | for (let i = 0; i < textTracks.length; i++) { |
233 | if (textTracks[i].label === 'hls-segment-metadata') { | 230 | if (textTracks[i].label === 'ad-cues') { |
234 | cuesTrack = textTracks[i]; | 231 | cuesTrack = textTracks[i]; |
235 | } | 232 | } |
236 | } | 233 | } |
... | @@ -240,12 +237,9 @@ cuesTrack.addEventListener('cuechange', function() { | ... | @@ -240,12 +237,9 @@ cuesTrack.addEventListener('cuechange', function() { |
240 | 237 | ||
241 | for (let i = 0; i < activeCues.length; i++) { | 238 | for (let i = 0; i < activeCues.length; i++) { |
242 | let activeCue = activeCues[i]; | 239 | let activeCue = activeCues[i]; |
243 | let cueData = JSON.parse(activeCue.text); | ||
244 | 240 | ||
245 | console.log('Cue runs from ' + activeCue.startTime + | 241 | console.log('Cue runs from ' + activeCue.startTime + |
246 | ' to ' + activeCue.endTime + | 242 | ' to ' + activeCue.endTime); |
247 | ' with cue tag contents ' + | ||
248 | (cueData.cueOut || cueData.cueOutCont || cueData.cueIn)); | ||
249 | } | 243 | } |
250 | }); | 244 | }); |
251 | ``` | 245 | ``` | ... | ... |
src/ad-cue-tags.js
0 → 100644
1 | /** | ||
2 | * @file ad-cue-tags.js | ||
3 | */ | ||
4 | import window from 'global/window'; | ||
5 | |||
6 | /** | ||
7 | * Searches for an ad cue that overlaps with the given mediaTime | ||
8 | */ | ||
9 | const findAdCue = function(track, mediaTime) { | ||
10 | let cues = track.cues; | ||
11 | |||
12 | for (let i = 0; i < cues.length; i++) { | ||
13 | let cue = cues[i]; | ||
14 | |||
15 | if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { | ||
16 | return cue; | ||
17 | } | ||
18 | } | ||
19 | return null; | ||
20 | }; | ||
21 | |||
22 | const updateAdCues = function(media, track, offset = 0) { | ||
23 | if (!media.segments) { | ||
24 | return; | ||
25 | } | ||
26 | |||
27 | let mediaTime = offset; | ||
28 | let cue; | ||
29 | |||
30 | for (let i = 0; i < media.segments.length; i++) { | ||
31 | let segment = media.segments[i]; | ||
32 | |||
33 | if (!cue) { | ||
34 | // Since the cues will span for at least the segment duration, adding a fudge | ||
35 | // factor of half segment duration will prevent duplicate cues from being | ||
36 | // created when timing info is not exact (e.g. cue start time initialized | ||
37 | // at 10.006677, but next call mediaTime is 10.003332 ) | ||
38 | cue = findAdCue(track, mediaTime + (segment.duration / 2)); | ||
39 | } | ||
40 | |||
41 | if (cue) { | ||
42 | if ('cueIn' in segment) { | ||
43 | // Found a CUE-IN so end the cue | ||
44 | cue.endTime = mediaTime; | ||
45 | cue.adEndTime = mediaTime; | ||
46 | mediaTime += segment.duration; | ||
47 | cue = null; | ||
48 | continue; | ||
49 | } | ||
50 | |||
51 | if (mediaTime < cue.endTime) { | ||
52 | // Already processed this mediaTime for this cue | ||
53 | mediaTime += segment.duration; | ||
54 | continue; | ||
55 | } | ||
56 | |||
57 | // otherwise extend cue until a CUE-IN is found | ||
58 | cue.endTime += segment.duration; | ||
59 | |||
60 | } else { | ||
61 | if ('cueOut' in segment) { | ||
62 | cue = new window.VTTCue(mediaTime, | ||
63 | mediaTime + segment.duration, | ||
64 | segment.cueOut); | ||
65 | cue.adStartTime = mediaTime; | ||
66 | // Assumes tag format to be | ||
67 | // #EXT-X-CUE-OUT:30 | ||
68 | cue.adEndTime = mediaTime + parseFloat(segment.cueOut); | ||
69 | track.addCue(cue); | ||
70 | } | ||
71 | |||
72 | if ('cueOutCont' in segment) { | ||
73 | // Entered into the middle of an ad cue | ||
74 | let adOffset; | ||
75 | let adTotal; | ||
76 | |||
77 | // Assumes tag formate to be | ||
78 | // #EXT-X-CUE-OUT-CONT:10/30 | ||
79 | [adOffset, adTotal] = segment.cueOutCont.split('/').map(parseFloat); | ||
80 | |||
81 | cue = new window.VTTCue(mediaTime, | ||
82 | mediaTime + segment.duration, | ||
83 | ''); | ||
84 | cue.adStartTime = mediaTime - adOffset; | ||
85 | cue.adEndTime = cue.adStartTime + adTotal; | ||
86 | track.addCue(cue); | ||
87 | } | ||
88 | } | ||
89 | mediaTime += segment.duration; | ||
90 | } | ||
91 | }; | ||
92 | |||
93 | export default { | ||
94 | updateAdCues, | ||
95 | findAdCue | ||
96 | }; | ||
97 |
... | @@ -6,7 +6,7 @@ import SegmentLoader from './segment-loader'; | ... | @@ -6,7 +6,7 @@ import SegmentLoader from './segment-loader'; |
6 | import Ranges from './ranges'; | 6 | import Ranges from './ranges'; |
7 | import videojs from 'video.js'; | 7 | import videojs from 'video.js'; |
8 | import HlsAudioTrack from './hls-audio-track'; | 8 | import HlsAudioTrack from './hls-audio-track'; |
9 | import window from 'global/window'; | 9 | import AdCueTags from './ad-cue-tags'; |
10 | 10 | ||
11 | // 5 minute blacklist | 11 | // 5 minute blacklist |
12 | const BLACKLIST_DURATION = 5 * 60 * 1000; | 12 | const BLACKLIST_DURATION = 5 * 60 * 1000; |
... | @@ -62,7 +62,8 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -62,7 +62,8 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
62 | this.mode_ = mode; | 62 | this.mode_ = mode; |
63 | this.useCueTags_ = useCueTags; | 63 | this.useCueTags_ = useCueTags; |
64 | if (this.useCueTags_) { | 64 | if (this.useCueTags_) { |
65 | this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'hls-segment-metadata'); | 65 | this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', |
66 | 'ad-cues'); | ||
66 | this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; | 67 | this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; |
67 | this.tech_.textTracks().addTrack_(this.cueTagsTrack_); | 68 | this.tech_.textTracks().addTrack_(this.cueTagsTrack_); |
68 | } | 69 | } |
... | @@ -133,7 +134,10 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -133,7 +134,10 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
133 | return; | 134 | return; |
134 | } | 135 | } |
135 | 136 | ||
136 | this.updateCues_(updatedPlaylist); | 137 | if (this.useCueTags_) { |
138 | this.updateAdCues_(updatedPlaylist, | ||
139 | this.masterPlaylistLoader_.expired_); | ||
140 | } | ||
137 | 141 | ||
138 | // TODO: Create a new event on the PlaylistLoader that signals | 142 | // TODO: Create a new event on the PlaylistLoader that signals |
139 | // that the segments have changed in some way and use that to | 143 | // that the segments have changed in some way and use that to |
... | @@ -831,43 +835,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -831,43 +835,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
831 | }); | 835 | }); |
832 | } | 836 | } |
833 | 837 | ||
834 | updateCues_(media) { | 838 | updateAdCues_(media, offset = 0) { |
835 | if (!this.useCueTags_ || !media.segments) { | 839 | AdCueTags.updateAdCues(media, this.cueTagsTrack_, offset); |
836 | return; | ||
837 | } | ||
838 | |||
839 | while (this.cueTagsTrack_.cues.length) { | ||
840 | this.cueTagsTrack_.removeCue(this.cueTagsTrack_.cues[0]); | ||
841 | } | ||
842 | |||
843 | let mediaTime = 0; | ||
844 | |||
845 | for (let i = 0; i < media.segments.length; i++) { | ||
846 | let segment = media.segments[i]; | ||
847 | |||
848 | if ('cueOut' in segment || 'cueOutCont' in segment || 'cueIn' in segment) { | ||
849 | let cueJson = {}; | ||
850 | |||
851 | if ('cueOut' in segment) { | ||
852 | cueJson.cueOut = segment.cueOut; | ||
853 | } | ||
854 | if ('cueOutCont' in segment) { | ||
855 | cueJson.cueOutCont = segment.cueOutCont; | ||
856 | } | ||
857 | if ('cueIn' in segment) { | ||
858 | cueJson.cueIn = segment.cueIn; | ||
859 | } | ||
860 | |||
861 | // Use a short duration for the cue point, as it should trigger for a segment | ||
862 | // transition (in this case, defined as the beginning of the segment that the tag | ||
863 | // precedes), but keep it for a minimum of 0.5 seconds to remain usable (won't | ||
864 | // lose it as an active cue by the time a user retrieves the active cues). | ||
865 | this.cueTagsTrack_.addCue(new window.VTTCue(mediaTime, | ||
866 | mediaTime + 0.5, | ||
867 | JSON.stringify(cueJson))); | ||
868 | } | ||
869 | |||
870 | mediaTime += segment.duration; | ||
871 | } | ||
872 | } | 840 | } |
873 | } | 841 | } | ... | ... |
test/ad-cue-tags.test.js
0 → 100644
1 | import QUnit from 'qunit'; | ||
2 | import AdCueTags from '../src/ad-cue-tags'; | ||
3 | import window from 'global/window'; | ||
4 | |||
5 | QUnit.module('AdCueTags', { | ||
6 | beforeEach() { | ||
7 | this.track = { | ||
8 | cues: [], | ||
9 | addCue(cue) { | ||
10 | this.cues.push(cue); | ||
11 | }, | ||
12 | clearTrack() { | ||
13 | this.cues = []; | ||
14 | } | ||
15 | }; | ||
16 | } | ||
17 | }); | ||
18 | |||
19 | QUnit.test('update tag cues', function() { | ||
20 | |||
21 | let testCue = new window.VTTCue(0, 10, 'test'); | ||
22 | |||
23 | this.track.addCue(testCue); | ||
24 | |||
25 | AdCueTags.updateAdCues({}, this.track); | ||
26 | |||
27 | QUnit.equal(this.track.cues.length, | ||
28 | 1, | ||
29 | 'does not change cues if media does not have segment property'); | ||
30 | QUnit.equal(this.track.cues[0], | ||
31 | testCue, | ||
32 | 'does not change cues if media does not have segment property'); | ||
33 | |||
34 | AdCueTags.updateAdCues({ | ||
35 | segments: [] | ||
36 | }, this.track); | ||
37 | |||
38 | QUnit.equal(this.track.cues.length, | ||
39 | 1, | ||
40 | 'does not remove cues even if no segments in playlist'); | ||
41 | |||
42 | this.track.clearTrack(); | ||
43 | |||
44 | AdCueTags.updateAdCues({ | ||
45 | segments: [{ | ||
46 | duration: 5.1, | ||
47 | cueOut: '11.5' | ||
48 | }, { | ||
49 | duration: 6.4, | ||
50 | cueOutCont: '5.1/11.5' | ||
51 | }, { | ||
52 | duration: 6, | ||
53 | cueIn: '' | ||
54 | }] | ||
55 | }, this.track, 10); | ||
56 | |||
57 | QUnit.equal(this.track.cues.length, 1, 'adds a single cue for entire ad'); | ||
58 | |||
59 | testCue = this.track.cues[0]; | ||
60 | QUnit.equal(testCue.startTime, 10, 'cue starts at 10'); | ||
61 | QUnit.equal(testCue.endTime, 21.5, 'cue ends at start time plus duration'); | ||
62 | |||
63 | this.track.clearTrack(); | ||
64 | |||
65 | AdCueTags.updateAdCues({ | ||
66 | segments: [{ | ||
67 | duration: 10, | ||
68 | cueOutCont: '10/30' | ||
69 | }, { | ||
70 | duration: 10, | ||
71 | cueOutCont: '20/30' | ||
72 | }, { | ||
73 | duration: 10, | ||
74 | cueIn: '' | ||
75 | }] | ||
76 | }, this.track); | ||
77 | |||
78 | QUnit.equal(this.track.cues.length, 1, | ||
79 | 'adds a single cue for entire ad when entering mid cue-out-cont'); | ||
80 | |||
81 | testCue = this.track.cues[0]; | ||
82 | QUnit.equal(testCue.startTime, 0, 'cue starts at 0'); | ||
83 | QUnit.equal(testCue.endTime, 20, 'cue ends at start time plus duration'); | ||
84 | QUnit.equal(testCue.adStartTime, -10, 'cue ad starts at -10'); | ||
85 | QUnit.equal(testCue.adEndTime, 20, 'cue ad ends at 20'); | ||
86 | }); | ||
87 | |||
88 | QUnit.test('update incomplete cue in live playlist situation', function() { | ||
89 | AdCueTags.updateAdCues({ | ||
90 | segments: [ | ||
91 | { | ||
92 | duration: 10, | ||
93 | cueOut: '30' | ||
94 | }, | ||
95 | { | ||
96 | duration: 10, | ||
97 | cueOutCont: '10/30' | ||
98 | } | ||
99 | ] | ||
100 | }, this.track, 10); | ||
101 | |||
102 | QUnit.equal(this.track.cues.length, 1, 'adds a single cue for new ad'); | ||
103 | |||
104 | let testCue = this.track.cues[0]; | ||
105 | |||
106 | QUnit.equal(testCue.startTime, 10, 'cue starts at 10'); | ||
107 | QUnit.equal(testCue.endTime, 30, 'cue ends at start time plus segment durations'); | ||
108 | QUnit.equal(testCue.adStartTime, 10, 'cue ad starts at 10'); | ||
109 | QUnit.equal(testCue.adEndTime, 40, 'cue ad ends at 40'); | ||
110 | |||
111 | AdCueTags.updateAdCues({ | ||
112 | segments: [ | ||
113 | { | ||
114 | duration: 10, | ||
115 | cueOutCont: '10/30' | ||
116 | }, | ||
117 | { | ||
118 | duration: 10, | ||
119 | cueOutCont: '20/30' | ||
120 | } | ||
121 | ] | ||
122 | }, this.track, 20); | ||
123 | |||
124 | QUnit.equal(this.track.cues.length, 1, 'did not remove cue or add a new one'); | ||
125 | |||
126 | QUnit.equal(testCue.startTime, 10, 'cue still starts at 10'); | ||
127 | QUnit.equal(testCue.endTime, 40, 'cue end updated to include next segment duration'); | ||
128 | QUnit.equal(testCue.adStartTime, 10, 'cue ad still starts at 10'); | ||
129 | QUnit.equal(testCue.adEndTime, 40, 'cue ad still ends at 40'); | ||
130 | |||
131 | AdCueTags.updateAdCues({ | ||
132 | segments: [ | ||
133 | { | ||
134 | duration: 10, | ||
135 | cueOutCont: '20/30' | ||
136 | }, | ||
137 | { | ||
138 | duration: 10, | ||
139 | cueIn: '' | ||
140 | } | ||
141 | ] | ||
142 | }, this.track, 30); | ||
143 | |||
144 | QUnit.equal(this.track.cues.length, 1, 'did not remove cue or add a new one'); | ||
145 | |||
146 | QUnit.equal(testCue.startTime, 10, 'cue still starts at 10'); | ||
147 | QUnit.equal(testCue.endTime, 40, 'cue end still 40'); | ||
148 | QUnit.equal(testCue.adStartTime, 10, 'cue ad still starts at 10'); | ||
149 | QUnit.equal(testCue.adEndTime, 40, 'cue ad still ends at 40'); | ||
150 | }); | ||
151 | |||
152 | QUnit.test('adjust cue end time in event of early CUE-IN', function() { | ||
153 | AdCueTags.updateAdCues({ | ||
154 | segments: [ | ||
155 | { | ||
156 | duration: 10, | ||
157 | cueOut: '30' | ||
158 | }, | ||
159 | { | ||
160 | duration: 10, | ||
161 | cueOutCont: '10/30' | ||
162 | }, | ||
163 | { | ||
164 | duration: 10, | ||
165 | cueOutCont: '20/30' | ||
166 | } | ||
167 | ] | ||
168 | }, this.track, 10); | ||
169 | |||
170 | QUnit.equal(this.track.cues.length, 1, 'adds a single cue for new ad'); | ||
171 | |||
172 | let testCue = this.track.cues[0]; | ||
173 | |||
174 | QUnit.equal(testCue.startTime, 10, 'cue starts at 10'); | ||
175 | QUnit.equal(testCue.endTime, 40, 'cue ends at start time plus segment durations'); | ||
176 | QUnit.equal(testCue.adStartTime, 10, 'cue ad starts at 10'); | ||
177 | QUnit.equal(testCue.adEndTime, 40, 'cue ad ends at 40'); | ||
178 | |||
179 | AdCueTags.updateAdCues({ | ||
180 | segments: [ | ||
181 | { | ||
182 | duration: 10, | ||
183 | cueOutCont: '10/30' | ||
184 | }, | ||
185 | { | ||
186 | duration: 10, | ||
187 | cueIn: '' | ||
188 | }, | ||
189 | { | ||
190 | duration: 10 | ||
191 | } | ||
192 | ] | ||
193 | }, this.track, 20); | ||
194 | |||
195 | QUnit.equal(this.track.cues.length, 1, 'did not remove cue or add a new one'); | ||
196 | |||
197 | QUnit.equal(testCue.startTime, 10, 'cue still starts at 10'); | ||
198 | QUnit.equal(testCue.endTime, 30, 'cue end updated to 30'); | ||
199 | QUnit.equal(testCue.adStartTime, 10, 'cue ad still starts at 10'); | ||
200 | QUnit.equal(testCue.adEndTime, 30, | ||
201 | 'cue ad end updated to 30 to account for early cueIn'); | ||
202 | }); | ||
203 | |||
204 | QUnit.test('correctly handle multiple ad cues', function() { | ||
205 | AdCueTags.updateAdCues({ | ||
206 | segments: [ | ||
207 | { | ||
208 | duration: 10 | ||
209 | }, | ||
210 | { | ||
211 | duration: 10 | ||
212 | }, | ||
213 | { | ||
214 | duration: 10 | ||
215 | }, | ||
216 | { | ||
217 | duration: 10, | ||
218 | cueOut: '30' | ||
219 | }, | ||
220 | { | ||
221 | duration: 10, | ||
222 | cueOutCont: '10/30' | ||
223 | }, | ||
224 | { | ||
225 | duration: 10, | ||
226 | cueOutCont: '20/30' | ||
227 | }, | ||
228 | { | ||
229 | duration: 10, | ||
230 | cueIn: '' | ||
231 | }, | ||
232 | { | ||
233 | duration: 10 | ||
234 | }, | ||
235 | { | ||
236 | duration: 10 | ||
237 | }, | ||
238 | { | ||
239 | duration: 10 | ||
240 | }, | ||
241 | { | ||
242 | duration: 10, | ||
243 | cueOut: '20' | ||
244 | }, | ||
245 | { | ||
246 | duration: 10, | ||
247 | cueOutCont: '10/20' | ||
248 | }, | ||
249 | { | ||
250 | duration: 10, | ||
251 | cueIn: '' | ||
252 | }, | ||
253 | { | ||
254 | duration: 10 | ||
255 | } | ||
256 | ] | ||
257 | }, this.track); | ||
258 | |||
259 | QUnit.equal(this.track.cues.length, 2, 'correctly created 2 cues for the ads'); | ||
260 | QUnit.equal(this.track.cues[0].startTime, 30, 'cue created at correct start time'); | ||
261 | QUnit.equal(this.track.cues[0].endTime, 60, 'cue has correct end time'); | ||
262 | QUnit.equal(this.track.cues[0].adStartTime, 30, 'cue has correct ad start time'); | ||
263 | QUnit.equal(this.track.cues[0].adEndTime, 60, 'cue has correct ad end time'); | ||
264 | QUnit.equal(this.track.cues[1].startTime, 100, 'cue created at correct start time'); | ||
265 | QUnit.equal(this.track.cues[1].endTime, 120, 'cue has correct end time'); | ||
266 | QUnit.equal(this.track.cues[1].adStartTime, 100, 'cue has correct ad start time'); | ||
267 | QUnit.equal(this.track.cues[1].adEndTime, 120, 'cue has correct ad end time'); | ||
268 | }); | ||
269 | |||
270 | QUnit.test('findAdCue returns correct cue', function() { | ||
271 | this.track.cues = [ | ||
272 | { | ||
273 | adStartTime: 0, | ||
274 | adEndTime: 30 | ||
275 | }, | ||
276 | { | ||
277 | adStartTime: 45, | ||
278 | adEndTime: 55 | ||
279 | }, | ||
280 | { | ||
281 | adStartTime: 100, | ||
282 | adEndTime: 120 | ||
283 | } | ||
284 | ]; | ||
285 | |||
286 | let cue; | ||
287 | |||
288 | cue = AdCueTags.findAdCue(this.track, 15); | ||
289 | QUnit.equal(cue.adStartTime, 0, 'returned correct cue'); | ||
290 | |||
291 | cue = AdCueTags.findAdCue(this.track, 40); | ||
292 | QUnit.equal(cue, null, 'cue not found, returned null'); | ||
293 | |||
294 | cue = AdCueTags.findAdCue(this.track, 120); | ||
295 | QUnit.equal(cue.adStartTime, 100, 'returned correct cue'); | ||
296 | |||
297 | cue = AdCueTags.findAdCue(this.track, 45); | ||
298 | QUnit.equal(cue.adStartTime, 45, 'returned correct cue'); | ||
299 | }); |
... | @@ -13,7 +13,6 @@ import MasterPlaylistController from '../src/master-playlist-controller'; | ... | @@ -13,7 +13,6 @@ import MasterPlaylistController from '../src/master-playlist-controller'; |
13 | import { Hls } from '../src/videojs-contrib-hls'; | 13 | import { Hls } from '../src/videojs-contrib-hls'; |
14 | /* eslint-enable no-unused-vars */ | 14 | /* eslint-enable no-unused-vars */ |
15 | import Playlist from '../src/playlist'; | 15 | import Playlist from '../src/playlist'; |
16 | import window from 'global/window'; | ||
17 | 16 | ||
18 | QUnit.module('MasterPlaylistController', { | 17 | QUnit.module('MasterPlaylistController', { |
19 | beforeEach() { | 18 | beforeEach() { |
... | @@ -620,9 +619,22 @@ function() { | ... | @@ -620,9 +619,22 @@ function() { |
620 | }); | 619 | }); |
621 | 620 | ||
622 | QUnit.test('calls to update cues on new media', function() { | 621 | QUnit.test('calls to update cues on new media', function() { |
622 | let origHlsOptions = videojs.options.hls; | ||
623 | |||
624 | videojs.options.hls = { | ||
625 | useCueTags: true | ||
626 | }; | ||
627 | |||
628 | this.player = createPlayer(); | ||
629 | this.player.src({ | ||
630 | src: 'manifest/media.m3u8', | ||
631 | type: 'application/vnd.apple.mpegurl' | ||
632 | }); | ||
633 | this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_; | ||
634 | |||
623 | let callCount = 0; | 635 | let callCount = 0; |
624 | 636 | ||
625 | this.masterPlaylistController.updateCues_ = (media) => callCount++; | 637 | this.masterPlaylistController.updateAdCues_ = (media) => callCount++; |
626 | 638 | ||
627 | // master | 639 | // master |
628 | standardXHRResponse(this.requests.shift()); | 640 | standardXHRResponse(this.requests.shift()); |
... | @@ -637,19 +649,24 @@ QUnit.test('calls to update cues on new media', function() { | ... | @@ -637,19 +649,24 @@ QUnit.test('calls to update cues on new media', function() { |
637 | this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist'); | 649 | this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist'); |
638 | 650 | ||
639 | QUnit.equal(callCount, 2, 'calls to update cues on subsequent media'); | 651 | QUnit.equal(callCount, 2, 'calls to update cues on subsequent media'); |
652 | |||
653 | videojs.options.hls = origHlsOptions; | ||
640 | }); | 654 | }); |
641 | 655 | ||
642 | QUnit.test('calls to update cues on media when no master', function() { | 656 | QUnit.test('calls to update cues on media when no master', function() { |
643 | this.requests.length = 0; | 657 | this.requests.length = 0; |
658 | |||
644 | this.player.src({ | 659 | this.player.src({ |
645 | src: 'manifest/media.m3u8', | 660 | src: 'manifest/media.m3u8', |
646 | type: 'application/vnd.apple.mpegurl' | 661 | type: 'application/vnd.apple.mpegurl' |
647 | }); | 662 | }); |
663 | |||
648 | this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_; | 664 | this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_; |
665 | this.masterPlaylistController.useCueTags_ = true; | ||
649 | 666 | ||
650 | let callCount = 0; | 667 | let callCount = 0; |
651 | 668 | ||
652 | this.masterPlaylistController.updateCues_ = (media) => callCount++; | 669 | this.masterPlaylistController.updateAdCues_ = (media) => callCount++; |
653 | 670 | ||
654 | // media | 671 | // media |
655 | standardXHRResponse(this.requests.shift()); | 672 | standardXHRResponse(this.requests.shift()); |
... | @@ -662,21 +679,6 @@ QUnit.test('calls to update cues on media when no master', function() { | ... | @@ -662,21 +679,6 @@ QUnit.test('calls to update cues on media when no master', function() { |
662 | }); | 679 | }); |
663 | 680 | ||
664 | QUnit.test('respects useCueTags option', function() { | 681 | QUnit.test('respects useCueTags option', function() { |
665 | this.masterPlaylistController.updateCues_({ | ||
666 | segments: [{ | ||
667 | duration: 10, | ||
668 | tags: ['test'] | ||
669 | }] | ||
670 | }); | ||
671 | |||
672 | QUnit.ok(!this.masterPlaylistController.cueTagsTrack_, | ||
673 | 'does not create cueTagsTrack_ if useCueTags is falsy'); | ||
674 | QUnit.equal(this.player.textTracks().length, | ||
675 | 0, | ||
676 | 'does not create a text track if useCueTags is falsy'); | ||
677 | |||
678 | this.player.dispose(); | ||
679 | |||
680 | let origHlsOptions = videojs.options.hls; | 682 | let origHlsOptions = videojs.options.hls; |
681 | 683 | ||
682 | videojs.options.hls = { | 684 | videojs.options.hls = { |
... | @@ -693,109 +695,10 @@ QUnit.test('respects useCueTags option', function() { | ... | @@ -693,109 +695,10 @@ QUnit.test('respects useCueTags option', function() { |
693 | QUnit.ok(this.masterPlaylistController.cueTagsTrack_, | 695 | QUnit.ok(this.masterPlaylistController.cueTagsTrack_, |
694 | 'creates cueTagsTrack_ if useCueTags is truthy'); | 696 | 'creates cueTagsTrack_ if useCueTags is truthy'); |
695 | QUnit.equal(this.masterPlaylistController.cueTagsTrack_.label, | 697 | QUnit.equal(this.masterPlaylistController.cueTagsTrack_.label, |
696 | 'hls-segment-metadata', | 698 | 'ad-cues', |
697 | 'cueTagsTrack_ has label of hls-segment-metadata'); | 699 | 'cueTagsTrack_ has label of ad-cues'); |
698 | QUnit.equal(this.player.textTracks()[0], this.masterPlaylistController.cueTagsTrack_, | 700 | QUnit.equal(this.player.textTracks()[0], this.masterPlaylistController.cueTagsTrack_, |
699 | 'adds cueTagsTrack as a text track if useCueTags is truthy'); | 701 | 'adds cueTagsTrack as a text track if useCueTags is truthy'); |
700 | 702 | ||
701 | this.masterPlaylistController.updateCues_({ | ||
702 | segments: [{ | ||
703 | duration: 10, | ||
704 | cueOut: 'test' | ||
705 | }] | ||
706 | }); | ||
707 | |||
708 | let cue = this.masterPlaylistController.cueTagsTrack_.cues[0]; | ||
709 | |||
710 | QUnit.equal(cue.startTime, | ||
711 | 0, | ||
712 | 'adds cue with correct start time if useCueTags is truthy'); | ||
713 | QUnit.equal(cue.endTime, | ||
714 | 0.5, | ||
715 | 'adds cue with correct end time if useCueTags is truthy'); | ||
716 | QUnit.equal(cue.text, | ||
717 | JSON.stringify({ cueOut: 'test' }), | ||
718 | 'adds cue with correct text if useCueTags is truthy'); | ||
719 | |||
720 | videojs.options.hls = origHlsOptions; | ||
721 | }); | ||
722 | |||
723 | QUnit.test('update tag cues', function() { | ||
724 | let origHlsOptions = videojs.options.hls; | ||
725 | |||
726 | videojs.options.hls = { | ||
727 | useCueTags: true | ||
728 | }; | ||
729 | |||
730 | this.player = createPlayer(); | ||
731 | this.player.src({ | ||
732 | src: 'manifest/master.m3u8', | ||
733 | type: 'application/vnd.apple.mpegurl' | ||
734 | }); | ||
735 | this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_; | ||
736 | |||
737 | let cueTagsTrack = this.masterPlaylistController.cueTagsTrack_; | ||
738 | let testCue = new window.VTTCue(0, 10, 'test'); | ||
739 | |||
740 | cueTagsTrack.addCue(testCue); | ||
741 | |||
742 | this.masterPlaylistController.updateCues_({}); | ||
743 | |||
744 | QUnit.equal(cueTagsTrack.cues.length, | ||
745 | 1, | ||
746 | 'does not change cues if media does not have segment property'); | ||
747 | QUnit.equal(cueTagsTrack.cues[0], | ||
748 | testCue, | ||
749 | 'does not change cues if media does not have segment property'); | ||
750 | |||
751 | this.masterPlaylistController.updateCues_({ | ||
752 | segments: [] | ||
753 | }); | ||
754 | |||
755 | QUnit.equal(cueTagsTrack.cues.length, | ||
756 | 0, | ||
757 | 'removes cues even if no segments in playlist'); | ||
758 | |||
759 | this.masterPlaylistController.updateCues_({ | ||
760 | segments: [{ | ||
761 | duration: 5.1, | ||
762 | cueOut: '11.5' | ||
763 | }, { | ||
764 | duration: 6.4, | ||
765 | cueOutCont: '5.1/11.5' | ||
766 | }, { | ||
767 | duration: 6, | ||
768 | cueIn: '' | ||
769 | }] | ||
770 | }); | ||
771 | |||
772 | QUnit.equal(cueTagsTrack.cues.length, 3, 'adds a cue for each segment'); | ||
773 | |||
774 | QUnit.equal(cueTagsTrack.cues[0].startTime, 0, 'cue starts at 0'); | ||
775 | QUnit.equal(cueTagsTrack.cues[0].endTime, 0.5, 'cue ends at start time plus duration'); | ||
776 | QUnit.equal(JSON.parse(cueTagsTrack.cues[0].text).cueOut, '11.5', 'cueOut matches'); | ||
777 | QUnit.ok(!('cueOutCont' in JSON.parse(cueTagsTrack.cues[0].text)), | ||
778 | 'cueOutCont not in cue'); | ||
779 | QUnit.ok(!('cueIn' in JSON.parse(cueTagsTrack.cues[0].text)), 'cueIn not in cue'); | ||
780 | QUnit.equal(cueTagsTrack.cues[1].startTime, 5.1, 'cue starts at 5.1'); | ||
781 | QUnit.equal(cueTagsTrack.cues[1].endTime, 5.6, 'cue ends at start time plus duration'); | ||
782 | QUnit.equal(JSON.parse(cueTagsTrack.cues[1].text).cueOutCont, | ||
783 | '5.1/11.5', | ||
784 | 'cueOutCont matches'); | ||
785 | QUnit.ok(!('cueOut' in JSON.parse(cueTagsTrack.cues[1].text)), 'cueOut not in cue'); | ||
786 | QUnit.ok(!('cueIn' in JSON.parse(cueTagsTrack.cues[1].text)), 'cueIn not in cue'); | ||
787 | QUnit.equal(cueTagsTrack.cues[2].startTime, 11.5, 'cue starts at 11.5'); | ||
788 | QUnit.equal(cueTagsTrack.cues[2].endTime, 12, 'cue ends at start time plus duration'); | ||
789 | QUnit.equal(JSON.parse(cueTagsTrack.cues[2].text).cueIn, '', 'cueIn matches'); | ||
790 | QUnit.ok(!('cueOut' in JSON.parse(cueTagsTrack.cues[2].text)), 'cueOut not in cue'); | ||
791 | QUnit.ok(!('cueOutCont' in JSON.parse(cueTagsTrack.cues[2].text)), | ||
792 | 'cueOutCont not in cue'); | ||
793 | |||
794 | this.masterPlaylistController.updateCues_({ | ||
795 | segments: [] | ||
796 | }); | ||
797 | |||
798 | QUnit.equal(cueTagsTrack.cues.length, 0, 'removes old cues on update'); | ||
799 | |||
800 | videojs.options.hls = origHlsOptions; | 703 | videojs.options.hls = origHlsOptions; |
801 | }); | 704 | }); | ... | ... |
-
Please register or sign in to post a comment