fixed style issues, bare playlist-loader conversion
fixed pre-existing assertion duplication issues
Showing
3 changed files
with
294 additions
and
317 deletions
... | @@ -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' + | ... | ... |
-
Please register or sign in to post a comment