0de2141b by brandonocasey

fixed style issues, bare playlist-loader conversion

fixed pre-existing assertion duplication issues
1 parent a07f52ee
...@@ -12,34 +12,6 @@ import Stream from './stream'; ...@@ -12,34 +12,6 @@ import Stream from './stream';
12 import m3u8 from './m3u8'; 12 import m3u8 from './m3u8';
13 13
14 /** 14 /**
15 * Returns a new array of segments that is the result of merging
16 * properties from an older list of segments onto an updated
17 * list. No properties on the updated playlist will be overridden.
18 * @param original {array} the outdated list of segments
19 * @param update {array} the updated list of segments
20 * @param offset {number} (optional) the index of the first update
21 * segment in the original segment list. For non-live playlists,
22 * this should always be zero and does not need to be
23 * specified. For live playlists, it should be the difference
24 * between the media sequence numbers in the original and updated
25 * playlists.
26 * @return a list of merged segment objects
27 */
28 const updateSegments = function(original, update, offset) {
29 let result = update.slice();
30 let length;
31 let i;
32
33 offset = offset || 0;
34 length = Math.min(original.length, update.length + offset);
35
36 for (i = offset; i < length; i++) {
37 result[i - offset] = mergeOptions(original[i], result[i - offset]);
38 }
39 return result;
40 };
41
42 /**
43 * Returns a new master playlist that is the result of merging an 15 * Returns a new master playlist that is the result of merging an
44 * updated media playlist into the original version. If the 16 * updated media playlist into the original version. If the
45 * updated media playlist does not match any of the playlist 17 * updated media playlist does not match any of the playlist
...@@ -85,151 +57,85 @@ const updateMaster = function(master, media) { ...@@ -85,151 +57,85 @@ const updateMaster = function(master, media) {
85 return changed ? result : null; 57 return changed ? result : null;
86 }; 58 };
87 59
88 export default class PlaylistLoader extends Stream { 60 /**
89 constructor(srcUrl, withCredentials) { 61 * Returns a new array of segments that is the result of merging
90 super(); 62 * properties from an older list of segments onto an updated
91 this.srcUrl_ = srcUrl; 63 * list. No properties on the updated playlist will be overridden.
92 this.withCredentials_ = withCredentials; 64 * @param original {array} the outdated list of segments
65 * @param update {array} the updated list of segments
66 * @param offset {number} (optional) the index of the first update
67 * segment in the original segment list. For non-live playlists,
68 * this should always be zero and does not need to be
69 * specified. For live playlists, it should be the difference
70 * between the media sequence numbers in the original and updated
71 * playlists.
72 * @return a list of merged segment objects
73 */
74 const updateSegments = function(original, update, offset) {
75 let result = update.slice();
76 let length;
77 let i;
93 78
94 this.mediaUpdateTimeout_ = null; 79 offset = offset || 0;
80 length = Math.min(original.length, update.length + offset);
95 81
96 // initialize the loader state 82 for (i = offset; i < length; i++) {
97 this.state = 'HAVE_NOTHING'; 83 result[i - offset] = mergeOptions(original[i], result[i - offset]);
84 }
85 return result;
86 };
98 87
99 // track the time that has expired from the live window 88 export default class PlaylistLoader extends Stream {
100 // this allows the seekable start range to be calculated even if 89 constructor(srcUrl, withCredentials) {
101 // all segments with timing information have expired 90 super();
102 this.expired_ = 0; 91 let loader = this;
92 let dispose;
93 let mediaUpdateTimeout;
94 let request;
95 let playlistRequestError;
96 let haveMetadata;
103 97
104 // a flag that disables "expired time"-tracking this setting has 98 // a flag that disables "expired time"-tracking this setting has
105 // no effect when not playing a live stream 99 // no effect when not playing a live stream
106 this.trackExpiredTime_ = false; 100 this.trackExpiredTime_ = false;
107 101
108 if (!this.srcUrl_) { 102 if (!srcUrl) {
109 throw new Error('A non-empty playlist URL is required'); 103 throw new Error('A non-empty playlist URL is required');
110 } 104 }
111 105
112 // In a live list, don't keep track of the expired time until 106 playlistRequestError = function(xhr, url, startingState) {
113 // HLS tells us that "first play" has commenced 107 loader.setBandwidth(request || xhr);
114 this.on('firstplay', function() {
115 this.trackExpiredTime_ = true;
116 });
117
118 // live playlist staleness timeout
119 this.on('mediaupdatetimeout', () => {
120 if (this.state !== 'HAVE_METADATA') {
121 // only refresh the media playlist if no other activity is going on
122 return;
123 }
124
125 this.state = 'HAVE_CURRENT_METADATA';
126 this.request_ = XhrModule({
127 uri: resolveUrl(this.master.uri, this.media().uri),
128 withCredentials: this.withCredentials_
129 }, (error, request) => {
130 if (error) {
131 return this.playlistRequestError_(request, this.media().uri);
132 }
133 this.haveMetadata_(request, this.media().uri);
134 });
135 });
136
137 // request the specified URL
138 this.request_ = XhrModule({
139 uri: this.srcUrl_,
140 withCredentials: this.withCredentials_
141 }, (error, request) => {
142 let parser = new m3u8.Parser();
143 let i;
144
145 // clear the loader's request reference
146 this.request_ = null;
147
148 if (error) {
149 this.error = {
150 status: request.status,
151 message: 'HLS playlist request error at URL: ' + this.srcUrl_,
152 responseText: request.responseText,
153 // MEDIA_ERR_NETWORK
154 code: 2
155 };
156 return this.trigger('error');
157 }
158
159 parser.push(request.responseText);
160 parser.end();
161
162 this.state = 'HAVE_MASTER';
163
164 parser.manifest.uri = this.srcUrl_;
165
166 // loaded a master playlist
167 if (parser.manifest.playlists) {
168 this.master = parser.manifest;
169
170 // setup by-URI lookups
171 i = this.master.playlists.length;
172 while (i--) {
173 this.master.playlists[this.master.playlists[i].uri] =
174 this.master.playlists[i];
175 }
176
177 this.trigger('loadedplaylist');
178 if (!this.request_) {
179 // no media playlist was specifically selected so start
180 // from the first listed one
181 this.media(parser.manifest.playlists[0]);
182 }
183 return;
184 }
185
186 // loaded a media playlist
187 // infer a master playlist if none was previously requested
188 this.master = {
189 uri: window.location.href,
190 playlists: [{
191 uri: this.srcUrl_
192 }]
193 };
194 this.master.playlists[this.srcUrl_] = this.master.playlists[0];
195 this.haveMetadata_(request, this.srcUrl_);
196 return this.trigger('loadedmetadata');
197 });
198 }
199
200 playlistRequestError_(xhr, url, startingState) {
201 this.setBandwidth(this.request_ || xhr);
202 108
203 // any in-flight request is now finished 109 // any in-flight request is now finished
204 this.request_ = null; 110 request = null;
205 111
206 if (startingState) { 112 if (startingState) {
207 this.state = startingState; 113 loader.state = startingState;
208 } 114 }
209 115
210 this.error = { 116 loader.error = {
211 playlist: this.master.playlists[url], 117 playlist: loader.master.playlists[url],
212 status: xhr.status, 118 status: xhr.status,
213 message: 'HLS playlist request error at URL: ' + url, 119 message: 'HLS playlist request error at URL: ' + url,
214 responseText: xhr.responseText, 120 responseText: xhr.responseText,
215 code: (xhr.status >= 500) ? 4 : 2 121 code: (xhr.status >= 500) ? 4 : 2
216 }; 122 };
217 this.trigger('error'); 123 loader.trigger('error');
218 } 124 };
219 125
220 // update the playlist loader's state in response to a new or 126 // update the playlist loader's state in response to a new or
221 // updated playlist. 127 // updated playlist.
222 haveMetadata_(xhr, url) { 128
129 haveMetadata = function(xhr, url) {
223 let parser; 130 let parser;
224 let refreshDelay; 131 let refreshDelay;
225 let update; 132 let update;
226 133
227 this.setBandwidth(this.request_ || xhr); 134 loader.setBandwidth(request || xhr);
228 135
229 // any in-flight request is now finished 136 // any in-flight request is now finished
230 this.request_ = null; 137 request = null;
231 138 loader.state = 'HAVE_METADATA';
232 this.state = 'HAVE_METADATA';
233 139
234 parser = new m3u8.Parser(); 140 parser = new m3u8.Parser();
235 parser.push(xhr.responseText); 141 parser.push(xhr.responseText);
...@@ -237,11 +143,11 @@ export default class PlaylistLoader extends Stream { ...@@ -237,11 +143,11 @@ export default class PlaylistLoader extends Stream {
237 parser.manifest.uri = url; 143 parser.manifest.uri = url;
238 144
239 // merge this playlist into the master 145 // merge this playlist into the master
240 update = updateMaster(this.master, parser.manifest); 146 update = updateMaster(loader.master, parser.manifest);
241 refreshDelay = (parser.manifest.targetDuration || 10) * 1000; 147 refreshDelay = (parser.manifest.targetDuration || 10) * 1000;
242 if (update) { 148 if (update) {
243 this.master = update; 149 loader.master = update;
244 this.updateMediaPlaylist_(parser.manifest); 150 loader.updateMediaPlaylist_(parser.manifest);
245 } else { 151 } else {
246 // if the playlist is unchanged since the last reload, 152 // if the playlist is unchanged since the last reload,
247 // try again after half the target duration 153 // try again after half the target duration
...@@ -249,38 +155,39 @@ export default class PlaylistLoader extends Stream { ...@@ -249,38 +155,39 @@ export default class PlaylistLoader extends Stream {
249 } 155 }
250 156
251 // refresh live playlists after a target duration passes 157 // refresh live playlists after a target duration passes
252 if (!this.media().endList) { 158 if (!loader.media().endList) {
253 this.clearMediaUpdateTimeout_(); 159 window.clearTimeout(mediaUpdateTimeout);
254 this.mediaUpdateTimeout_ = window.setTimeout(() => { 160 mediaUpdateTimeout = window.setTimeout(function() {
255 this.trigger('mediaupdatetimeout'); 161 loader.trigger('mediaupdatetimeout');
256 }, refreshDelay); 162 }, refreshDelay);
257 } 163 }
258 164
259 this.trigger('loadedplaylist'); 165 loader.trigger('loadedplaylist');
260 } 166 };
261 167
262 clearMediaUpdateTimeout_() { 168 // initialize the loader state
263 if (this.mediaUpdateTimeout_) { 169 loader.state = 'HAVE_NOTHING';
264 window.clearTimeout(this.mediaUpdateTimeout_);
265 }
266 }
267 170
268 requestDispose_() { 171 // track the time that has expired from the live window
269 if (this.request_) { 172 // this allows the seekable start range to be calculated even if
270 this.request_.onreadystatechange = null; 173 // all segments with timing information have expired
271 this.request_.abort(); 174 this.expired_ = 0;
272 this.request_ = null; 175
273 } 176 // capture the prototype dispose function
274 } 177 dispose = this.dispose;
275 178
276 /** 179 /**
277 * Abort any outstanding work and clean up. 180 * Abort any outstanding work and clean up.
278 */ 181 */
279 dispose() { 182 loader.dispose = function() {
280 this.requestDispose_(); 183 if (request) {
281 this.clearMediaUpdateTimeout_(); 184 request.onreadystatechange = null;
282 super.dispose(); 185 request.abort();
283 } 186 request = null;
187 }
188 window.clearTimeout(mediaUpdateTimeout);
189 dispose.call(this);
190 };
284 191
285 /** 192 /**
286 * When called without any arguments, returns the currently 193 * When called without any arguments, returns the currently
...@@ -292,41 +199,44 @@ export default class PlaylistLoader extends Stream { ...@@ -292,41 +199,44 @@ export default class PlaylistLoader extends Stream {
292 * @param playlist (optional) {object} the parsed media playlist 199 * @param playlist (optional) {object} the parsed media playlist
293 * object to switch to 200 * object to switch to
294 */ 201 */
295 media(playlist) { 202 loader.media = function(playlist) {
296 let startingState = this.state; 203 let startingState = loader.state;
297 let mediaChange; 204 let mediaChange;
298
299 // getter 205 // getter
300 if (!playlist) { 206 if (!playlist) {
301 return this.media_; 207 return loader.media_;
302 } 208 }
303 209
304 // setter 210 // setter
305 if (this.state === 'HAVE_NOTHING') { 211 if (loader.state === 'HAVE_NOTHING') {
306 throw new Error('Cannot switch media playlist from ' + this.state); 212 throw new Error('Cannot switch media playlist from ' + loader.state);
307 } 213 }
308 214
309 // find the playlist object if the target playlist has been 215 // find the playlist object if the target playlist has been
310 // specified by URI 216 // specified by URI
311 if (typeof playlist === 'string') { 217 if (typeof playlist === 'string') {
312 if (!this.master.playlists[playlist]) { 218 if (!loader.master.playlists[playlist]) {
313 throw new Error('Unknown playlist URI: ' + playlist); 219 throw new Error('Unknown playlist URI: ' + playlist);
314 } 220 }
315 playlist = this.master.playlists[playlist]; 221 playlist = loader.master.playlists[playlist];
316 } 222 }
317 223
318 mediaChange = !this.media_ || playlist.uri !== this.media_.uri; 224 mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri;
319 225
320 // switch to fully loaded playlists immediately 226 // switch to fully loaded playlists immediately
321 if (this.master.playlists[playlist.uri].endList) { 227 if (loader.master.playlists[playlist.uri].endList) {
322 // abort outstanding playlist requests 228 // abort outstanding playlist requests
323 this.requestDispose_(); 229 if (request) {
324 this.state = 'HAVE_METADATA'; 230 request.onreadystatechange = null;
325 this.media_ = playlist; 231 request.abort();
232 request = null;
233 }
234 loader.state = 'HAVE_METADATA';
235 loader.media_ = playlist;
326 236
327 // trigger media change if the active media has been updated 237 // trigger media change if the active media has been updated
328 if (mediaChange) { 238 if (mediaChange) {
329 this.trigger('mediachange'); 239 loader.trigger('mediachange');
330 } 240 }
331 return; 241 return;
332 } 242 }
...@@ -336,43 +246,130 @@ export default class PlaylistLoader extends Stream { ...@@ -336,43 +246,130 @@ export default class PlaylistLoader extends Stream {
336 return; 246 return;
337 } 247 }
338 248
339 this.state = 'SWITCHING_MEDIA'; 249 loader.state = 'SWITCHING_MEDIA';
340 250
341 // there is already an outstanding playlist request 251 // there is already an outstanding playlist request
342 if (this.request_) { 252 if (request) {
343 if (resolveUrl(this.master.uri, playlist.uri) === this.request_.url) { 253 if (resolveUrl(loader.master.uri, playlist.uri) === request.url) {
344 // requesting to switch to the same playlist multiple times 254 // requesting to switch to the same playlist multiple times
345 // has no effect after the first 255 // has no effect after the first
346 return; 256 return;
347 } 257 }
348 this.requestDispose_(); 258 request.onreadystatechange = null;
259 request.abort();
260 request = null;
349 } 261 }
350 262
351 // request the new playlist 263 // request the new playlist
352 this.request_ = XhrModule({ 264 request = XhrModule({
353 uri: resolveUrl(this.master.uri, playlist.uri), 265 uri: resolveUrl(loader.master.uri, playlist.uri),
354 withCredentials: this.withCredentials_ 266 withCredentials
355 }, (error, request) => { 267 }, function(error, request) {
356 if (error) { 268 if (error) {
357 return this.playlistRequestError_(request, playlist.uri, startingState); 269 return playlistRequestError(request, playlist.uri, startingState);
358 } 270 }
359 this.haveMetadata_(request, playlist.uri);
360 271
361 if (error) { 272 haveMetadata(request, playlist.uri);
362 return;
363 }
364 273
365 // fire loadedmetadata the first time a media playlist is loaded 274 // fire loadedmetadata the first time a media playlist is loaded
366 if (startingState === 'HAVE_MASTER') { 275 if (startingState === 'HAVE_MASTER') {
367 this.trigger('loadedmetadata'); 276 loader.trigger('loadedmetadata');
368 } else { 277 } else {
369 this.trigger('mediachange'); 278 loader.trigger('mediachange');
279 }
280 });
281 };
282
283 loader.setBandwidth = function(xhr) {
284 loader.bandwidth = xhr.bandwidth;
285 };
286
287 // In a live list, don't keep track of the expired time until
288 // HLS tells us that "first play" has commenced
289 loader.on('firstplay', function() {
290 this.trackExpiredTime_ = true;
291 });
292
293 // live playlist staleness timeout
294 loader.on('mediaupdatetimeout', function() {
295 if (loader.state !== 'HAVE_METADATA') {
296 // only refresh the media playlist if no other activity is going on
297 return;
370 } 298 }
299
300 loader.state = 'HAVE_CURRENT_METADATA';
301 request = XhrModule({
302 uri: resolveUrl(loader.master.uri, loader.media().uri),
303 withCredentials
304 }, function(error, request) {
305 if (error) {
306 return playlistRequestError(request, loader.media().uri);
307 }
308 haveMetadata(request, loader.media().uri);
309 });
371 }); 310 });
311
312 // request the specified URL
313 request = XhrModule({
314 uri: srcUrl,
315 withCredentials
316 }, function(error, req) {
317 let parser;
318 let i;
319
320 // clear the loader's request reference
321 request = null;
322
323 if (error) {
324 loader.error = {
325 status: req.status,
326 message: 'HLS playlist request error at URL: ' + srcUrl,
327 responseText: req.responseText,
328 // MEDIA_ERR_NETWORK
329 code: 2
330 };
331 return loader.trigger('error');
372 } 332 }
373 333
374 setBandwidth(xhr) { 334 parser = new m3u8.Parser();
375 this.bandwidth = xhr.bandwidth; 335 parser.push(req.responseText);
336 parser.end();
337
338 loader.state = 'HAVE_MASTER';
339
340 parser.manifest.uri = srcUrl;
341
342 // loaded a master playlist
343 if (parser.manifest.playlists) {
344 loader.master = parser.manifest;
345
346 // setup by-URI lookups
347 i = loader.master.playlists.length;
348 while (i--) {
349 loader.master.playlists[loader.master.playlists[i].uri] = loader.master.playlists[i];
350 }
351
352 loader.trigger('loadedplaylist');
353 if (!request) {
354 // no media playlist was specifically selected so start
355 // from the first listed one
356 loader.media(parser.manifest.playlists[0]);
357 }
358 return;
359 }
360
361 // loaded a media playlist
362 // infer a master playlist if none was previously requested
363 loader.master = {
364 uri: window.location.href,
365 playlists: [{
366 uri: srcUrl
367 }]
368 };
369 loader.master.playlists[srcUrl] = loader.master.playlists[0];
370 haveMetadata(req, srcUrl);
371 return loader.trigger('loadedmetadata');
372 });
376 } 373 }
377 /** 374 /**
378 * Update the PlaylistLoader state to reflect the changes in an 375 * Update the PlaylistLoader state to reflect the changes in an
...@@ -406,10 +403,10 @@ export default class PlaylistLoader extends Stream { ...@@ -406,10 +403,10 @@ export default class PlaylistLoader extends Stream {
406 // try using precise timing from first segment of the updated 403 // try using precise timing from first segment of the updated
407 // playlist 404 // playlist
408 if (update.segments.length) { 405 if (update.segments.length) {
409 if (typeof update.segments[0].start !== 'undefined') { 406 if (update.segments[0].start !== undefined) {
410 this.expired_ = update.segments[0].start; 407 this.expired_ = update.segments[0].start;
411 return; 408 return;
412 } else if (typeof update.segments[0].end !== 'undefined') { 409 } else if (update.segments[0].end !== undefined) {
413 this.expired_ = update.segments[0].end - update.segments[0].duration; 410 this.expired_ = update.segments[0].end - update.segments[0].duration;
414 return; 411 return;
415 } 412 }
...@@ -430,11 +427,11 @@ export default class PlaylistLoader extends Stream { ...@@ -430,11 +427,11 @@ export default class PlaylistLoader extends Stream {
430 continue; 427 continue;
431 } 428 }
432 429
433 if (typeof segment.end !== 'undefined') { 430 if (segment.end !== undefined) {
434 this.expired_ = segment.end; 431 this.expired_ = segment.end;
435 return; 432 return;
436 } 433 }
437 if (typeof segment.start !== 'undefined') { 434 if (segment.start !== undefined) {
438 this.expired_ = segment.start + segment.duration; 435 this.expired_ = segment.start + segment.duration;
439 return; 436 return;
440 } 437 }
...@@ -499,7 +496,7 @@ export default class PlaylistLoader extends Stream { ...@@ -499,7 +496,7 @@ export default class PlaylistLoader extends Stream {
499 496
500 // use the bounds we just found and playlist information to 497 // use the bounds we just found and playlist information to
501 // estimate the segment that contains the time we are looking for 498 // estimate the segment that contains the time we are looking for
502 if (typeof startIndex !== 'undefined') { 499 if (startIndex !== undefined) {
503 // We have a known-start point that is before our desired time so 500 // We have a known-start point that is before our desired time so
504 // walk from that point forwards 501 // walk from that point forwards
505 time = time - knownStart; 502 time = time - knownStart;
...@@ -517,14 +514,14 @@ export default class PlaylistLoader extends Stream { ...@@ -517,14 +514,14 @@ export default class PlaylistLoader extends Stream {
517 // so fallback to interpolating between the segment index 514 // so fallback to interpolating between the segment index
518 // based on the known span of the timeline we are dealing with 515 // based on the known span of the timeline we are dealing with
519 // and the number of segments inside that span 516 // and the number of segments inside that span
520 return startIndex + Math.floor(((originalTime - knownStart) / 517 return startIndex + Math.floor(
521 (knownEnd - knownStart)) * 518 ((originalTime - knownStart) / (knownEnd - knownStart)) *
522 (endIndex - startIndex)); 519 (endIndex - startIndex));
523 } 520 }
524 521
525 // We _still_ haven't found a segment so load the last one 522 // We _still_ haven't found a segment so load the last one
526 return lastSegment; 523 return lastSegment;
527 } else if (typeof endIndex !== 'undefined') { 524 } else if (endIndex !== undefined) {
528 // We _only_ have a known-end point that is after our desired time so 525 // We _only_ have a known-end point that is after our desired time so
529 // walk from that point backwards 526 // walk from that point backwards
530 time = knownEnd - time; 527 time = knownEnd - time;
...@@ -540,9 +537,10 @@ export default class PlaylistLoader extends Stream { ...@@ -540,9 +537,10 @@ export default class PlaylistLoader extends Stream {
540 // We haven't found a segment so load the first one if time is zero 537 // We haven't found a segment so load the first one if time is zero
541 if (time === 0) { 538 if (time === 0) {
542 return 0; 539 return 0;
543 } 540 } else {
544 return -1; 541 return -1;
545 } 542 }
543 } else {
546 // We known nothing so walk from the front of the playlist, 544 // We known nothing so walk from the front of the playlist,
547 // subtracting durations until we find a segment that contains 545 // subtracting durations until we find a segment that contains
548 // time and return it 546 // time and return it
...@@ -564,4 +562,5 @@ export default class PlaylistLoader extends Stream { ...@@ -564,4 +562,5 @@ export default class PlaylistLoader extends Stream {
564 // the one most likely to tell us something about the timeline 562 // the one most likely to tell us something about the timeline
565 return lastSegment; 563 return lastSegment;
566 } 564 }
565 }
567 } 566 }
......
...@@ -35,10 +35,8 @@ const xhr = function(options, callback) { ...@@ -35,10 +35,8 @@ const xhr = function(options, callback) {
35 response.statusCode !== 200 && 35 response.statusCode !== 200 &&
36 response.statusCode !== 206 && 36 response.statusCode !== 206 &&
37 response.statusCode !== 0) { 37 response.statusCode !== 0) {
38 error = new Error( 38 error = new Error('XHR Failed with a response of: ' +
39 'XHR Failed with a response of: ' + 39 (request && (request.response || request.responseText)));
40 (request && (request.response || request.responseText))
41 );
42 } 40 }
43 41
44 callback(error, request); 42 callback(error, request);
......
...@@ -12,10 +12,6 @@ const urlTo = function(path) { ...@@ -12,10 +12,6 @@ const urlTo = function(path) {
12 .join('/'); 12 .join('/');
13 }; 13 };
14 14
15 const respond = function(request, string) {
16 return request.respond(200, null, string);
17 };
18
19 QUnit.module('Playlist Loader', { 15 QUnit.module('Playlist Loader', {
20 beforeEach() { 16 beforeEach() {
21 // fake XHRs 17 // fake XHRs
...@@ -57,7 +53,7 @@ QUnit.test('starts without any metadata', function() { ...@@ -57,7 +53,7 @@ QUnit.test('starts without any metadata', function() {
57 QUnit.test('starts with no expired time', function() { 53 QUnit.test('starts with no expired time', function() {
58 let loader = new PlaylistLoader('media.m3u8'); 54 let loader = new PlaylistLoader('media.m3u8');
59 55
60 respond(this.requests.pop(), 56 this.requests.pop().respond(200, null,
61 '#EXTM3U\n' + 57 '#EXTM3U\n' +
62 '#EXTINF:10,\n' + 58 '#EXTINF:10,\n' +
63 '0.ts\n'); 59 '0.ts\n');
...@@ -66,7 +62,7 @@ QUnit.test('starts with no expired time', function() { ...@@ -66,7 +62,7 @@ QUnit.test('starts with no expired time', function() {
66 'zero seconds expired'); 62 'zero seconds expired');
67 }); 63 });
68 64
69 QUnit.test('this.requests the initial playlist immediately', function() { 65 QUnit.test('requests the initial playlist immediately', function() {
70 /* eslint-disable no-unused-vars */ 66 /* eslint-disable no-unused-vars */
71 let loader = new PlaylistLoader('master.m3u8'); 67 let loader = new PlaylistLoader('master.m3u8');
72 /* eslint-enable no-unused-vars */ 68 /* eslint-enable no-unused-vars */
...@@ -84,7 +80,7 @@ QUnit.test('moves to HAVE_MASTER after loading a master playlist', function() { ...@@ -84,7 +80,7 @@ QUnit.test('moves to HAVE_MASTER after loading a master playlist', function() {
84 loader.on('loadedplaylist', function() { 80 loader.on('loadedplaylist', function() {
85 state = loader.state; 81 state = loader.state;
86 }); 82 });
87 respond(this.requests.pop(), 83 this.requests.pop().respond(200, null,
88 '#EXTM3U\n' + 84 '#EXTM3U\n' +
89 '#EXT-X-STREAM-INF:\n' + 85 '#EXT-X-STREAM-INF:\n' +
90 'media.m3u8\n'); 86 'media.m3u8\n');
...@@ -99,7 +95,7 @@ QUnit.test('jumps to HAVE_METADATA when initialized with a media playlist', func ...@@ -99,7 +95,7 @@ QUnit.test('jumps to HAVE_METADATA when initialized with a media playlist', func
99 loader.on('loadedmetadata', function() { 95 loader.on('loadedmetadata', function() {
100 loadedmetadatas++; 96 loadedmetadatas++;
101 }); 97 });
102 respond(this.requests.pop(), 98 this.requests.pop().respond(200, null,
103 '#EXTM3U\n' + 99 '#EXTM3U\n' +
104 '#EXTINF:10,\n' + 100 '#EXTINF:10,\n' +
105 '0.ts\n' + 101 '0.ts\n' +
...@@ -108,16 +104,15 @@ QUnit.test('jumps to HAVE_METADATA when initialized with a media playlist', func ...@@ -108,16 +104,15 @@ QUnit.test('jumps to HAVE_METADATA when initialized with a media playlist', func
108 QUnit.ok(loader.media(), 'sets the media playlist'); 104 QUnit.ok(loader.media(), 'sets the media playlist');
109 QUnit.ok(loader.media().uri, 'sets the media playlist URI'); 105 QUnit.ok(loader.media().uri, 'sets the media playlist URI');
110 QUnit.strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); 106 QUnit.strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct');
111 QUnit.strictEqual(this.requests.length, 0, 'no more this.requests are made'); 107 QUnit.strictEqual(this.requests.length, 0, 'no more requests are made');
112 QUnit.strictEqual(loadedmetadatas, 1, 'fired one loadedmetadata'); 108 QUnit.strictEqual(loadedmetadatas, 1, 'fired one loadedmetadata');
113 }); 109 });
114 110
115 QUnit.test( 111 QUnit.test('jumps to HAVE_METADATA when initialized with a live media playlist',
116 'jumps to HAVE_METADATA when initialized with a live media playlist',
117 function() { 112 function() {
118 let loader = new PlaylistLoader('media.m3u8'); 113 let loader = new PlaylistLoader('media.m3u8');
119 114
120 respond(this.requests.pop(), 115 this.requests.pop().respond(200, null,
121 '#EXTM3U\n' + 116 '#EXTM3U\n' +
122 '#EXTINF:10,\n' + 117 '#EXTINF:10,\n' +
123 '0.ts\n'); 118 '0.ts\n');
...@@ -137,20 +132,20 @@ QUnit.test('moves to HAVE_METADATA after loading a media playlist', function() { ...@@ -137,20 +132,20 @@ QUnit.test('moves to HAVE_METADATA after loading a media playlist', function() {
137 loader.on('loadedmetadata', function() { 132 loader.on('loadedmetadata', function() {
138 loadedMetadata++; 133 loadedMetadata++;
139 }); 134 });
140 respond(this.requests.pop(), 135 this.requests.pop().respond(200, null,
141 '#EXTM3U\n' + 136 '#EXTM3U\n' +
142 '#EXT-X-STREAM-INF:\n' + 137 '#EXT-X-STREAM-INF:\n' +
143 'media.m3u8\n' + 138 'media.m3u8\n' +
144 'alt.m3u8\n'); 139 'alt.m3u8\n');
145 QUnit.strictEqual(loadedPlaylist, 1, 'fired loadedplaylist once'); 140 QUnit.strictEqual(loadedPlaylist, 1, 'fired loadedplaylist once');
146 QUnit.strictEqual(loadedMetadata, 0, 'did not fire loadedmetadata'); 141 QUnit.strictEqual(loadedMetadata, 0, 'did not fire loadedmetadata');
147 QUnit.strictEqual(this.requests.length, 1, 'this.requests the media playlist'); 142 QUnit.strictEqual(this.requests.length, 1, 'requests the media playlist');
148 QUnit.strictEqual(this.requests[0].method, 'GET', 'GETs the media playlist'); 143 QUnit.strictEqual(this.requests[0].method, 'GET', 'GETs the media playlist');
149 QUnit.strictEqual(this.requests[0].url, 144 QUnit.strictEqual(this.requests[0].url,
150 urlTo('media.m3u8'), 145 urlTo('media.m3u8'),
151 'this.requests the first playlist'); 146 'requests the first playlist');
152 147
153 respond(this.requests.pop(), 148 this.requests.pop().respond(200, null,
154 '#EXTM3U\n' + 149 '#EXTM3U\n' +
155 '#EXTINF:10,\n' + 150 '#EXTINF:10,\n' +
156 '0.ts\n'); 151 '0.ts\n');
...@@ -164,7 +159,7 @@ QUnit.test('moves to HAVE_METADATA after loading a media playlist', function() { ...@@ -164,7 +159,7 @@ QUnit.test('moves to HAVE_METADATA after loading a media playlist', function() {
164 QUnit.test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() { 159 QUnit.test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() {
165 let loader = new PlaylistLoader('live.m3u8'); 160 let loader = new PlaylistLoader('live.m3u8');
166 161
167 respond(this.requests.pop(), 162 this.requests.pop().respond(200, null,
168 '#EXTM3U\n' + 163 '#EXTM3U\n' +
169 '#EXTINF:10,\n' + 164 '#EXTINF:10,\n' +
170 '0.ts\n'); 165 '0.ts\n');
...@@ -180,25 +175,24 @@ QUnit.test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', functi ...@@ -180,25 +175,24 @@ QUnit.test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', functi
180 QUnit.test('returns to HAVE_METADATA after refreshing the playlist', function() { 175 QUnit.test('returns to HAVE_METADATA after refreshing the playlist', function() {
181 let loader = new PlaylistLoader('live.m3u8'); 176 let loader = new PlaylistLoader('live.m3u8');
182 177
183 respond(this.requests.pop(), 178 this.requests.pop().respond(200, null,
184 '#EXTM3U\n' + 179 '#EXTM3U\n' +
185 '#EXTINF:10,\n' + 180 '#EXTINF:10,\n' +
186 '0.ts\n'); 181 '0.ts\n');
187 // 10s, one target duration 182 // 10s, one target duration
188 this.clock.tick(10 * 1000); 183 this.clock.tick(10 * 1000);
189 respond(this.requests.pop(), 184 this.requests.pop().respond(200, null,
190 '#EXTM3U\n' + 185 '#EXTM3U\n' +
191 '#EXTINF:10,\n' + 186 '#EXTINF:10,\n' +
192 '1.ts\n'); 187 '1.ts\n');
193 QUnit.strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); 188 QUnit.strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct');
194 }); 189 });
195 190
196 QUnit.test( 191 QUnit.test('does not increment expired seconds before firstplay is triggered',
197 'does not increment expired seconds before firstplay is triggered',
198 function() { 192 function() {
199 let loader = new PlaylistLoader('live.m3u8'); 193 let loader = new PlaylistLoader('live.m3u8');
200 194
201 respond(this.requests.pop(), 195 this.requests.pop().respond(200, null,
202 '#EXTM3U\n' + 196 '#EXTM3U\n' +
203 '#EXT-X-MEDIA-SEQUENCE:0\n' + 197 '#EXT-X-MEDIA-SEQUENCE:0\n' +
204 '#EXTINF:10,\n' + 198 '#EXTINF:10,\n' +
...@@ -211,7 +205,7 @@ function() { ...@@ -211,7 +205,7 @@ function() {
211 '3.ts\n'); 205 '3.ts\n');
212 // 10s, one target duration 206 // 10s, one target duration
213 this.clock.tick(10 * 1000); 207 this.clock.tick(10 * 1000);
214 respond(this.requests.pop(), 208 this.requests.pop().respond(200, null,
215 '#EXTM3U\n' + 209 '#EXTM3U\n' +
216 '#EXT-X-MEDIA-SEQUENCE:1\n' + 210 '#EXT-X-MEDIA-SEQUENCE:1\n' +
217 '#EXTINF:10,\n' + 211 '#EXTINF:10,\n' +
...@@ -229,7 +223,7 @@ QUnit.test('increments expired seconds after a segment is removed', function() { ...@@ -229,7 +223,7 @@ QUnit.test('increments expired seconds after a segment is removed', function() {
229 let loader = new PlaylistLoader('live.m3u8'); 223 let loader = new PlaylistLoader('live.m3u8');
230 224
231 loader.trigger('firstplay'); 225 loader.trigger('firstplay');
232 respond(this.requests.pop(), 226 this.requests.pop().respond(200, null,
233 '#EXTM3U\n' + 227 '#EXTM3U\n' +
234 '#EXT-X-MEDIA-SEQUENCE:0\n' + 228 '#EXT-X-MEDIA-SEQUENCE:0\n' +
235 '#EXTINF:10,\n' + 229 '#EXTINF:10,\n' +
...@@ -242,7 +236,7 @@ QUnit.test('increments expired seconds after a segment is removed', function() { ...@@ -242,7 +236,7 @@ QUnit.test('increments expired seconds after a segment is removed', function() {
242 '3.ts\n'); 236 '3.ts\n');
243 // 10s, one target duration 237 // 10s, one target duration
244 this.clock.tick(10 * 1000); 238 this.clock.tick(10 * 1000);
245 respond(this.requests.pop(), 239 this.requests.pop().respond(200, null,
246 '#EXTM3U\n' + 240 '#EXTM3U\n' +
247 '#EXT-X-MEDIA-SEQUENCE:1\n' + 241 '#EXT-X-MEDIA-SEQUENCE:1\n' +
248 '#EXTINF:10,\n' + 242 '#EXTINF:10,\n' +
...@@ -260,7 +254,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() { ...@@ -260,7 +254,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() {
260 let loader = new PlaylistLoader('live.m3u8'); 254 let loader = new PlaylistLoader('live.m3u8');
261 255
262 loader.trigger('firstplay'); 256 loader.trigger('firstplay');
263 respond(this.requests.pop(), 257 this.requests.pop().respond(200, null,
264 '#EXTM3U\n' + 258 '#EXTM3U\n' +
265 '#EXT-X-MEDIA-SEQUENCE:0\n' + 259 '#EXT-X-MEDIA-SEQUENCE:0\n' +
266 '#EXTINF:10,\n' + 260 '#EXTINF:10,\n' +
...@@ -272,7 +266,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() { ...@@ -272,7 +266,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() {
272 '2.ts\n'); 266 '2.ts\n');
273 // 10s, one target duration 267 // 10s, one target duration
274 this.clock.tick(10 * 1000); 268 this.clock.tick(10 * 1000);
275 respond(this.requests.pop(), 269 this.requests.pop().respond(200, null,
276 '#EXTM3U\n' + 270 '#EXTM3U\n' +
277 '#EXT-X-MEDIA-SEQUENCE:1\n' + 271 '#EXT-X-MEDIA-SEQUENCE:1\n' +
278 '#EXTINF:3,\n' + 272 '#EXTINF:3,\n' +
...@@ -284,7 +278,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() { ...@@ -284,7 +278,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() {
284 278
285 // 10s, one target duration 279 // 10s, one target duration
286 this.clock.tick(10 * 1000); 280 this.clock.tick(10 * 1000);
287 respond(this.requests.pop(), 281 this.requests.pop().respond(200, null,
288 '#EXTM3U\n' + 282 '#EXTM3U\n' +
289 '#EXT-X-MEDIA-SEQUENCE:2\n' + 283 '#EXT-X-MEDIA-SEQUENCE:2\n' +
290 '#EXT-X-DISCONTINUITY\n' + 284 '#EXT-X-DISCONTINUITY\n' +
...@@ -294,7 +288,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() { ...@@ -294,7 +288,7 @@ QUnit.test('increments expired seconds after a discontinuity', function() {
294 288
295 // 10s, one target duration 289 // 10s, one target duration
296 this.clock.tick(10 * 1000); 290 this.clock.tick(10 * 1000);
297 respond(this.requests.pop(), 291 this.requests.pop().respond(200, null,
298 '#EXTM3U\n' + 292 '#EXTM3U\n' +
299 '#EXT-X-MEDIA-SEQUENCE:3\n' + 293 '#EXT-X-MEDIA-SEQUENCE:3\n' +
300 '#EXT-X-DISCONTINUITY-SEQUENCE:1\n' + 294 '#EXT-X-DISCONTINUITY-SEQUENCE:1\n' +
...@@ -303,13 +297,12 @@ QUnit.test('increments expired seconds after a discontinuity', function() { ...@@ -303,13 +297,12 @@ QUnit.test('increments expired seconds after a discontinuity', function() {
303 QUnit.equal(loader.expired_, 17, 'tracked expiration across the discontinuity'); 297 QUnit.equal(loader.expired_, 17, 'tracked expiration across the discontinuity');
304 }); 298 });
305 299
306 QUnit.test( 300 QUnit.test('tracks expired seconds properly when two discontinuities expire at once',
307 'tracks expired seconds properly when two discontinuities expire at once',
308 function() { 301 function() {
309 let loader = new PlaylistLoader('live.m3u8'); 302 let loader = new PlaylistLoader('live.m3u8');
310 303
311 loader.trigger('firstplay'); 304 loader.trigger('firstplay');
312 respond(this.requests.pop(), 305 this.requests.pop().respond(200, null,
313 '#EXTM3U\n' + 306 '#EXTM3U\n' +
314 '#EXT-X-MEDIA-SEQUENCE:0\n' + 307 '#EXT-X-MEDIA-SEQUENCE:0\n' +
315 '#EXTINF:4,\n' + 308 '#EXTINF:4,\n' +
...@@ -323,7 +316,7 @@ function() { ...@@ -323,7 +316,7 @@ function() {
323 '#EXTINF:7,\n' + 316 '#EXTINF:7,\n' +
324 '3.ts\n'); 317 '3.ts\n');
325 this.clock.tick(10 * 1000); 318 this.clock.tick(10 * 1000);
326 respond(this.requests.pop(), 319 this.requests.pop().respond(200, null,
327 '#EXTM3U\n' + 320 '#EXTM3U\n' +
328 '#EXT-X-MEDIA-SEQUENCE:3\n' + 321 '#EXT-X-MEDIA-SEQUENCE:3\n' +
329 '#EXT-X-DISCONTINUITY-SEQUENCE:2\n' + 322 '#EXT-X-DISCONTINUITY-SEQUENCE:2\n' +
...@@ -332,13 +325,12 @@ function() { ...@@ -332,13 +325,12 @@ function() {
332 QUnit.equal(loader.expired_, 4 + 5 + 6, 'tracked multiple expiring discontinuities'); 325 QUnit.equal(loader.expired_, 4 + 5 + 6, 'tracked multiple expiring discontinuities');
333 }); 326 });
334 327
335 QUnit.test( 328 QUnit.test('estimates expired if an entire window elapses between live playlist updates',
336 'estimates expired if an entire window elapses between live playlist updates',
337 function() { 329 function() {
338 let loader = new PlaylistLoader('live.m3u8'); 330 let loader = new PlaylistLoader('live.m3u8');
339 331
340 loader.trigger('firstplay'); 332 loader.trigger('firstplay');
341 respond(this.requests.pop(), 333 this.requests.pop().respond(200, null,
342 '#EXTM3U\n' + 334 '#EXTM3U\n' +
343 '#EXT-X-MEDIA-SEQUENCE:0\n' + 335 '#EXT-X-MEDIA-SEQUENCE:0\n' +
344 '#EXTINF:4,\n' + 336 '#EXTINF:4,\n' +
...@@ -347,7 +339,7 @@ function() { ...@@ -347,7 +339,7 @@ function() {
347 '1.ts\n'); 339 '1.ts\n');
348 340
349 this.clock.tick(10 * 1000); 341 this.clock.tick(10 * 1000);
350 respond(this.requests.pop(), 342 this.requests.pop().respond(200, null,
351 '#EXTM3U\n' + 343 '#EXTM3U\n' +
352 '#EXT-X-MEDIA-SEQUENCE:4\n' + 344 '#EXT-X-MEDIA-SEQUENCE:4\n' +
353 '#EXTINF:6,\n' + 345 '#EXTINF:6,\n' +
...@@ -380,7 +372,7 @@ QUnit.test('errors when an initial media playlist request fails', function() { ...@@ -380,7 +372,7 @@ QUnit.test('errors when an initial media playlist request fails', function() {
380 loader.on('error', function() { 372 loader.on('error', function() {
381 errors.push(loader.error); 373 errors.push(loader.error);
382 }); 374 });
383 respond(this.requests.pop(), 375 this.requests.pop().respond(200, null,
384 '#EXTM3U\n' + 376 '#EXTM3U\n' +
385 '#EXT-X-STREAM-INF:\n' + 377 '#EXT-X-STREAM-INF:\n' +
386 'media.m3u8\n'); 378 'media.m3u8\n');
...@@ -394,21 +386,20 @@ QUnit.test('errors when an initial media playlist request fails', function() { ...@@ -394,21 +386,20 @@ QUnit.test('errors when an initial media playlist request fails', function() {
394 }); 386 });
395 387
396 // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4 388 // http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4
397 QUnit.test( 389 QUnit.test('halves the refresh timeout if a playlist is unchanged since the last reload',
398 'halves the refresh timeout if a playlist is unchanged since the last reload',
399 function() { 390 function() {
400 /* eslint-disable no-unused-vars */ 391 /* eslint-disable no-unused-vars */
401 let loader = new PlaylistLoader('live.m3u8'); 392 let loader = new PlaylistLoader('live.m3u8');
402 /* eslint-enable no-unused-vars */ 393 /* eslint-enable no-unused-vars */
403 394
404 respond(this.requests.pop(), 395 this.requests.pop().respond(200, null,
405 '#EXTM3U\n' + 396 '#EXTM3U\n' +
406 '#EXT-X-MEDIA-SEQUENCE:0\n' + 397 '#EXT-X-MEDIA-SEQUENCE:0\n' +
407 '#EXTINF:10,\n' + 398 '#EXTINF:10,\n' +
408 '0.ts\n'); 399 '0.ts\n');
409 // trigger a refresh 400 // trigger a refresh
410 this.clock.tick(10 * 1000); 401 this.clock.tick(10 * 1000);
411 respond(this.requests.pop(), 402 this.requests.pop().respond(200, null,
412 '#EXTM3U\n' + 403 '#EXTM3U\n' +
413 '#EXT-X-MEDIA-SEQUENCE:0\n' + 404 '#EXT-X-MEDIA-SEQUENCE:0\n' +
414 '#EXTINF:10,\n' + 405 '#EXTINF:10,\n' +
...@@ -426,7 +417,7 @@ QUnit.test('preserves segment metadata across playlist refreshes', function() { ...@@ -426,7 +417,7 @@ QUnit.test('preserves segment metadata across playlist refreshes', function() {
426 let loader = new PlaylistLoader('live.m3u8'); 417 let loader = new PlaylistLoader('live.m3u8');
427 let segment; 418 let segment;
428 419
429 respond(this.requests.pop(), 420 this.requests.pop().respond(200, null,
430 '#EXTM3U\n' + 421 '#EXTM3U\n' +
431 '#EXT-X-MEDIA-SEQUENCE:0\n' + 422 '#EXT-X-MEDIA-SEQUENCE:0\n' +
432 '#EXTINF:10,\n' + 423 '#EXTINF:10,\n' +
...@@ -443,7 +434,7 @@ QUnit.test('preserves segment metadata across playlist refreshes', function() { ...@@ -443,7 +434,7 @@ QUnit.test('preserves segment metadata across playlist refreshes', function() {
443 434
444 // trigger a refresh 435 // trigger a refresh
445 this.clock.tick(10 * 1000); 436 this.clock.tick(10 * 1000);
446 respond(this.requests.pop(), 437 this.requests.pop().respond(200, null,
447 '#EXTM3U\n' + 438 '#EXTM3U\n' +
448 '#EXT-X-MEDIA-SEQUENCE:1\n' + 439 '#EXT-X-MEDIA-SEQUENCE:1\n' +
449 '#EXTINF:10,\n' + 440 '#EXTINF:10,\n' +
...@@ -463,21 +454,21 @@ QUnit.test('clears the update timeout when switching quality', function() { ...@@ -463,21 +454,21 @@ QUnit.test('clears the update timeout when switching quality', function() {
463 refreshes++; 454 refreshes++;
464 }); 455 });
465 // deliver the master 456 // deliver the master
466 respond(this.requests.pop(), 457 this.requests.pop().respond(200, null,
467 '#EXTM3U\n' + 458 '#EXTM3U\n' +
468 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 459 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
469 'live-low.m3u8\n' + 460 'live-low.m3u8\n' +
470 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 461 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
471 'live-high.m3u8\n'); 462 'live-high.m3u8\n');
472 // deliver the low quality playlist 463 // deliver the low quality playlist
473 respond(this.requests.pop(), 464 this.requests.pop().respond(200, null,
474 '#EXTM3U\n' + 465 '#EXTM3U\n' +
475 '#EXT-X-MEDIA-SEQUENCE:0\n' + 466 '#EXT-X-MEDIA-SEQUENCE:0\n' +
476 '#EXTINF:10,\n' + 467 '#EXTINF:10,\n' +
477 'low-0.ts\n'); 468 'low-0.ts\n');
478 // change to a higher quality playlist 469 // change to a higher quality playlist
479 loader.media('live-high.m3u8'); 470 loader.media('live-high.m3u8');
480 respond(this.requests.pop(), 471 this.requests.pop().respond(200, null,
481 '#EXTM3U\n' + 472 '#EXTM3U\n' +
482 '#EXT-X-MEDIA-SEQUENCE:0\n' + 473 '#EXT-X-MEDIA-SEQUENCE:0\n' +
483 '#EXTINF:10,\n' + 474 '#EXTINF:10,\n' +
...@@ -493,14 +484,14 @@ QUnit.test('media-sequence updates are considered a playlist change', function() ...@@ -493,14 +484,14 @@ QUnit.test('media-sequence updates are considered a playlist change', function()
493 let loader = new PlaylistLoader('live.m3u8'); 484 let loader = new PlaylistLoader('live.m3u8');
494 /* eslint-enable no-unused-vars */ 485 /* eslint-enable no-unused-vars */
495 486
496 respond(this.requests.pop(), 487 this.requests.pop().respond(200, null,
497 '#EXTM3U\n' + 488 '#EXTM3U\n' +
498 '#EXT-X-MEDIA-SEQUENCE:0\n' + 489 '#EXT-X-MEDIA-SEQUENCE:0\n' +
499 '#EXTINF:10,\n' + 490 '#EXTINF:10,\n' +
500 '0.ts\n'); 491 '0.ts\n');
501 // trigger a refresh 492 // trigger a refresh
502 this.clock.tick(10 * 1000); 493 this.clock.tick(10 * 1000);
503 respond(this.requests.pop(), 494 this.requests.pop().respond(200, null,
504 '#EXTM3U\n' + 495 '#EXTM3U\n' +
505 '#EXT-X-MEDIA-SEQUENCE:1\n' + 496 '#EXT-X-MEDIA-SEQUENCE:1\n' +
506 '#EXTINF:10,\n' + 497 '#EXTINF:10,\n' +
...@@ -519,7 +510,7 @@ QUnit.test('emits an error if a media refresh fails', function() { ...@@ -519,7 +510,7 @@ QUnit.test('emits an error if a media refresh fails', function() {
519 loader.on('error', function() { 510 loader.on('error', function() {
520 errors++; 511 errors++;
521 }); 512 });
522 respond(this.requests.pop(), 513 this.requests.pop().respond(200, null,
523 '#EXTM3U\n' + 514 '#EXTM3U\n' +
524 '#EXT-X-MEDIA-SEQUENCE:0\n' + 515 '#EXT-X-MEDIA-SEQUENCE:0\n' +
525 '#EXTINF:10,\n' + 516 '#EXTINF:10,\n' +
...@@ -538,13 +529,13 @@ QUnit.test('emits an error if a media refresh fails', function() { ...@@ -538,13 +529,13 @@ QUnit.test('emits an error if a media refresh fails', function() {
538 QUnit.test('switches media playlists when requested', function() { 529 QUnit.test('switches media playlists when requested', function() {
539 let loader = new PlaylistLoader('master.m3u8'); 530 let loader = new PlaylistLoader('master.m3u8');
540 531
541 respond(this.requests.pop(), 532 this.requests.pop().respond(200, null,
542 '#EXTM3U\n' + 533 '#EXTM3U\n' +
543 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 534 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
544 'low.m3u8\n' + 535 'low.m3u8\n' +
545 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 536 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
546 'high.m3u8\n'); 537 'high.m3u8\n');
547 respond(this.requests.pop(), 538 this.requests.pop().respond(200, null,
548 '#EXTM3U\n' + 539 '#EXTM3U\n' +
549 '#EXT-X-MEDIA-SEQUENCE:0\n' + 540 '#EXT-X-MEDIA-SEQUENCE:0\n' +
550 '#EXTINF:10,\n' + 541 '#EXTINF:10,\n' +
...@@ -553,7 +544,7 @@ QUnit.test('switches media playlists when requested', function() { ...@@ -553,7 +544,7 @@ QUnit.test('switches media playlists when requested', function() {
553 loader.media(loader.master.playlists[1]); 544 loader.media(loader.master.playlists[1]);
554 QUnit.strictEqual(loader.state, 'SWITCHING_MEDIA', 'updated the state'); 545 QUnit.strictEqual(loader.state, 'SWITCHING_MEDIA', 'updated the state');
555 546
556 respond(this.requests.pop(), 547 this.requests.pop().respond(200, null,
557 '#EXTM3U\n' + 548 '#EXTM3U\n' +
558 '#EXT-X-MEDIA-SEQUENCE:0\n' + 549 '#EXT-X-MEDIA-SEQUENCE:0\n' +
559 '#EXTINF:10,\n' + 550 '#EXTINF:10,\n' +
...@@ -570,7 +561,7 @@ QUnit.test('can switch playlists immediately after the master is downloaded', fu ...@@ -570,7 +561,7 @@ QUnit.test('can switch playlists immediately after the master is downloaded', fu
570 loader.on('loadedplaylist', function() { 561 loader.on('loadedplaylist', function() {
571 loader.media('high.m3u8'); 562 loader.media('high.m3u8');
572 }); 563 });
573 respond(this.requests.pop(), 564 this.requests.pop().respond(200, null,
574 '#EXTM3U\n' + 565 '#EXTM3U\n' +
575 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 566 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
576 'low.m3u8\n' + 567 'low.m3u8\n' +
...@@ -582,13 +573,13 @@ QUnit.test('can switch playlists immediately after the master is downloaded', fu ...@@ -582,13 +573,13 @@ QUnit.test('can switch playlists immediately after the master is downloaded', fu
582 QUnit.test('can switch media playlists based on URI', function() { 573 QUnit.test('can switch media playlists based on URI', function() {
583 let loader = new PlaylistLoader('master.m3u8'); 574 let loader = new PlaylistLoader('master.m3u8');
584 575
585 respond(this.requests.pop(), 576 this.requests.pop().respond(200, null,
586 '#EXTM3U\n' + 577 '#EXTM3U\n' +
587 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 578 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
588 'low.m3u8\n' + 579 'low.m3u8\n' +
589 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 580 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
590 'high.m3u8\n'); 581 'high.m3u8\n');
591 respond(this.requests.pop(), 582 this.requests.pop().respond(200, null,
592 '#EXTM3U\n' + 583 '#EXTM3U\n' +
593 '#EXT-X-MEDIA-SEQUENCE:0\n' + 584 '#EXT-X-MEDIA-SEQUENCE:0\n' +
594 '#EXTINF:10,\n' + 585 '#EXTINF:10,\n' +
...@@ -597,7 +588,7 @@ QUnit.test('can switch media playlists based on URI', function() { ...@@ -597,7 +588,7 @@ QUnit.test('can switch media playlists based on URI', function() {
597 loader.media('high.m3u8'); 588 loader.media('high.m3u8');
598 QUnit.strictEqual(loader.state, 'SWITCHING_MEDIA', 'updated the state'); 589 QUnit.strictEqual(loader.state, 'SWITCHING_MEDIA', 'updated the state');
599 590
600 respond(this.requests.pop(), 591 this.requests.pop().respond(200, null,
601 '#EXTM3U\n' + 592 '#EXTM3U\n' +
602 '#EXT-X-MEDIA-SEQUENCE:0\n' + 593 '#EXT-X-MEDIA-SEQUENCE:0\n' +
603 '#EXTINF:10,\n' + 594 '#EXTINF:10,\n' +
...@@ -611,13 +602,13 @@ QUnit.test('can switch media playlists based on URI', function() { ...@@ -611,13 +602,13 @@ QUnit.test('can switch media playlists based on URI', function() {
611 QUnit.test('aborts in-flight playlist refreshes when switching', function() { 602 QUnit.test('aborts in-flight playlist refreshes when switching', function() {
612 let loader = new PlaylistLoader('master.m3u8'); 603 let loader = new PlaylistLoader('master.m3u8');
613 604
614 respond(this.requests.pop(), 605 this.requests.pop().respond(200, null,
615 '#EXTM3U\n' + 606 '#EXTM3U\n' +
616 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 607 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
617 'low.m3u8\n' + 608 'low.m3u8\n' +
618 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 609 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
619 'high.m3u8\n'); 610 'high.m3u8\n');
620 respond(this.requests.pop(), 611 this.requests.pop().respond(200, null,
621 '#EXTM3U\n' + 612 '#EXTM3U\n' +
622 '#EXT-X-MEDIA-SEQUENCE:0\n' + 613 '#EXT-X-MEDIA-SEQUENCE:0\n' +
623 '#EXTINF:10,\n' + 614 '#EXTINF:10,\n' +
...@@ -633,13 +624,13 @@ QUnit.test('aborts in-flight playlist refreshes when switching', function() { ...@@ -633,13 +624,13 @@ QUnit.test('aborts in-flight playlist refreshes when switching', function() {
633 QUnit.test('switching to the active playlist is a no-op', function() { 624 QUnit.test('switching to the active playlist is a no-op', function() {
634 let loader = new PlaylistLoader('master.m3u8'); 625 let loader = new PlaylistLoader('master.m3u8');
635 626
636 respond(this.requests.pop(), 627 this.requests.pop().respond(200, null,
637 '#EXTM3U\n' + 628 '#EXTM3U\n' +
638 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 629 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
639 'low.m3u8\n' + 630 'low.m3u8\n' +
640 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 631 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
641 'high.m3u8\n'); 632 'high.m3u8\n');
642 respond(this.requests.pop(), 633 this.requests.pop().respond(200, null,
643 '#EXTM3U\n' + 634 '#EXTM3U\n' +
644 '#EXT-X-MEDIA-SEQUENCE:0\n' + 635 '#EXT-X-MEDIA-SEQUENCE:0\n' +
645 '#EXTINF:10,\n' + 636 '#EXTINF:10,\n' +
...@@ -647,45 +638,45 @@ QUnit.test('switching to the active playlist is a no-op', function() { ...@@ -647,45 +638,45 @@ QUnit.test('switching to the active playlist is a no-op', function() {
647 '#EXT-X-ENDLIST\n'); 638 '#EXT-X-ENDLIST\n');
648 loader.media('low.m3u8'); 639 loader.media('low.m3u8');
649 640
650 QUnit.strictEqual(this.requests.length, 0, 'no this.requests are sent'); 641 QUnit.strictEqual(this.requests.length, 0, 'no requests are sent');
651 }); 642 });
652 643
653 QUnit.test('switching to the active live playlist is a no-op', function() { 644 QUnit.test('switching to the active live playlist is a no-op', function() {
654 let loader = new PlaylistLoader('master.m3u8'); 645 let loader = new PlaylistLoader('master.m3u8');
655 646
656 respond(this.requests.pop(), 647 this.requests.pop().respond(200, null,
657 '#EXTM3U\n' + 648 '#EXTM3U\n' +
658 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 649 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
659 'low.m3u8\n' + 650 'low.m3u8\n' +
660 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 651 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
661 'high.m3u8\n'); 652 'high.m3u8\n');
662 respond(this.requests.pop(), 653 this.requests.pop().respond(200, null,
663 '#EXTM3U\n' + 654 '#EXTM3U\n' +
664 '#EXT-X-MEDIA-SEQUENCE:0\n' + 655 '#EXT-X-MEDIA-SEQUENCE:0\n' +
665 '#EXTINF:10,\n' + 656 '#EXTINF:10,\n' +
666 'low-0.ts\n'); 657 'low-0.ts\n');
667 loader.media('low.m3u8'); 658 loader.media('low.m3u8');
668 659
669 QUnit.strictEqual(this.requests.length, 0, 'no this.requests are sent'); 660 QUnit.strictEqual(this.requests.length, 0, 'no requests are sent');
670 }); 661 });
671 662
672 QUnit.test('switches back to loaded playlists without re-requesting them', function() { 663 QUnit.test('switches back to loaded playlists without re-requesting them', function() {
673 let loader = new PlaylistLoader('master.m3u8'); 664 let loader = new PlaylistLoader('master.m3u8');
674 665
675 respond(this.requests.pop(), 666 this.requests.pop().respond(200, null,
676 '#EXTM3U\n' + 667 '#EXTM3U\n' +
677 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 668 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
678 'low.m3u8\n' + 669 'low.m3u8\n' +
679 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 670 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
680 'high.m3u8\n'); 671 'high.m3u8\n');
681 respond(this.requests.pop(), 672 this.requests.pop().respond(200, null,
682 '#EXTM3U\n' + 673 '#EXTM3U\n' +
683 '#EXT-X-MEDIA-SEQUENCE:0\n' + 674 '#EXT-X-MEDIA-SEQUENCE:0\n' +
684 '#EXTINF:10,\n' + 675 '#EXTINF:10,\n' +
685 'low-0.ts\n' + 676 'low-0.ts\n' +
686 '#EXT-X-ENDLIST\n'); 677 '#EXT-X-ENDLIST\n');
687 loader.media('high.m3u8'); 678 loader.media('high.m3u8');
688 respond(this.requests.pop(), 679 this.requests.pop().respond(200, null,
689 '#EXTM3U\n' + 680 '#EXTM3U\n' +
690 '#EXT-X-MEDIA-SEQUENCE:0\n' + 681 '#EXT-X-MEDIA-SEQUENCE:0\n' +
691 '#EXTINF:10,\n' + 682 '#EXTINF:10,\n' +
...@@ -693,22 +684,21 @@ QUnit.test('switches back to loaded playlists without re-requesting them', funct ...@@ -693,22 +684,21 @@ QUnit.test('switches back to loaded playlists without re-requesting them', funct
693 '#EXT-X-ENDLIST\n'); 684 '#EXT-X-ENDLIST\n');
694 loader.media('low.m3u8'); 685 loader.media('low.m3u8');
695 686
696 QUnit.strictEqual(this.requests.length, 0, 'no outstanding this.requests'); 687 QUnit.strictEqual(this.requests.length, 0, 'no outstanding requests');
697 QUnit.strictEqual(loader.state, 'HAVE_METADATA', 'returned to loaded playlist'); 688 QUnit.strictEqual(loader.state, 'HAVE_METADATA', 'returned to loaded playlist');
698 }); 689 });
699 690
700 QUnit.test( 691 QUnit.test('aborts outstanding requests if switching back to an already loaded playlist',
701 'aborts outstanding this.requests if switching back to an already loaded playlist',
702 function() { 692 function() {
703 let loader = new PlaylistLoader('master.m3u8'); 693 let loader = new PlaylistLoader('master.m3u8');
704 694
705 respond(this.requests.pop(), 695 this.requests.pop().respond(200, null,
706 '#EXTM3U\n' + 696 '#EXTM3U\n' +
707 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 697 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
708 'low.m3u8\n' + 698 'low.m3u8\n' +
709 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 699 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
710 'high.m3u8\n'); 700 'high.m3u8\n');
711 respond(this.requests.pop(), 701 this.requests.pop().respond(200, null,
712 '#EXTM3U\n' + 702 '#EXTM3U\n' +
713 '#EXT-X-MEDIA-SEQUENCE:0\n' + 703 '#EXT-X-MEDIA-SEQUENCE:0\n' +
714 '#EXTINF:10,\n' + 704 '#EXTINF:10,\n' +
...@@ -732,18 +722,17 @@ function() { ...@@ -732,18 +722,17 @@ function() {
732 'switched to loaded playlist'); 722 'switched to loaded playlist');
733 }); 723 });
734 724
735 QUnit.test( 725 QUnit.test('does not abort requests when the same playlist is re-requested',
736 'does not abort this.requests when the same playlist is re-requested',
737 function() { 726 function() {
738 let loader = new PlaylistLoader('master.m3u8'); 727 let loader = new PlaylistLoader('master.m3u8');
739 728
740 respond(this.requests.pop(), 729 this.requests.pop().respond(200, null,
741 '#EXTM3U\n' + 730 '#EXTM3U\n' +
742 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 731 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
743 'low.m3u8\n' + 732 'low.m3u8\n' +
744 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 733 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
745 'high.m3u8\n'); 734 'high.m3u8\n');
746 respond(this.requests.pop(), 735 this.requests.pop().respond(200, null,
747 '#EXTM3U\n' + 736 '#EXTM3U\n' +
748 '#EXT-X-MEDIA-SEQUENCE:0\n' + 737 '#EXT-X-MEDIA-SEQUENCE:0\n' +
749 '#EXTINF:10,\n' + 738 '#EXTINF:10,\n' +
...@@ -763,7 +752,7 @@ QUnit.test('throws an error if a media switch is initiated too early', function( ...@@ -763,7 +752,7 @@ QUnit.test('throws an error if a media switch is initiated too early', function(
763 loader.media('high.m3u8'); 752 loader.media('high.m3u8');
764 }, 'threw an error from HAVE_NOTHING'); 753 }, 'threw an error from HAVE_NOTHING');
765 754
766 respond(this.requests.pop(), 755 this.requests.pop().respond(200, null,
767 '#EXTM3U\n' + 756 '#EXTM3U\n' +
768 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 757 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
769 'low.m3u8\n' + 758 'low.m3u8\n' +
...@@ -771,12 +760,11 @@ QUnit.test('throws an error if a media switch is initiated too early', function( ...@@ -771,12 +760,11 @@ QUnit.test('throws an error if a media switch is initiated too early', function(
771 'high.m3u8\n'); 760 'high.m3u8\n');
772 }); 761 });
773 762
774 QUnit.test( 763 QUnit.test('throws an error if a switch to an unrecognized playlist is requested',
775 'throws an error if a switch to an unrecognized playlist is requested',
776 function() { 764 function() {
777 let loader = new PlaylistLoader('master.m3u8'); 765 let loader = new PlaylistLoader('master.m3u8');
778 766
779 respond(this.requests.pop(), 767 this.requests.pop().respond(200, null,
780 '#EXTM3U\n' + 768 '#EXTM3U\n' +
781 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 769 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
782 'media.m3u8\n'); 770 'media.m3u8\n');
...@@ -789,7 +777,7 @@ function() { ...@@ -789,7 +777,7 @@ function() {
789 QUnit.test('dispose cancels the refresh timeout', function() { 777 QUnit.test('dispose cancels the refresh timeout', function() {
790 let loader = new PlaylistLoader('live.m3u8'); 778 let loader = new PlaylistLoader('live.m3u8');
791 779
792 respond(this.requests.pop(), 780 this.requests.pop().respond(200, null,
793 '#EXTM3U\n' + 781 '#EXTM3U\n' +
794 '#EXT-X-MEDIA-SEQUENCE:0\n' + 782 '#EXT-X-MEDIA-SEQUENCE:0\n' +
795 '#EXTINF:10,\n' + 783 '#EXTINF:10,\n' +
...@@ -801,10 +789,10 @@ QUnit.test('dispose cancels the refresh timeout', function() { ...@@ -801,10 +789,10 @@ QUnit.test('dispose cancels the refresh timeout', function() {
801 QUnit.strictEqual(this.requests.length, 0, 'no refresh request was made'); 789 QUnit.strictEqual(this.requests.length, 0, 'no refresh request was made');
802 }); 790 });
803 791
804 QUnit.test('dispose aborts pending refresh this.requests', function() { 792 QUnit.test('dispose aborts pending refresh requests', function() {
805 let loader = new PlaylistLoader('live.m3u8'); 793 let loader = new PlaylistLoader('live.m3u8');
806 794
807 respond(this.requests.pop(), 795 this.requests.pop().respond(200, null,
808 '#EXTM3U\n' + 796 '#EXTM3U\n' +
809 '#EXT-X-MEDIA-SEQUENCE:0\n' + 797 '#EXT-X-MEDIA-SEQUENCE:0\n' +
810 '#EXTINF:10,\n' + 798 '#EXTINF:10,\n' +
...@@ -813,13 +801,12 @@ QUnit.test('dispose aborts pending refresh this.requests', function() { ...@@ -813,13 +801,12 @@ QUnit.test('dispose aborts pending refresh this.requests', function() {
813 801
814 loader.dispose(); 802 loader.dispose();
815 QUnit.ok(this.requests[0].aborted, 'refresh request aborted'); 803 QUnit.ok(this.requests[0].aborted, 'refresh request aborted');
816 QUnit.ok( 804 QUnit.ok(!this.requests[0].onreadystatechange,
817 !this.requests[0].onreadystatechange,
818 'onreadystatechange handler should not exist after dispose called' 805 'onreadystatechange handler should not exist after dispose called'
819 ); 806 );
820 }); 807 });
821 808
822 QUnit.test('errors if this.requests take longer than 45s', function() { 809 QUnit.test('errors if requests take longer than 45s', function() {
823 let loader = new PlaylistLoader('media.m3u8'); 810 let loader = new PlaylistLoader('media.m3u8');
824 let errors = 0; 811 let errors = 0;
825 812
...@@ -839,13 +826,13 @@ QUnit.test('triggers an event when the active media changes', function() { ...@@ -839,13 +826,13 @@ QUnit.test('triggers an event when the active media changes', function() {
839 loader.on('mediachange', function() { 826 loader.on('mediachange', function() {
840 mediaChanges++; 827 mediaChanges++;
841 }); 828 });
842 respond(this.requests.pop(), 829 this.requests.pop().respond(200, null,
843 '#EXTM3U\n' + 830 '#EXTM3U\n' +
844 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' + 831 '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
845 'low.m3u8\n' + 832 'low.m3u8\n' +
846 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' + 833 '#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
847 'high.m3u8\n'); 834 'high.m3u8\n');
848 respond(this.requests.shift(), 835 this.requests.shift().respond(200, null,
849 '#EXTM3U\n' + 836 '#EXTM3U\n' +
850 '#EXT-X-MEDIA-SEQUENCE:0\n' + 837 '#EXT-X-MEDIA-SEQUENCE:0\n' +
851 '#EXTINF:10,\n' + 838 '#EXTINF:10,\n' +
...@@ -856,7 +843,7 @@ QUnit.test('triggers an event when the active media changes', function() { ...@@ -856,7 +843,7 @@ QUnit.test('triggers an event when the active media changes', function() {
856 loader.media('high.m3u8'); 843 loader.media('high.m3u8');
857 QUnit.strictEqual(mediaChanges, 0, 'mediachange does not fire immediately'); 844 QUnit.strictEqual(mediaChanges, 0, 'mediachange does not fire immediately');
858 845
859 respond(this.requests.shift(), 846 this.requests.shift().respond(200, null,
860 '#EXTM3U\n' + 847 '#EXTM3U\n' +
861 '#EXT-X-MEDIA-SEQUENCE:0\n' + 848 '#EXT-X-MEDIA-SEQUENCE:0\n' +
862 '#EXTINF:10,\n' + 849 '#EXTINF:10,\n' +
...@@ -876,7 +863,7 @@ QUnit.test('triggers an event when the active media changes', function() { ...@@ -876,7 +863,7 @@ QUnit.test('triggers an event when the active media changes', function() {
876 QUnit.test('can get media index by playback position for non-live videos', function() { 863 QUnit.test('can get media index by playback position for non-live videos', function() {
877 let loader = new PlaylistLoader('media.m3u8'); 864 let loader = new PlaylistLoader('media.m3u8');
878 865
879 respond(this.requests.shift(), 866 this.requests.shift().respond(200, null,
880 '#EXTM3U\n' + 867 '#EXTM3U\n' +
881 '#EXT-X-MEDIA-SEQUENCE:0\n' + 868 '#EXT-X-MEDIA-SEQUENCE:0\n' +
882 '#EXTINF:4,\n' + 869 '#EXTINF:4,\n' +
...@@ -901,7 +888,7 @@ QUnit.test('can get media index by playback position for non-live videos', funct ...@@ -901,7 +888,7 @@ QUnit.test('can get media index by playback position for non-live videos', funct
901 QUnit.test('returns the lower index when calculating for a segment boundary', function() { 888 QUnit.test('returns the lower index when calculating for a segment boundary', function() {
902 let loader = new PlaylistLoader('media.m3u8'); 889 let loader = new PlaylistLoader('media.m3u8');
903 890
904 respond(this.requests.shift(), 891 this.requests.shift().respond(200, null,
905 '#EXTM3U\n' + 892 '#EXTM3U\n' +
906 '#EXT-X-MEDIA-SEQUENCE:0\n' + 893 '#EXT-X-MEDIA-SEQUENCE:0\n' +
907 '#EXTINF:4,\n' + 894 '#EXTINF:4,\n' +
...@@ -914,12 +901,11 @@ QUnit.test('returns the lower index when calculating for a segment boundary', fu ...@@ -914,12 +901,11 @@ QUnit.test('returns the lower index when calculating for a segment boundary', fu
914 QUnit.equal(loader.getMediaIndexForTime_(4.5), 1, 'rounds up at 0.5'); 901 QUnit.equal(loader.getMediaIndexForTime_(4.5), 1, 'rounds up at 0.5');
915 }); 902 });
916 903
917 QUnit.test( 904 QUnit.test('accounts for non-zero starting segment time when calculating media index',
918 'accounts for non-zero starting segment time when calculating media index',
919 function() { 905 function() {
920 let loader = new PlaylistLoader('media.m3u8'); 906 let loader = new PlaylistLoader('media.m3u8');
921 907
922 respond(this.requests.shift(), 908 this.requests.shift().respond(200, null,
923 '#EXTM3U\n' + 909 '#EXTM3U\n' +
924 '#EXT-X-MEDIA-SEQUENCE:1001\n' + 910 '#EXT-X-MEDIA-SEQUENCE:1001\n' +
925 '#EXTINF:4,\n' + 911 '#EXTINF:4,\n' +
...@@ -943,9 +929,6 @@ function() { ...@@ -943,9 +929,6 @@ function() {
943 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 2), 929 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 2),
944 0, 930 0,
945 'calculates within the first segment'); 931 'calculates within the first segment');
946 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 2),
947 0,
948 'calculates within the first segment');
949 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 4), 932 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 4),
950 1, 933 1,
951 'calculates within the second segment'); 934 'calculates within the second segment');
...@@ -961,7 +944,7 @@ QUnit.test('prefers precise segment timing when tracking expired time', function ...@@ -961,7 +944,7 @@ QUnit.test('prefers precise segment timing when tracking expired time', function
961 let loader = new PlaylistLoader('media.m3u8'); 944 let loader = new PlaylistLoader('media.m3u8');
962 945
963 loader.trigger('firstplay'); 946 loader.trigger('firstplay');
964 respond(this.requests.shift(), 947 this.requests.shift().respond(200, null,
965 '#EXTM3U\n' + 948 '#EXTM3U\n' +
966 '#EXT-X-MEDIA-SEQUENCE:1001\n' + 949 '#EXT-X-MEDIA-SEQUENCE:1001\n' +
967 '#EXTINF:4,\n' + 950 '#EXTINF:4,\n' +
...@@ -981,7 +964,7 @@ QUnit.test('prefers precise segment timing when tracking expired time', function ...@@ -981,7 +964,7 @@ QUnit.test('prefers precise segment timing when tracking expired time', function
981 964
982 // trigger a playlist refresh 965 // trigger a playlist refresh
983 this.clock.tick(10 * 1000); 966 this.clock.tick(10 * 1000);
984 respond(this.requests.shift(), 967 this.requests.shift().respond(200, null,
985 '#EXTM3U\n' + 968 '#EXTM3U\n' +
986 '#EXT-X-MEDIA-SEQUENCE:1002\n' + 969 '#EXT-X-MEDIA-SEQUENCE:1002\n' +
987 '#EXTINF:5,\n' + 970 '#EXTINF:5,\n' +
...@@ -994,7 +977,7 @@ QUnit.test('prefers precise segment timing when tracking expired time', function ...@@ -994,7 +977,7 @@ QUnit.test('prefers precise segment timing when tracking expired time', function
994 QUnit.test('accounts for expired time when calculating media index', function() { 977 QUnit.test('accounts for expired time when calculating media index', function() {
995 let loader = new PlaylistLoader('media.m3u8'); 978 let loader = new PlaylistLoader('media.m3u8');
996 979
997 respond(this.requests.shift(), 980 this.requests.shift().respond(200, null,
998 '#EXTM3U\n' + 981 '#EXTM3U\n' +
999 '#EXT-X-MEDIA-SEQUENCE:1001\n' + 982 '#EXT-X-MEDIA-SEQUENCE:1001\n' +
1000 '#EXTINF:4,\n' + 983 '#EXTINF:4,\n' +
...@@ -1015,9 +998,6 @@ QUnit.test('accounts for expired time when calculating media index', function() ...@@ -1015,9 +998,6 @@ QUnit.test('accounts for expired time when calculating media index', function()
1015 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 2), 998 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 2),
1016 0, 999 0,
1017 'calculates within the first segment'); 1000 'calculates within the first segment');
1018 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 2),
1019 0,
1020 'calculates within the first segment');
1021 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 4.5), 1001 QUnit.equal(loader.getMediaIndexForTime_(50 + 100 + 4.5),
1022 1, 1002 1,
1023 'calculates within the second segment'); 1003 'calculates within the second segment');
...@@ -1030,7 +1010,7 @@ QUnit.test('does not misintrepret playlists missing newlines at the end', functi ...@@ -1030,7 +1010,7 @@ QUnit.test('does not misintrepret playlists missing newlines at the end', functi
1030 let loader = new PlaylistLoader('media.m3u8'); 1010 let loader = new PlaylistLoader('media.m3u8');
1031 1011
1032 // no newline 1012 // no newline
1033 respond(this.requests.shift(), 1013 this.requests.shift().respond(200, null,
1034 '#EXTM3U\n' + 1014 '#EXTM3U\n' +
1035 '#EXT-X-MEDIA-SEQUENCE:0\n' + 1015 '#EXT-X-MEDIA-SEQUENCE:0\n' +
1036 '#EXTINF:10,\n' + 1016 '#EXTINF:10,\n' +
......