Reorganized videojs-hls.js to make it a bit more readable and set it up for further simplification
Showing
7 changed files
with
430 additions
and
428 deletions
... | @@ -16,6 +16,7 @@ | ... | @@ -16,6 +16,7 @@ |
16 | <script src="src/videojs-hls.js"></script> | 16 | <script src="src/videojs-hls.js"></script> |
17 | 17 | ||
18 | <!-- segment handling --> | 18 | <!-- segment handling --> |
19 | <script src="src/xhr.js"></script> | ||
19 | <script src="src/flv-tag.js"></script> | 20 | <script src="src/flv-tag.js"></script> |
20 | <script src="src/exp-golomb.js"></script> | 21 | <script src="src/exp-golomb.js"></script> |
21 | <script src="src/h264-stream.js"></script> | 22 | <script src="src/h264-stream.js"></script> | ... | ... |
1 | /* | 1 | /* |
2 | * video-js-hls | 2 | * videojs-hls |
3 | * | 3 | * |
4 | * | 4 | * Copyright (c) 2014 Brightcove |
5 | * Copyright (c) 2013 Brightcove | ||
6 | * All rights reserved. | 5 | * All rights reserved. |
7 | */ | 6 | */ |
8 | 7 | ||
... | @@ -10,273 +9,209 @@ | ... | @@ -10,273 +9,209 @@ |
10 | 'use strict'; | 9 | 'use strict'; |
11 | 10 | ||
12 | var | 11 | var |
13 | |||
14 | // a fudge factor to apply to advertised playlist bitrates to account for | 12 | // a fudge factor to apply to advertised playlist bitrates to account for |
15 | // temporary flucations in client bandwidth | 13 | // temporary flucations in client bandwidth |
16 | bandwidthVariance = 1.1, | 14 | bandwidthVariance = 1.1, |
15 | resolveUrl; | ||
17 | 16 | ||
18 | /** | 17 | videojs.Hls = videojs.Flash.extend({ |
19 | * A comparator function to sort two playlist object by bandwidth. | 18 | init: function(player, options, ready) { |
20 | * @param left {object} a media playlist object | 19 | var |
21 | * @param right {object} a media playlist object | 20 | source = options.source, |
22 | * @return {number} Greater than zero if the bandwidth attribute of | 21 | settings = player.options(); |
23 | * left is greater than the corresponding attribute of right. Less | ||
24 | * than zero if the bandwidth of right is greater than left and | ||
25 | * exactly zero if the two are equal. | ||
26 | */ | ||
27 | playlistBandwidth = function(left, right) { | ||
28 | var leftBandwidth, rightBandwidth; | ||
29 | if (left.attributes && left.attributes.BANDWIDTH) { | ||
30 | leftBandwidth = left.attributes.BANDWIDTH; | ||
31 | } | ||
32 | leftBandwidth = leftBandwidth || window.Number.MAX_VALUE; | ||
33 | if (right.attributes && right.attributes.BANDWIDTH) { | ||
34 | rightBandwidth = right.attributes.BANDWIDTH; | ||
35 | } | ||
36 | rightBandwidth = rightBandwidth || window.Number.MAX_VALUE; | ||
37 | |||
38 | return leftBandwidth - rightBandwidth; | ||
39 | }, | ||
40 | 22 | ||
41 | /** | 23 | player.hls = this; |
42 | * A comparator function to sort two playlist object by resolution (width). | 24 | delete options.source; |
43 | * @param left {object} a media playlist object | 25 | options.swf = settings.flash.swf; |
44 | * @param right {object} a media playlist object | 26 | videojs.Flash.call(this, player, options, ready); |
45 | * @return {number} Greater than zero if the resolution.width attribute of | 27 | options.source = source; |
46 | * left is greater than the corresponding attribute of right. Less | 28 | this.bytesReceived = 0; |
47 | * than zero if the resolution.width of right is greater than left and | ||
48 | * exactly zero if the two are equal. | ||
49 | */ | ||
50 | playlistResolution = function(left, right) { | ||
51 | var leftWidth, rightWidth; | ||
52 | 29 | ||
53 | if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { | 30 | // TODO: After video.js#1347 is pulled in move these to the prototype |
54 | leftWidth = left.attributes.RESOLUTION.width; | 31 | this.currentTime = function() { |
32 | if (this.lastSeekedTime_) { | ||
33 | return this.lastSeekedTime_; | ||
55 | } | 34 | } |
56 | 35 | // currentTime is zero while the tech is initializing | |
57 | leftWidth = leftWidth || window.Number.MAX_VALUE; | 36 | if (!this.el() || !this.el().vjs_getProperty) { |
58 | 37 | return 0; | |
59 | if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { | ||
60 | rightWidth = right.attributes.RESOLUTION.width; | ||
61 | } | 38 | } |
62 | 39 | return this.el().vjs_getProperty('currentTime'); | |
63 | rightWidth = rightWidth || window.Number.MAX_VALUE; | 40 | }; |
64 | 41 | this.setCurrentTime = function(currentTime) { | |
65 | // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions | 42 | if (!(this.playlists && this.playlists.media())) { |
66 | // have the same media dimensions/ resolution | 43 | // return immediately if the metadata is not ready yet |
67 | if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { | 44 | return 0; |
68 | return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; | ||
69 | } else { | ||
70 | return leftWidth - rightWidth; | ||
71 | } | 45 | } |
72 | }, | ||
73 | 46 | ||
74 | xhr, | 47 | // save the seek target so currentTime can report it correctly |
48 | // while the seek is pending | ||
49 | this.lastSeekedTime_ = currentTime; | ||
75 | 50 | ||
76 | /** | 51 | // determine the requested segment |
77 | * TODO - Document this great feature. | 52 | this.mediaIndex = videojs.Hls.getMediaIndexByTime(this.playlists.media(), currentTime); |
78 | * | ||
79 | * @param playlist | ||
80 | * @param time | ||
81 | * @returns int | ||
82 | */ | ||
83 | getMediaIndexByTime = function(playlist, time) { | ||
84 | var index, counter, timeRanges, currentSegmentRange; | ||
85 | 53 | ||
86 | timeRanges = []; | 54 | // abort any segments still being decoded |
87 | for (index = 0; index < playlist.segments.length; index++) { | 55 | this.sourceBuffer.abort(); |
88 | currentSegmentRange = {}; | ||
89 | currentSegmentRange.start = (index === 0) ? 0 : timeRanges[index - 1].end; | ||
90 | currentSegmentRange.end = currentSegmentRange.start + playlist.segments[index].duration; | ||
91 | timeRanges.push(currentSegmentRange); | ||
92 | } | ||
93 | 56 | ||
94 | for (counter = 0; counter < timeRanges.length; counter++) { | 57 | // cancel outstanding requests and buffer appends |
95 | if (time >= timeRanges[counter].start && time < timeRanges[counter].end) { | 58 | if (this.segmentXhr_) { |
96 | return counter; | 59 | this.segmentXhr_.abort(); |
97 | } | ||
98 | } | 60 | } |
99 | 61 | ||
100 | return -1; | 62 | // clear out any buffered segments |
101 | 63 | this.segmentBuffer_ = []; | |
102 | }, | ||
103 | 64 | ||
104 | /** | 65 | // begin filling the buffer at the new position |
105 | * Determine the media index in one playlist that corresponds to a | 66 | this.fillBuffer(currentTime * 1000); |
106 | * specified media index in another. This function can be used to | 67 | }; |
107 | * calculate a new segment position when a playlist is reloaded or a | ||
108 | * variant playlist is becoming active. | ||
109 | * @param mediaIndex {number} the index into the original playlist | ||
110 | * to translate | ||
111 | * @param original {object} the playlist to translate the media | ||
112 | * index from | ||
113 | * @param update {object} the playlist to translate the media index | ||
114 | * to | ||
115 | * @param {number} the corresponding media index in the updated | ||
116 | * playlist | ||
117 | */ | ||
118 | translateMediaIndex = function(mediaIndex, original, update) { | ||
119 | var | ||
120 | i, | ||
121 | originalSegment; | ||
122 | 68 | ||
123 | // no segments have been loaded from the original playlist | 69 | videojs.Hls.prototype.src.call(this, options.source && options.source.src); |
124 | if (mediaIndex === 0) { | ||
125 | return 0; | ||
126 | } | ||
127 | if (!(update && update.segments)) { | ||
128 | // let the media index be zero when there are no segments defined | ||
129 | return 0; | ||
130 | } | 70 | } |
71 | }); | ||
131 | 72 | ||
132 | // try to sync based on URI | 73 | // Add HLS to the standard tech order |
133 | i = update.segments.length; | 74 | videojs.options.techOrder.unshift('hls'); |
134 | originalSegment = original.segments[mediaIndex - 1]; | ||
135 | while (i--) { | ||
136 | if (originalSegment.uri === update.segments[i].uri) { | ||
137 | return i + 1; | ||
138 | } | ||
139 | } | ||
140 | 75 | ||
141 | // sync on media sequence | 76 | // the desired length of video to maintain in the buffer, in seconds |
142 | return (original.mediaSequence + mediaIndex) - update.mediaSequence; | 77 | videojs.Hls.GOAL_BUFFER_LENGTH = 30; |
143 | }, | ||
144 | 78 | ||
145 | /** | 79 | videojs.Hls.prototype.src = function(src) { |
146 | * Calculate the duration of a playlist from a given start index to a given | 80 | var |
147 | * end index. | 81 | self = this, |
148 | * @param playlist {object} a media playlist object | 82 | mediaSource, |
149 | * @param startIndex {number} an inclusive lower boundary for the playlist. | 83 | source; |
150 | * Defaults to 0. | ||
151 | * @param endIndex {number} an exclusive upper boundary for the playlist. | ||
152 | * Defaults to playlist length. | ||
153 | * @return {number} the duration between the start index and end index. | ||
154 | */ | ||
155 | duration = function(playlist, startIndex, endIndex) { | ||
156 | var dur = 0, | ||
157 | segment, | ||
158 | i; | ||
159 | 84 | ||
160 | startIndex = startIndex || 0; | 85 | if (src) { |
161 | endIndex = endIndex !== undefined ? endIndex : (playlist.segments || []).length; | 86 | this.src_ = src; |
162 | i = endIndex - 1; | ||
163 | 87 | ||
164 | for (; i >= startIndex; i--) { | 88 | mediaSource = new videojs.MediaSource(); |
165 | segment = playlist.segments[i]; | 89 | source = { |
166 | dur += segment.duration || playlist.targetDuration || 0; | 90 | src: videojs.URL.createObjectURL(mediaSource), |
167 | } | 91 | type: "video/flv" |
92 | }; | ||
93 | this.mediaSource = mediaSource; | ||
168 | 94 | ||
169 | return dur; | 95 | this.segmentBuffer_ = []; |
170 | }, | 96 | this.segmentParser_ = new videojs.Hls.SegmentParser(); |
171 | 97 | ||
172 | /** | 98 | // load the MediaSource into the player |
173 | * Calculate the total duration for a playlist based on segment metadata. | 99 | this.mediaSource.addEventListener('sourceopen', videojs.bind(this, this.handleSourceOpen)); |
174 | * @param playlist {object} a media playlist object | ||
175 | * @return {number} the currently known duration, in seconds | ||
176 | */ | ||
177 | totalDuration = function(playlist) { | ||
178 | if (!playlist) { | ||
179 | return 0; | ||
180 | } | ||
181 | 100 | ||
182 | // if present, use the duration specified in the playlist | 101 | this.player().ready(function() { |
183 | if (playlist.totalDuration) { | 102 | // do nothing if the tech has been disposed already |
184 | return playlist.totalDuration; | 103 | // this can occur if someone sets the src in player.ready(), for instance |
104 | if (!self.el()) { | ||
105 | return; | ||
185 | } | 106 | } |
186 | 107 | self.el().vjs_src(source.src); | |
187 | // duration should be Infinity for live playlists | 108 | }); |
188 | if (!playlist.endList) { | ||
189 | return window.Infinity; | ||
190 | } | 109 | } |
110 | }; | ||
191 | 111 | ||
192 | return duration(playlist); | 112 | videojs.Hls.prototype.handleSourceOpen = function() { |
193 | }, | 113 | // construct the video data buffer and set the appropriate MIME type |
194 | |||
195 | resolveUrl, | ||
196 | |||
197 | initSource = function(player, mediaSource, srcUrl) { | ||
198 | var | 114 | var |
199 | segmentParser = new videojs.Hls.SegmentParser(), | 115 | player = this.player(), |
200 | settings = videojs.util.mergeOptions({}, player.options().hls), | 116 | settings = player.options().hls || {}, |
201 | segmentBuffer = [], | 117 | sourceBuffer = this.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'), |
118 | oldMediaPlaylist; | ||
202 | 119 | ||
203 | lastSeekedTime, | 120 | this.sourceBuffer = sourceBuffer; |
204 | segmentXhr, | 121 | sourceBuffer.appendBuffer(this.segmentParser_.getFlvHeader()); |
205 | fillBuffer, | ||
206 | drainBuffer, | ||
207 | updateDuration; | ||
208 | 122 | ||
123 | this.mediaIndex = 0; | ||
124 | this.playlists = new videojs.Hls.PlaylistLoader(this.src_, settings.withCredentials); | ||
209 | 125 | ||
210 | player.hls.currentTime = function() { | 126 | this.playlists.on('loadedmetadata', videojs.bind(this, function() { |
211 | if (lastSeekedTime) { | 127 | oldMediaPlaylist = this.playlists.media(); |
212 | return lastSeekedTime; | ||
213 | } | ||
214 | // currentTime is zero while the tech is initializing | ||
215 | if (!this.el() || !this.el().vjs_getProperty) { | ||
216 | return 0; | ||
217 | } | ||
218 | return this.el().vjs_getProperty('currentTime'); | ||
219 | }; | ||
220 | 128 | ||
221 | player.hls.setCurrentTime = function(currentTime) { | 129 | // periodically check if new data needs to be downloaded or |
222 | if (!(this.playlists && this.playlists.media())) { | 130 | // buffered data should be appended to the source buffer |
223 | // return immediately if the metadata is not ready yet | 131 | this.fillBuffer(); |
224 | return 0; | 132 | player.on('timeupdate', videojs.bind(this, this.fillBuffer)); |
225 | } | 133 | player.on('timeupdate', videojs.bind(this, this.drainBuffer)); |
134 | player.on('waiting', videojs.bind(this, this.drainBuffer)); | ||
226 | 135 | ||
227 | // save the seek target so currentTime can report it correctly | 136 | player.trigger('loadedmetadata'); |
228 | // while the seek is pending | 137 | })); |
229 | lastSeekedTime = currentTime; | ||
230 | 138 | ||
231 | // determine the requested segment | 139 | this.playlists.on('error', videojs.bind(this, function() { |
232 | this.mediaIndex = | 140 | player.error(this.playlists.error); |
233 | getMediaIndexByTime(this.playlists.media(), currentTime); | 141 | })); |
234 | 142 | ||
235 | // abort any segments still being decoded | 143 | this.playlists.on('loadedplaylist', videojs.bind(this, function() { |
236 | this.sourceBuffer.abort(); | 144 | var updatedPlaylist = this.playlists.media(); |
237 | 145 | ||
238 | // cancel outstanding requests and buffer appends | 146 | if (!updatedPlaylist) { |
239 | if (segmentXhr) { | 147 | // do nothing before an initial media playlist has been activated |
240 | segmentXhr.abort(); | 148 | return; |
241 | } | 149 | } |
242 | 150 | ||
243 | // clear out any buffered segments | 151 | this.updateDuration(this.playlists.media()); |
244 | segmentBuffer = []; | 152 | this.mediaIndex = videojs.Hls.translateMediaIndex(this.mediaIndex, oldMediaPlaylist, updatedPlaylist); |
153 | oldMediaPlaylist = updatedPlaylist; | ||
154 | })); | ||
245 | 155 | ||
246 | // begin filling the buffer at the new position | 156 | this.playlists.on('mediachange', function() { |
247 | fillBuffer(currentTime * 1000); | 157 | player.trigger('mediachange'); |
248 | }; | 158 | }); |
159 | }; | ||
160 | |||
161 | videojs.Hls.prototype.duration = function() { | ||
162 | var playlists = this.playlists; | ||
163 | if (playlists) { | ||
164 | return videojs.Hls.getPlaylistTotalDuration(playlists.media()); | ||
165 | } | ||
166 | return 0; | ||
167 | }; | ||
249 | 168 | ||
250 | /** | 169 | /** |
251 | * Update the player duration | 170 | * Update the player duration |
252 | */ | 171 | */ |
253 | updateDuration = function(playlist) { | 172 | videojs.Hls.prototype.updateDuration = function(playlist) { |
254 | var oldDuration = player.duration(), | 173 | var player = this.player(), |
255 | newDuration = totalDuration(playlist); | 174 | oldDuration = player.duration(), |
175 | newDuration = videojs.Hls.getPlaylistTotalDuration(playlist); | ||
256 | 176 | ||
257 | // if the duration has changed, invalidate the cached value | 177 | // if the duration has changed, invalidate the cached value |
258 | if (oldDuration !== newDuration) { | 178 | if (oldDuration !== newDuration) { |
259 | player.trigger('durationchange'); | 179 | player.trigger('durationchange'); |
260 | } | 180 | } |
261 | }; | 181 | }; |
262 | 182 | ||
263 | /** | 183 | /** |
184 | * Abort all outstanding work and cleanup. | ||
185 | */ | ||
186 | videojs.Hls.prototype.dispose = function() { | ||
187 | if (this.segmentXhr_) { | ||
188 | this.segmentXhr_.onreadystatechange = null; | ||
189 | this.segmentXhr_.abort(); | ||
190 | } | ||
191 | if (this.playlists) { | ||
192 | this.playlists.dispose(); | ||
193 | } | ||
194 | videojs.Flash.prototype.dispose.call(this); | ||
195 | }; | ||
196 | |||
197 | /** | ||
264 | * Chooses the appropriate media playlist based on the current | 198 | * Chooses the appropriate media playlist based on the current |
265 | * bandwidth estimate and the player size. | 199 | * bandwidth estimate and the player size. |
266 | * @return the highest bitrate playlist less than the currently detected | 200 | * @return the highest bitrate playlist less than the currently detected |
267 | * bandwidth, accounting for some amount of bandwidth variance | 201 | * bandwidth, accounting for some amount of bandwidth variance |
268 | */ | 202 | */ |
269 | player.hls.selectPlaylist = function () { | 203 | videojs.Hls.prototype.selectPlaylist = function () { |
270 | var | 204 | var |
205 | player = this.player(), | ||
271 | effectiveBitrate, | 206 | effectiveBitrate, |
272 | sortedPlaylists = player.hls.playlists.master.playlists.slice(), | 207 | sortedPlaylists = this.playlists.master.playlists.slice(), |
273 | bandwidthPlaylists = [], | 208 | bandwidthPlaylists = [], |
274 | i = sortedPlaylists.length, | 209 | i = sortedPlaylists.length, |
275 | variant, | 210 | variant, |
276 | bandwidthBestVariant, | 211 | bandwidthBestVariant, |
277 | resolutionBestVariant; | 212 | resolutionBestVariant; |
278 | 213 | ||
279 | sortedPlaylists.sort(playlistBandwidth); | 214 | sortedPlaylists.sort(videojs.Hls.comparePlaylistBandwidth); |
280 | 215 | ||
281 | // filter out any variant that has greater effective bitrate | 216 | // filter out any variant that has greater effective bitrate |
282 | // than the current estimated bandwidth | 217 | // than the current estimated bandwidth |
... | @@ -304,7 +239,7 @@ var | ... | @@ -304,7 +239,7 @@ var |
304 | i = bandwidthPlaylists.length; | 239 | i = bandwidthPlaylists.length; |
305 | 240 | ||
306 | // sort variants by resolution | 241 | // sort variants by resolution |
307 | bandwidthPlaylists.sort(playlistResolution); | 242 | bandwidthPlaylists.sort(videojs.Hls.comparePlaylistResolution); |
308 | 243 | ||
309 | // iterate through the bandwidth-filtered playlists and find | 244 | // iterate through the bandwidth-filtered playlists and find |
310 | // best rendition by player dimension | 245 | // best rendition by player dimension |
... | @@ -331,30 +266,19 @@ var | ... | @@ -331,30 +266,19 @@ var |
331 | 266 | ||
332 | // fallback chain of variants | 267 | // fallback chain of variants |
333 | return resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0]; | 268 | return resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0]; |
334 | }; | 269 | }; |
335 | |||
336 | /** | ||
337 | * Abort all outstanding work and cleanup. | ||
338 | */ | ||
339 | player.hls.dispose = function() { | ||
340 | if (segmentXhr) { | ||
341 | segmentXhr.onreadystatechange = null; | ||
342 | segmentXhr.abort(); | ||
343 | } | ||
344 | if (this.playlists) { | ||
345 | this.playlists.dispose(); | ||
346 | } | ||
347 | videojs.Flash.prototype.dispose.call(this); | ||
348 | }; | ||
349 | 270 | ||
350 | /** | 271 | /** |
351 | * Determines whether there is enough video data currently in the buffer | 272 | * Determines whether there is enough video data currently in the buffer |
352 | * and downloads a new segment if the buffered time is less than the goal. | 273 | * and downloads a new segment if the buffered time is less than the goal. |
353 | * @param offset (optional) {number} the offset into the downloaded segment | 274 | * @param offset (optional) {number} the offset into the downloaded segment |
354 | * to seek to, in milliseconds | 275 | * to seek to, in milliseconds |
355 | */ | 276 | */ |
356 | fillBuffer = function(offset) { | 277 | videojs.Hls.prototype.fillBuffer = function(offset) { |
357 | var | 278 | var |
279 | self = this, | ||
280 | player = this.player(), | ||
281 | settings = player.options().hls || {}, | ||
358 | buffered = player.buffered(), | 282 | buffered = player.buffered(), |
359 | bufferedTime = 0, | 283 | bufferedTime = 0, |
360 | segment, | 284 | segment, |
... | @@ -362,18 +286,18 @@ var | ... | @@ -362,18 +286,18 @@ var |
362 | startTime; | 286 | startTime; |
363 | 287 | ||
364 | // if there is a request already in flight, do nothing | 288 | // if there is a request already in flight, do nothing |
365 | if (segmentXhr) { | 289 | if (this.segmentXhr_) { |
366 | return; | 290 | return; |
367 | } | 291 | } |
368 | 292 | ||
369 | // if no segments are available, do nothing | 293 | // if no segments are available, do nothing |
370 | if (player.hls.playlists.state === "HAVE_NOTHING" || | 294 | if (this.playlists.state === "HAVE_NOTHING" || |
371 | !player.hls.playlists.media().segments) { | 295 | !this.playlists.media().segments) { |
372 | return; | 296 | return; |
373 | } | 297 | } |
374 | 298 | ||
375 | // if the video has finished downloading, stop trying to buffer | 299 | // if the video has finished downloading, stop trying to buffer |
376 | segment = player.hls.playlists.media().segments[player.hls.mediaIndex]; | 300 | segment = this.playlists.media().segments[this.mediaIndex]; |
377 | if (!segment) { | 301 | if (!segment) { |
378 | return; | 302 | return; |
379 | } | 303 | } |
... | @@ -391,17 +315,17 @@ var | ... | @@ -391,17 +315,17 @@ var |
391 | } | 315 | } |
392 | 316 | ||
393 | // resolve the segment URL relative to the playlist | 317 | // resolve the segment URL relative to the playlist |
394 | if (player.hls.playlists.media().uri === srcUrl) { | 318 | if (this.playlists.media().uri === this.src_) { |
395 | segmentUri = resolveUrl(srcUrl, segment.uri); | 319 | segmentUri = resolveUrl(this.src_, segment.uri); |
396 | } else { | 320 | } else { |
397 | segmentUri = resolveUrl(resolveUrl(srcUrl, player.hls.playlists.media().uri || ''), | 321 | segmentUri = resolveUrl(resolveUrl(this.src_, this.playlists.media().uri || ''), |
398 | segment.uri); | 322 | segment.uri); |
399 | } | 323 | } |
400 | 324 | ||
401 | startTime = +new Date(); | 325 | startTime = +new Date(); |
402 | 326 | ||
403 | // request the next segment | 327 | // request the next segment |
404 | segmentXhr = xhr({ | 328 | this.segmentXhr_ = videojs.Hls.xhr({ |
405 | url: segmentUri, | 329 | url: segmentUri, |
406 | responseType: 'arraybuffer', | 330 | responseType: 'arraybuffer', |
407 | withCredentials: settings.withCredentials | 331 | withCredentials: settings.withCredentials |
... | @@ -409,23 +333,23 @@ var | ... | @@ -409,23 +333,23 @@ var |
409 | var tags; | 333 | var tags; |
410 | 334 | ||
411 | // the segment request is no longer outstanding | 335 | // the segment request is no longer outstanding |
412 | segmentXhr = null; | 336 | self.segmentXhr_ = null; |
413 | 337 | ||
414 | if (error) { | 338 | if (error) { |
415 | // if a segment request times out, we may have better luck with another playlist | 339 | // if a segment request times out, we may have better luck with another playlist |
416 | if (error === 'timeout') { | 340 | if (error === 'timeout') { |
417 | player.hls.bandwidth = 1; | 341 | self.bandwidth = 1; |
418 | return player.hls.playlists.media(player.hls.selectPlaylist()); | 342 | return self.playlists.media(self.selectPlaylist()); |
419 | } | 343 | } |
420 | // otherwise, try jumping ahead to the next segment | 344 | // otherwise, try jumping ahead to the next segment |
421 | player.hls.error = { | 345 | self.error = { |
422 | status: this.status, | 346 | status: this.status, |
423 | message: 'HLS segment request error at URL: ' + url, | 347 | message: 'HLS segment request error at URL: ' + url, |
424 | code: (this.status >= 500) ? 4 : 2 | 348 | code: (this.status >= 500) ? 4 : 2 |
425 | }; | 349 | }; |
426 | 350 | ||
427 | // try moving on to the next segment | 351 | // try moving on to the next segment |
428 | player.hls.mediaIndex++; | 352 | self.mediaIndex++; |
429 | return; | 353 | return; |
430 | } | 354 | } |
431 | 355 | ||
... | @@ -435,39 +359,39 @@ var | ... | @@ -435,39 +359,39 @@ var |
435 | } | 359 | } |
436 | 360 | ||
437 | // calculate the download bandwidth | 361 | // calculate the download bandwidth |
438 | player.hls.segmentXhrTime = (+new Date()) - startTime; | 362 | self.segmentXhrTime = (+new Date()) - startTime; |
439 | player.hls.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000; | 363 | self.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000; |
440 | player.hls.bytesReceived += this.response.byteLength; | 364 | self.bytesReceived += this.response.byteLength; |
441 | 365 | ||
442 | // transmux the segment data from MP2T to FLV | 366 | // transmux the segment data from MP2T to FLV |
443 | segmentParser.parseSegmentBinaryData(new Uint8Array(this.response)); | 367 | self.segmentParser_.parseSegmentBinaryData(new Uint8Array(this.response)); |
444 | segmentParser.flushTags(); | 368 | self.segmentParser_.flushTags(); |
445 | 369 | ||
446 | // package up all the work to append the segment | 370 | // package up all the work to append the segment |
447 | // if the segment is the start of a timestamp discontinuity, | 371 | // if the segment is the start of a timestamp discontinuity, |
448 | // we have to wait until the sourcebuffer is empty before | 372 | // we have to wait until the sourcebuffer is empty before |
449 | // aborting the source buffer processing | 373 | // aborting the source buffer processing |
450 | tags = []; | 374 | tags = []; |
451 | while (segmentParser.tagsAvailable()) { | 375 | while (self.segmentParser_.tagsAvailable()) { |
452 | tags.push(segmentParser.getNextTag()); | 376 | tags.push(self.segmentParser_.getNextTag()); |
453 | } | 377 | } |
454 | segmentBuffer.push({ | 378 | self.segmentBuffer_.push({ |
455 | mediaIndex: player.hls.mediaIndex, | 379 | mediaIndex: self.mediaIndex, |
456 | playlist: player.hls.playlists.media(), | 380 | playlist: self.playlists.media(), |
457 | offset: offset, | 381 | offset: offset, |
458 | tags: tags | 382 | tags: tags |
459 | }); | 383 | }); |
460 | drainBuffer(); | 384 | self.drainBuffer(); |
461 | 385 | ||
462 | player.hls.mediaIndex++; | 386 | self.mediaIndex++; |
463 | 387 | ||
464 | // figure out what stream the next segment should be downloaded from | 388 | // figure out what stream the next segment should be downloaded from |
465 | // with the updated bandwidth information | 389 | // with the updated bandwidth information |
466 | player.hls.playlists.media(player.hls.selectPlaylist()); | 390 | self.playlists.media(self.selectPlaylist()); |
467 | }); | 391 | }); |
468 | }; | 392 | }; |
469 | 393 | ||
470 | drainBuffer = function(event) { | 394 | videojs.Hls.prototype.drainBuffer = function(event) { |
471 | var | 395 | var |
472 | i = 0, | 396 | i = 0, |
473 | mediaIndex, | 397 | mediaIndex, |
... | @@ -477,7 +401,8 @@ var | ... | @@ -477,7 +401,8 @@ var |
477 | segment, | 401 | segment, |
478 | 402 | ||
479 | ptsTime, | 403 | ptsTime, |
480 | segmentOffset; | 404 | segmentOffset, |
405 | segmentBuffer = this.segmentBuffer_; | ||
481 | 406 | ||
482 | if (!segmentBuffer.length) { | 407 | if (!segmentBuffer.length) { |
483 | return; | 408 | return; |
... | @@ -490,7 +415,7 @@ var | ... | @@ -490,7 +415,7 @@ var |
490 | segment = playlist.segments[mediaIndex]; | 415 | segment = playlist.segments[mediaIndex]; |
491 | 416 | ||
492 | event = event || {}; | 417 | event = event || {}; |
493 | segmentOffset = duration(playlist, 0, mediaIndex) * 1000; | 418 | segmentOffset = videojs.Hls.getPlaylistDuration(playlist, 0, mediaIndex) * 1000; |
494 | 419 | ||
495 | // abort() clears any data queued in the source buffer so wait | 420 | // abort() clears any data queued in the source buffer so wait |
496 | // until it empties before calling it when a discontinuity is | 421 | // until it empties before calling it when a discontinuity is |
... | @@ -499,9 +424,9 @@ var | ... | @@ -499,9 +424,9 @@ var |
499 | if (event.type !== 'waiting') { | 424 | if (event.type !== 'waiting') { |
500 | return; | 425 | return; |
501 | } | 426 | } |
502 | player.hls.sourceBuffer.abort(); | 427 | this.sourceBuffer.abort(); |
503 | // tell the SWF where playback is continuing in the stitched timeline | 428 | // tell the SWF where playback is continuing in the stitched timeline |
504 | player.hls.el().vjs_setProperty('currentTime', segmentOffset * 0.001); | 429 | this.el().vjs_setProperty('currentTime', segmentOffset * 0.001); |
505 | } | 430 | } |
506 | 431 | ||
507 | // if we're refilling the buffer after a seek, scan through the muxed | 432 | // if we're refilling the buffer after a seek, scan through the muxed |
... | @@ -515,11 +440,11 @@ var | ... | @@ -515,11 +440,11 @@ var |
515 | } | 440 | } |
516 | 441 | ||
517 | // tell the SWF where we will be seeking to | 442 | // tell the SWF where we will be seeking to |
518 | player.hls.el().vjs_setProperty('currentTime', (tags[i].pts - tags[0].pts + segmentOffset) * 0.001); | 443 | this.el().vjs_setProperty('currentTime', (tags[i].pts - tags[0].pts + segmentOffset) * 0.001); |
519 | 444 | ||
520 | tags = tags.slice(i); | 445 | tags = tags.slice(i); |
521 | 446 | ||
522 | lastSeekedTime = null; | 447 | this.lastSeekedTime_ = null; |
523 | } | 448 | } |
524 | 449 | ||
525 | for (i = 0; i < tags.length; i++) { | 450 | for (i = 0; i < tags.length; i++) { |
... | @@ -527,83 +452,16 @@ var | ... | @@ -527,83 +452,16 @@ var |
527 | // the queue gives control back to the browser between tags | 452 | // the queue gives control back to the browser between tags |
528 | // so that large segments don't cause a "hiccup" in playback | 453 | // so that large segments don't cause a "hiccup" in playback |
529 | 454 | ||
530 | player.hls.sourceBuffer.appendBuffer(tags[i].bytes, player); | 455 | this.sourceBuffer.appendBuffer(tags[i].bytes, this.player()); |
531 | } | 456 | } |
532 | 457 | ||
533 | // we're done processing this segment | 458 | // we're done processing this segment |
534 | segmentBuffer.shift(); | 459 | segmentBuffer.shift(); |
535 | 460 | ||
536 | if (mediaIndex === playlist.segments.length) { | 461 | if (mediaIndex === playlist.segments.length) { |
537 | mediaSource.endOfStream(); | 462 | this.mediaSource.endOfStream(); |
538 | } | ||
539 | }; | ||
540 | |||
541 | // load the MediaSource into the player | ||
542 | mediaSource.addEventListener('sourceopen', function() { | ||
543 | // construct the video data buffer and set the appropriate MIME type | ||
544 | var | ||
545 | sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'), | ||
546 | oldMediaPlaylist; | ||
547 | |||
548 | player.hls.sourceBuffer = sourceBuffer; | ||
549 | sourceBuffer.appendBuffer(segmentParser.getFlvHeader()); | ||
550 | |||
551 | player.hls.mediaIndex = 0; | ||
552 | player.hls.playlists = | ||
553 | new videojs.Hls.PlaylistLoader(srcUrl, settings.withCredentials); | ||
554 | player.hls.playlists.on('loadedmetadata', function() { | ||
555 | oldMediaPlaylist = player.hls.playlists.media(); | ||
556 | |||
557 | // periodically check if new data needs to be downloaded or | ||
558 | // buffered data should be appended to the source buffer | ||
559 | fillBuffer(); | ||
560 | player.on('timeupdate', fillBuffer); | ||
561 | player.on('timeupdate', drainBuffer); | ||
562 | player.on('waiting', drainBuffer); | ||
563 | |||
564 | player.trigger('loadedmetadata'); | ||
565 | }); | ||
566 | player.hls.playlists.on('error', function() { | ||
567 | player.error(player.hls.playlists.error); | ||
568 | }); | ||
569 | player.hls.playlists.on('loadedplaylist', function() { | ||
570 | var updatedPlaylist = player.hls.playlists.media(); | ||
571 | |||
572 | if (!updatedPlaylist) { | ||
573 | // do nothing before an initial media playlist has been activated | ||
574 | return; | ||
575 | } | 463 | } |
576 | 464 | }; | |
577 | updateDuration(player.hls.playlists.media()); | ||
578 | player.hls.mediaIndex = translateMediaIndex(player.hls.mediaIndex, | ||
579 | oldMediaPlaylist, | ||
580 | updatedPlaylist); | ||
581 | oldMediaPlaylist = updatedPlaylist; | ||
582 | }); | ||
583 | player.hls.playlists.on('mediachange', function() { | ||
584 | player.trigger('mediachange'); | ||
585 | }); | ||
586 | }); | ||
587 | }; | ||
588 | |||
589 | var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; | ||
590 | |||
591 | videojs.Hls = videojs.Flash.extend({ | ||
592 | init: function(player, options, ready) { | ||
593 | var | ||
594 | source = options.source, | ||
595 | settings = player.options(); | ||
596 | |||
597 | player.hls = this; | ||
598 | delete options.source; | ||
599 | options.swf = settings.flash.swf; | ||
600 | videojs.Flash.call(this, player, options, ready); | ||
601 | options.source = source; | ||
602 | this.bytesReceived = 0; | ||
603 | |||
604 | videojs.Hls.prototype.src.call(this, options.source && options.source.src); | ||
605 | } | ||
606 | }); | ||
607 | 465 | ||
608 | /** | 466 | /** |
609 | * Whether the browser has built-in HLS support. | 467 | * Whether the browser has built-in HLS support. |
... | @@ -625,43 +483,6 @@ videojs.Hls.supportsNativeHls = (function() { | ... | @@ -625,43 +483,6 @@ videojs.Hls.supportsNativeHls = (function() { |
625 | (/probably|maybe/).test(vndMpeg); | 483 | (/probably|maybe/).test(vndMpeg); |
626 | })(); | 484 | })(); |
627 | 485 | ||
628 | // the desired length of video to maintain in the buffer, in seconds | ||
629 | videojs.Hls.GOAL_BUFFER_LENGTH = 30; | ||
630 | |||
631 | videojs.Hls.prototype.src = function(src) { | ||
632 | var | ||
633 | player = this.player(), | ||
634 | self = this, | ||
635 | mediaSource, | ||
636 | source; | ||
637 | |||
638 | if (src) { | ||
639 | mediaSource = new videojs.MediaSource(); | ||
640 | source = { | ||
641 | src: videojs.URL.createObjectURL(mediaSource), | ||
642 | type: "video/flv" | ||
643 | }; | ||
644 | this.mediaSource = mediaSource; | ||
645 | initSource(player, mediaSource, src); | ||
646 | this.player().ready(function() { | ||
647 | // do nothing if the tech has been disposed already | ||
648 | // this can occur if someone sets the src in player.ready(), for instance | ||
649 | if (!self.el()) { | ||
650 | return; | ||
651 | } | ||
652 | self.el().vjs_src(source.src); | ||
653 | }); | ||
654 | } | ||
655 | }; | ||
656 | |||
657 | videojs.Hls.prototype.duration = function() { | ||
658 | var playlists = this.playlists; | ||
659 | if (playlists) { | ||
660 | return totalDuration(playlists.media()); | ||
661 | } | ||
662 | return 0; | ||
663 | }; | ||
664 | |||
665 | videojs.Hls.isSupported = function() { | 486 | videojs.Hls.isSupported = function() { |
666 | return !videojs.Hls.supportsNativeHls && | 487 | return !videojs.Hls.supportsNativeHls && |
667 | videojs.Flash.isSupported() && | 488 | videojs.Flash.isSupported() && |
... | @@ -669,89 +490,182 @@ videojs.Hls.isSupported = function() { | ... | @@ -669,89 +490,182 @@ videojs.Hls.isSupported = function() { |
669 | }; | 490 | }; |
670 | 491 | ||
671 | videojs.Hls.canPlaySource = function(srcObj) { | 492 | videojs.Hls.canPlaySource = function(srcObj) { |
493 | var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; | ||
672 | return mpegurlRE.test(srcObj.type); | 494 | return mpegurlRE.test(srcObj.type); |
673 | }; | 495 | }; |
674 | 496 | ||
675 | /** | 497 | /** |
676 | * Creates and sends an XMLHttpRequest. | 498 | * Calculate the duration of a playlist from a given start index to a given |
677 | * @param options {string | object} if this argument is a string, it | 499 | * end index. |
678 | * is intrepreted as a URL and a simple GET request is | 500 | * @param playlist {object} a media playlist object |
679 | * inititated. If it is an object, it should contain a `url` | 501 | * @param startIndex {number} an inclusive lower boundary for the playlist. |
680 | * property that indicates the URL to request and optionally a | 502 | * Defaults to 0. |
681 | * `method` which is the type of HTTP request to send. | 503 | * @param endIndex {number} an exclusive upper boundary for the playlist. |
682 | * @param callback (optional) {function} a function to call when the | 504 | * Defaults to playlist length. |
683 | * request completes. If the request was not successful, the first | 505 | * @return {number} the duration between the start index and end index. |
684 | * argument will be falsey. | ||
685 | * @return {object} the XMLHttpRequest that was initiated. | ||
686 | */ | 506 | */ |
687 | xhr = videojs.Hls.xhr = function(url, callback) { | 507 | videojs.Hls.getPlaylistDuration = function(playlist, startIndex, endIndex) { |
688 | var | 508 | var dur = 0, |
689 | options = { | 509 | segment, |
690 | method: 'GET', | 510 | i; |
691 | timeout: 45 * 1000 | ||
692 | }, | ||
693 | request, | ||
694 | abortTimeout; | ||
695 | 511 | ||
696 | if (typeof callback !== 'function') { | 512 | startIndex = startIndex || 0; |
697 | callback = function() {}; | 513 | endIndex = endIndex !== undefined ? endIndex : (playlist.segments || []).length; |
514 | i = endIndex - 1; | ||
515 | |||
516 | for (; i >= startIndex; i--) { | ||
517 | segment = playlist.segments[i]; | ||
518 | dur += segment.duration || playlist.targetDuration || 0; | ||
698 | } | 519 | } |
699 | 520 | ||
700 | if (typeof url === 'object') { | 521 | return dur; |
701 | options = videojs.util.mergeOptions(options, url); | 522 | }; |
702 | url = options.url; | 523 | |
524 | /** | ||
525 | * Calculate the total duration for a playlist based on segment metadata. | ||
526 | * @param playlist {object} a media playlist object | ||
527 | * @return {number} the currently known duration, in seconds | ||
528 | */ | ||
529 | videojs.Hls.getPlaylistTotalDuration = function(playlist) { | ||
530 | if (!playlist) { | ||
531 | return 0; | ||
703 | } | 532 | } |
704 | 533 | ||
705 | request = new window.XMLHttpRequest(); | 534 | // if present, use the duration specified in the playlist |
706 | request.open(options.method, url); | 535 | if (playlist.totalDuration) { |
707 | request.url = url; | 536 | return playlist.totalDuration; |
537 | } | ||
708 | 538 | ||
709 | if (options.responseType) { | 539 | // duration should be Infinity for live playlists |
710 | request.responseType = options.responseType; | 540 | if (!playlist.endList) { |
541 | return window.Infinity; | ||
711 | } | 542 | } |
712 | if (options.withCredentials) { | 543 | |
713 | request.withCredentials = true; | 544 | return videojs.Hls.getPlaylistDuration(playlist); |
545 | }; | ||
546 | |||
547 | /** | ||
548 | * Determine the media index in one playlist that corresponds to a | ||
549 | * specified media index in another. This function can be used to | ||
550 | * calculate a new segment position when a playlist is reloaded or a | ||
551 | * variant playlist is becoming active. | ||
552 | * @param mediaIndex {number} the index into the original playlist | ||
553 | * to translate | ||
554 | * @param original {object} the playlist to translate the media | ||
555 | * index from | ||
556 | * @param update {object} the playlist to translate the media index | ||
557 | * to | ||
558 | * @param {number} the corresponding media index in the updated | ||
559 | * playlist | ||
560 | */ | ||
561 | videojs.Hls.translateMediaIndex = function(mediaIndex, original, update) { | ||
562 | var | ||
563 | i, | ||
564 | originalSegment; | ||
565 | |||
566 | // no segments have been loaded from the original playlist | ||
567 | if (mediaIndex === 0) { | ||
568 | return 0; | ||
714 | } | 569 | } |
715 | if (options.timeout) { | 570 | if (!(update && update.segments)) { |
716 | if (request.timeout === 0) { | 571 | // let the media index be zero when there are no segments defined |
717 | request.timeout = options.timeout; | 572 | return 0; |
718 | request.ontimeout = function() { | ||
719 | request.timedout = true; | ||
720 | }; | ||
721 | } else { | ||
722 | // polyfill XHR2 by aborting after the timeout | ||
723 | abortTimeout = window.setTimeout(function() { | ||
724 | if (request.readyState !== 4) { | ||
725 | request.timedout = true; | ||
726 | request.abort(); | ||
727 | } | 573 | } |
728 | }, options.timeout); | 574 | |
575 | // try to sync based on URI | ||
576 | i = update.segments.length; | ||
577 | originalSegment = original.segments[mediaIndex - 1]; | ||
578 | while (i--) { | ||
579 | if (originalSegment.uri === update.segments[i].uri) { | ||
580 | return i + 1; | ||
729 | } | 581 | } |
730 | } | 582 | } |
731 | 583 | ||
732 | request.onreadystatechange = function() { | 584 | // sync on media sequence |
733 | // wait until the request completes | 585 | return (original.mediaSequence + mediaIndex) - update.mediaSequence; |
734 | if (this.readyState !== 4) { | 586 | }; |
735 | return; | 587 | |
588 | /** | ||
589 | * TODO - Document this great feature. | ||
590 | * | ||
591 | * @param playlist | ||
592 | * @param time | ||
593 | * @returns int | ||
594 | */ | ||
595 | videojs.Hls.getMediaIndexByTime = function(playlist, time) { | ||
596 | var index, counter, timeRanges, currentSegmentRange; | ||
597 | |||
598 | timeRanges = []; | ||
599 | for (index = 0; index < playlist.segments.length; index++) { | ||
600 | currentSegmentRange = {}; | ||
601 | currentSegmentRange.start = (index === 0) ? 0 : timeRanges[index - 1].end; | ||
602 | currentSegmentRange.end = currentSegmentRange.start + playlist.segments[index].duration; | ||
603 | timeRanges.push(currentSegmentRange); | ||
604 | } | ||
605 | |||
606 | for (counter = 0; counter < timeRanges.length; counter++) { | ||
607 | if (time >= timeRanges[counter].start && time < timeRanges[counter].end) { | ||
608 | return counter; | ||
609 | } | ||
736 | } | 610 | } |
737 | 611 | ||
738 | // clear outstanding timeouts | 612 | return -1; |
739 | window.clearTimeout(abortTimeout); | 613 | }; |
740 | 614 | ||
741 | // request timeout | 615 | /** |
742 | if (request.timedout) { | 616 | * A comparator function to sort two playlist object by bandwidth. |
743 | return callback.call(this, 'timeout', url); | 617 | * @param left {object} a media playlist object |
618 | * @param right {object} a media playlist object | ||
619 | * @return {number} Greater than zero if the bandwidth attribute of | ||
620 | * left is greater than the corresponding attribute of right. Less | ||
621 | * than zero if the bandwidth of right is greater than left and | ||
622 | * exactly zero if the two are equal. | ||
623 | */ | ||
624 | videojs.Hls.comparePlaylistBandwidth = function(left, right) { | ||
625 | var leftBandwidth, rightBandwidth; | ||
626 | if (left.attributes && left.attributes.BANDWIDTH) { | ||
627 | leftBandwidth = left.attributes.BANDWIDTH; | ||
744 | } | 628 | } |
629 | leftBandwidth = leftBandwidth || window.Number.MAX_VALUE; | ||
630 | if (right.attributes && right.attributes.BANDWIDTH) { | ||
631 | rightBandwidth = right.attributes.BANDWIDTH; | ||
632 | } | ||
633 | rightBandwidth = rightBandwidth || window.Number.MAX_VALUE; | ||
745 | 634 | ||
746 | // request aborted or errored | 635 | return leftBandwidth - rightBandwidth; |
747 | if (this.status >= 400 || this.status === 0) { | 636 | }; |
748 | return callback.call(this, true, url); | 637 | |
638 | /** | ||
639 | * A comparator function to sort two playlist object by resolution (width). | ||
640 | * @param left {object} a media playlist object | ||
641 | * @param right {object} a media playlist object | ||
642 | * @return {number} Greater than zero if the resolution.width attribute of | ||
643 | * left is greater than the corresponding attribute of right. Less | ||
644 | * than zero if the resolution.width of right is greater than left and | ||
645 | * exactly zero if the two are equal. | ||
646 | */ | ||
647 | videojs.Hls.comparePlaylistResolution = function(left, right) { | ||
648 | var leftWidth, rightWidth; | ||
649 | |||
650 | if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { | ||
651 | leftWidth = left.attributes.RESOLUTION.width; | ||
749 | } | 652 | } |
750 | 653 | ||
751 | return callback.call(this, false, url); | 654 | leftWidth = leftWidth || window.Number.MAX_VALUE; |
752 | }; | 655 | |
753 | request.send(null); | 656 | if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { |
754 | return request; | 657 | rightWidth = right.attributes.RESOLUTION.width; |
658 | } | ||
659 | |||
660 | rightWidth = rightWidth || window.Number.MAX_VALUE; | ||
661 | |||
662 | // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions | ||
663 | // have the same media dimensions/ resolution | ||
664 | if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { | ||
665 | return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; | ||
666 | } else { | ||
667 | return leftWidth - rightWidth; | ||
668 | } | ||
755 | }; | 669 | }; |
756 | 670 | ||
757 | /** | 671 | /** |
... | @@ -793,7 +707,4 @@ resolveUrl = videojs.Hls.resolveUrl = function(basePath, path) { | ... | @@ -793,7 +707,4 @@ resolveUrl = videojs.Hls.resolveUrl = function(basePath, path) { |
793 | return result; | 707 | return result; |
794 | }; | 708 | }; |
795 | 709 | ||
796 | // Add HLS to the standard tech order | ||
797 | videojs.options.techOrder.unshift('hls'); | ||
798 | |||
799 | })(window, window.videojs, document); | 710 | })(window, window.videojs, document); | ... | ... |
src/xhr.js
0 → 100644
1 | (function(videojs){ | ||
2 | /** | ||
3 | * Creates and sends an XMLHttpRequest. | ||
4 | * TODO - expose video.js core's XHR and use that instead | ||
5 | * | ||
6 | * @param options {string | object} if this argument is a string, it | ||
7 | * is intrepreted as a URL and a simple GET request is | ||
8 | * inititated. If it is an object, it should contain a `url` | ||
9 | * property that indicates the URL to request and optionally a | ||
10 | * `method` which is the type of HTTP request to send. | ||
11 | * @param callback (optional) {function} a function to call when the | ||
12 | * request completes. If the request was not successful, the first | ||
13 | * argument will be falsey. | ||
14 | * @return {object} the XMLHttpRequest that was initiated. | ||
15 | */ | ||
16 | videojs.Hls.xhr = function(url, callback) { | ||
17 | var | ||
18 | options = { | ||
19 | method: 'GET', | ||
20 | timeout: 45 * 1000 | ||
21 | }, | ||
22 | request, | ||
23 | abortTimeout; | ||
24 | |||
25 | if (typeof callback !== 'function') { | ||
26 | callback = function() {}; | ||
27 | } | ||
28 | |||
29 | if (typeof url === 'object') { | ||
30 | options = videojs.util.mergeOptions(options, url); | ||
31 | url = options.url; | ||
32 | } | ||
33 | |||
34 | request = new window.XMLHttpRequest(); | ||
35 | request.open(options.method, url); | ||
36 | request.url = url; | ||
37 | |||
38 | if (options.responseType) { | ||
39 | request.responseType = options.responseType; | ||
40 | } | ||
41 | if (options.withCredentials) { | ||
42 | request.withCredentials = true; | ||
43 | } | ||
44 | if (options.timeout) { | ||
45 | if (request.timeout === 0) { | ||
46 | request.timeout = options.timeout; | ||
47 | request.ontimeout = function() { | ||
48 | request.timedout = true; | ||
49 | }; | ||
50 | } else { | ||
51 | // polyfill XHR2 by aborting after the timeout | ||
52 | abortTimeout = window.setTimeout(function() { | ||
53 | if (request.readyState !== 4) { | ||
54 | request.timedout = true; | ||
55 | request.abort(); | ||
56 | } | ||
57 | }, options.timeout); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | request.onreadystatechange = function() { | ||
62 | // wait until the request completes | ||
63 | if (this.readyState !== 4) { | ||
64 | return; | ||
65 | } | ||
66 | |||
67 | // clear outstanding timeouts | ||
68 | window.clearTimeout(abortTimeout); | ||
69 | |||
70 | // request timeout | ||
71 | if (request.timedout) { | ||
72 | return callback.call(this, 'timeout', url); | ||
73 | } | ||
74 | |||
75 | // request aborted or errored | ||
76 | if (this.status >= 400 || this.status === 0) { | ||
77 | return callback.call(this, true, url); | ||
78 | } | ||
79 | |||
80 | return callback.call(this, false, url); | ||
81 | }; | ||
82 | request.send(null); | ||
83 | return request; | ||
84 | }; | ||
85 | |||
86 | })(window.videojs); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -79,6 +79,7 @@ module.exports = function(config) { | ... | @@ -79,6 +79,7 @@ module.exports = function(config) { |
79 | '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', | 79 | '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', |
80 | '../test/karma-qunit-shim.js', | 80 | '../test/karma-qunit-shim.js', |
81 | '../src/videojs-hls.js', | 81 | '../src/videojs-hls.js', |
82 | '../src/xhr.js', | ||
82 | '../src/flv-tag.js', | 83 | '../src/flv-tag.js', |
83 | '../src/exp-golomb.js', | 84 | '../src/exp-golomb.js', |
84 | '../src/h264-stream.js', | 85 | '../src/h264-stream.js', | ... | ... |
... | @@ -43,6 +43,7 @@ module.exports = function(config) { | ... | @@ -43,6 +43,7 @@ module.exports = function(config) { |
43 | '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', | 43 | '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', |
44 | '../test/karma-qunit-shim.js', | 44 | '../test/karma-qunit-shim.js', |
45 | '../src/videojs-hls.js', | 45 | '../src/videojs-hls.js', |
46 | '../src/xhr.js', | ||
46 | '../src/flv-tag.js', | 47 | '../src/flv-tag.js', |
47 | '../src/exp-golomb.js', | 48 | '../src/exp-golomb.js', |
48 | '../src/h264-stream.js', | 49 | '../src/h264-stream.js', | ... | ... |
... | @@ -123,6 +123,7 @@ | ... | @@ -123,6 +123,7 @@ |
123 | <script src="../../node_modules/video.js/dist/video-js/video.js"></script> | 123 | <script src="../../node_modules/video.js/dist/video-js/video.js"></script> |
124 | <script src="../../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> | 124 | <script src="../../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> |
125 | <script src="../../src/videojs-hls.js"></script> | 125 | <script src="../../src/videojs-hls.js"></script> |
126 | <script src="../../src/xhr.js"></script> | ||
126 | <script src="../../src/stream.js"></script> | 127 | <script src="../../src/stream.js"></script> |
127 | <script src="../../src/m3u8/m3u8-parser.js"></script> | 128 | <script src="../../src/m3u8/m3u8-parser.js"></script> |
128 | <script src="../../src/playlist-loader.js"></script> | 129 | <script src="../../src/playlist-loader.js"></script> | ... | ... |
... | @@ -20,6 +20,7 @@ | ... | @@ -20,6 +20,7 @@ |
20 | 20 | ||
21 | <!-- HLS plugin --> | 21 | <!-- HLS plugin --> |
22 | <script src="../src/videojs-hls.js"></script> | 22 | <script src="../src/videojs-hls.js"></script> |
23 | <script src="../src/xhr.js"></script> | ||
23 | <script src="../src/flv-tag.js"></script> | 24 | <script src="../src/flv-tag.js"></script> |
24 | <script src="../src/exp-golomb.js"></script> | 25 | <script src="../src/exp-golomb.js"></script> |
25 | <script src="../src/h264-stream.js"></script> | 26 | <script src="../src/h264-stream.js"></script> | ... | ... |
-
Please register or sign in to post a comment