7e55d2e1 by Matthew Neil Committed by Jon-Carlos Rivera

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
1 parent c8da7b50
...@@ -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 letcuesTrack; 227 letcuesTrack;
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 ```
......
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 }
......
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 });
......