b0fe8348 by jrivera

Merge remote-tracking branch 'origin/master'

2 parents 9ee2a7db e45497bc
...@@ -170,6 +170,40 @@ export default class MasterPlaylistController extends videojs.EventTarget { ...@@ -170,6 +170,40 @@ export default class MasterPlaylistController extends videojs.EventTarget {
170 } 170 }
171 171
172 /** 172 /**
173 * get the total number of media requests from the `audiosegmentloader_`
174 * and the `mainSegmentLoader_`
175 *
176 * @private
177 */
178 mediaRequests_() {
179 return this.audioSegmentLoader_.mediaRequests +
180 this.mainSegmentLoader_.mediaRequests;
181 }
182
183 /**
184 * get the total time that media requests have spent trnasfering
185 * from the `audiosegmentloader_` and the `mainSegmentLoader_`
186 *
187 * @private
188 */
189 mediaTransferDuration_() {
190 return this.audioSegmentLoader_.mediaTransferDuration +
191 this.mainSegmentLoader_.mediaTransferDuration;
192
193 }
194
195 /**
196 * get the total number of bytes transfered during media requests
197 * from the `audiosegmentloader_` and the `mainSegmentLoader_`
198 *
199 * @private
200 */
201 mediaBytesTransferred_() {
202 return this.audioSegmentLoader_.mediaBytesTransferred +
203 this.mainSegmentLoader_.mediaBytesTransferred;
204 }
205
206 /**
173 * fill our internal list of HlsAudioTracks with data from 207 * fill our internal list of HlsAudioTracks with data from
174 * the master playlist or use a default 208 * the master playlist or use a default
175 * 209 *
...@@ -350,7 +384,8 @@ export default class MasterPlaylistController extends videojs.EventTarget { ...@@ -350,7 +384,8 @@ export default class MasterPlaylistController extends videojs.EventTarget {
350 384
351 if (media !== this.masterPlaylistLoader_.media()) { 385 if (media !== this.masterPlaylistLoader_.media()) {
352 this.masterPlaylistLoader_.media(media); 386 this.masterPlaylistLoader_.media(media);
353 this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5, Infinity); 387 this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5,
388 Infinity);
354 } 389 }
355 } 390 }
356 391
......
...@@ -198,27 +198,58 @@ const bufferIntersection = function(bufferA, bufferB) { ...@@ -198,27 +198,58 @@ const bufferIntersection = function(bufferA, bufferB) {
198 * covers adjusted according to currentTime 198 * covers adjusted according to currentTime
199 * @param {TimeRanges} referenceRange - the original time range that the 199 * @param {TimeRanges} referenceRange - the original time range that the
200 * segment covers 200 * segment covers
201 * @param {Number} currentTime - time in seconds where the current playback
202 * is at
201 * @param {TimeRanges} buffered - the currently buffered time ranges 203 * @param {TimeRanges} buffered - the currently buffered time ranges
202 * @returns {Number} percent of the segment currently buffered 204 * @returns {Number} percent of the segment currently buffered
203 */ 205 */
204 const calculateBufferedPercent = function(segmentRange, referenceRange, buffered) { 206 const calculateBufferedPercent = function(adjustedRange,
207 referenceRange,
208 currentTime,
209 buffered) {
205 let referenceDuration = referenceRange.end(0) - referenceRange.start(0); 210 let referenceDuration = referenceRange.end(0) - referenceRange.start(0);
206 let segmentDuration = segmentRange.end(0) - segmentRange.start(0); 211 let adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0);
207 let intersection = bufferIntersection(segmentRange, buffered); 212 let bufferMissingFromAdjusted = referenceDuration - adjustedDuration;
208 let count = intersection.length; 213 let adjustedIntersection = bufferIntersection(adjustedRange, buffered);
214 let referenceIntersection = bufferIntersection(referenceRange, buffered);
215 let adjustedOverlap = 0;
216 let referenceOverlap = 0;
217
218 let count = adjustedIntersection.length;
219
220 while (count--) {
221 adjustedOverlap += adjustedIntersection.end(count) -
222 adjustedIntersection.start(count);
223
224 // If the current overlap segment starts at currentTime, then increase the
225 // overlap duration so that it actually starts at the beginning of referenceRange
226 // by including the difference between the two Range's durations
227 // This is a work around for the way Flash has no buffer before currentTime
228 if (adjustedIntersection.start(count) === currentTime) {
229 adjustedOverlap += bufferMissingFromAdjusted;
230 }
231 }
232
233 count = referenceIntersection.length;
209 234
210 while (count--) { 235 while (count--) {
211 segmentDuration -= intersection.end(count) - intersection.start(count); 236 referenceOverlap += referenceIntersection.end(count) -
237 referenceIntersection.start(count);
212 } 238 }
213 return (referenceDuration - segmentDuration) / referenceDuration * 100; 239
240 // Use whichever value is larger for the percentage-buffered since that value
241 // is likely more accurate because the only way
242 return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100;
214 }; 243 };
215 244
216 /** 245 /**
217 * Return the amount of a segment specified by the mediaIndex overlaps 246 * Return the amount of a range specified by the startOfSegment and segmentDuration
218 * the current buffered content. 247 * overlaps the current buffered content.
219 * 248 *
220 * @param {Number} startOfSegment - the time where the segment begins 249 * @param {Number} startOfSegment - the time where the segment begins
221 * @param {Number} segmentDuration - the duration of the segment in seconds 250 * @param {Number} segmentDuration - the duration of the segment in seconds
251 * @param {Number} currentTime - time in seconds where the current playback
252 * is at
222 * @param {TimeRanges} buffered - the state of the buffer 253 * @param {TimeRanges} buffered - the state of the buffer
223 * @returns {Number} percentage of the segment's time range that is 254 * @returns {Number} percentage of the segment's time range that is
224 * already in `buffered` 255 * already in `buffered`
...@@ -254,6 +285,7 @@ const getSegmentBufferedPercent = function(startOfSegment, ...@@ -254,6 +285,7 @@ const getSegmentBufferedPercent = function(startOfSegment,
254 285
255 let percent = calculateBufferedPercent(adjustedSegmentRange, 286 let percent = calculateBufferedPercent(adjustedSegmentRange,
256 originalSegmentRange, 287 originalSegmentRange,
288 currentTime,
257 buffered); 289 buffered);
258 290
259 // If the segment is reported as having a zero duration, return 0% 291 // If the segment is reported as having a zero duration, return 0%
......
...@@ -131,7 +131,7 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -131,7 +131,7 @@ export default class SegmentLoader extends videojs.EventTarget {
131 this.state = 'INIT'; 131 this.state = 'INIT';
132 this.bandwidth = settings.bandwidth; 132 this.bandwidth = settings.bandwidth;
133 this.roundTrip = NaN; 133 this.roundTrip = NaN;
134 this.bytesReceived = 0; 134 this.resetStats_();
135 135
136 // private properties 136 // private properties
137 this.hasPlayed_ = settings.hasPlayed; 137 this.hasPlayed_ = settings.hasPlayed;
...@@ -153,6 +153,17 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -153,6 +153,17 @@ export default class SegmentLoader extends videojs.EventTarget {
153 } 153 }
154 154
155 /** 155 /**
156 * reset all of our media stats
157 *
158 * @private
159 */
160 resetStats_() {
161 this.mediaBytesTransferred = 0;
162 this.mediaRequests = 0;
163 this.mediaTransferDuration = 0;
164 }
165
166 /**
156 * dispose of the SegmentLoader and reset to the default state 167 * dispose of the SegmentLoader and reset to the default state
157 */ 168 */
158 dispose() { 169 dispose() {
...@@ -161,6 +172,7 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -161,6 +172,7 @@ export default class SegmentLoader extends videojs.EventTarget {
161 if (this.sourceUpdater_) { 172 if (this.sourceUpdater_) {
162 this.sourceUpdater_.dispose(); 173 this.sourceUpdater_.dispose();
163 } 174 }
175 this.resetStats_();
164 } 176 }
165 177
166 /** 178 /**
...@@ -625,7 +637,9 @@ export default class SegmentLoader extends videojs.EventTarget { ...@@ -625,7 +637,9 @@ export default class SegmentLoader extends videojs.EventTarget {
625 // calculate the download bandwidth based on segment request 637 // calculate the download bandwidth based on segment request
626 this.roundTrip = request.roundTripTime; 638 this.roundTrip = request.roundTripTime;
627 this.bandwidth = request.bandwidth; 639 this.bandwidth = request.bandwidth;
628 this.bytesReceived += request.bytesReceived || 0; 640 this.mediaBytesTransferred += request.bytesReceived || 0;
641 this.mediaRequests += 1;
642 this.mediaTransferDuration += request.roundTripTime || 0;
629 643
630 if (segment.key) { 644 if (segment.key) {
631 segmentInfo.encryptedBytes = new Uint8Array(request.response); 645 segmentInfo.encryptedBytes = new Uint8Array(request.response);
......
...@@ -301,12 +301,16 @@ class HlsHandler extends Component { ...@@ -301,12 +301,16 @@ class HlsHandler extends Component {
301 301
302 this.tech_ = tech; 302 this.tech_ = tech;
303 this.source_ = source; 303 this.source_ = source;
304 this.stats = {};
304 305
305 // handle global & Source Handler level options 306 // handle global & Source Handler level options
306 this.options_ = videojs.mergeOptions(videojs.options.hls || {}, options.hls); 307 this.options_ = videojs.mergeOptions(videojs.options.hls || {}, options.hls);
307 this.setOptions_(); 308 this.setOptions_();
308 309
309 this.bytesReceived = 0; 310 // start playlist selection at a reasonable bandwidth for
311 // broadband internet
312 // 0.5 Mbps
313 this.bandwidth = this.options_.bandwidth || 4194304;
310 314
311 // listen for fullscreenchange events for this player so that we 315 // listen for fullscreenchange events for this player so that we
312 // can adjust our quality selection quickly 316 // can adjust our quality selection quickly
...@@ -406,6 +410,19 @@ class HlsHandler extends Component { ...@@ -406,6 +410,19 @@ class HlsHandler extends Component {
406 } 410 }
407 }); 411 });
408 412
413 Object.defineProperty(this.stats, 'bandwidth', {
414 get: () => this.bandwidth || 0
415 });
416 Object.defineProperty(this.stats, 'mediaRequests', {
417 get: () => this.masterPlaylistController_.mediaRequests_() || 0
418 });
419 Object.defineProperty(this.stats, 'mediaTransferDuration', {
420 get: () => this.masterPlaylistController_.mediaTransferDuration_() || 0
421 });
422 Object.defineProperty(this.stats, 'mediaBytesTransferred', {
423 get: () => this.masterPlaylistController_.mediaBytesTransferred_() || 0
424 });
425
409 this.tech_.one('canplay', 426 this.tech_.one('canplay',
410 this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 427 this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
411 428
...@@ -527,7 +544,6 @@ class HlsHandler extends Component { ...@@ -527,7 +544,6 @@ class HlsHandler extends Component {
527 this.masterPlaylistController_.dispose(); 544 this.masterPlaylistController_.dispose();
528 } 545 }
529 this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_); 546 this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_);
530
531 super.dispose(); 547 super.dispose();
532 } 548 }
533 } 549 }
......
...@@ -64,6 +64,9 @@ QUnit.test('obeys none preload option', function() { ...@@ -64,6 +64,9 @@ QUnit.test('obeys none preload option', function() {
64 openMediaSource(this.player, this.clock); 64 openMediaSource(this.player, this.clock);
65 65
66 QUnit.equal(this.requests.length, 0, 'no segment requests'); 66 QUnit.equal(this.requests.length, 0, 'no segment requests');
67
68 // verify stats
69 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
67 }); 70 });
68 71
69 QUnit.test('obeys auto preload option', function() { 72 QUnit.test('obeys auto preload option', function() {
...@@ -76,6 +79,9 @@ QUnit.test('obeys auto preload option', function() { ...@@ -76,6 +79,9 @@ QUnit.test('obeys auto preload option', function() {
76 openMediaSource(this.player, this.clock); 79 openMediaSource(this.player, this.clock);
77 80
78 QUnit.equal(this.requests.length, 1, '1 segment request'); 81 QUnit.equal(this.requests.length, 1, '1 segment request');
82
83 // verify stats
84 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
79 }); 85 });
80 86
81 QUnit.test('obeys metadata preload option', function() { 87 QUnit.test('obeys metadata preload option', function() {
...@@ -88,6 +94,9 @@ QUnit.test('obeys metadata preload option', function() { ...@@ -88,6 +94,9 @@ QUnit.test('obeys metadata preload option', function() {
88 openMediaSource(this.player, this.clock); 94 openMediaSource(this.player, this.clock);
89 95
90 QUnit.equal(this.requests.length, 1, '1 segment request'); 96 QUnit.equal(this.requests.length, 1, '1 segment request');
97
98 // verify stats
99 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
91 }); 100 });
92 101
93 QUnit.test('clears some of the buffer for a fast quality change', function() { 102 QUnit.test('clears some of the buffer for a fast quality change', function() {
...@@ -114,6 +123,9 @@ QUnit.test('clears some of the buffer for a fast quality change', function() { ...@@ -114,6 +123,9 @@ QUnit.test('clears some of the buffer for a fast quality change', function() {
114 QUnit.equal(removes.length, 1, 'removed buffered content'); 123 QUnit.equal(removes.length, 1, 'removed buffered content');
115 QUnit.equal(removes[0].start, 7 + 5, 'removed from a bit after current time'); 124 QUnit.equal(removes[0].start, 7 + 5, 'removed from a bit after current time');
116 QUnit.equal(removes[0].end, Infinity, 'removed to the end'); 125 QUnit.equal(removes[0].end, Infinity, 'removed to the end');
126
127 // verify stats
128 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
117 }); 129 });
118 130
119 QUnit.test('does not clear the buffer when no fast quality change occurs', function() { 131 QUnit.test('does not clear the buffer when no fast quality change occurs', function() {
...@@ -134,6 +146,8 @@ QUnit.test('does not clear the buffer when no fast quality change occurs', funct ...@@ -134,6 +146,8 @@ QUnit.test('does not clear the buffer when no fast quality change occurs', funct
134 this.masterPlaylistController.fastQualityChange_(); 146 this.masterPlaylistController.fastQualityChange_();
135 147
136 QUnit.equal(removes.length, 0, 'did not remove content'); 148 QUnit.equal(removes.length, 0, 'did not remove content');
149 // verify stats
150 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
137 }); 151 });
138 152
139 QUnit.test('if buffered, will request second segment byte range', function() { 153 QUnit.test('if buffered, will request second segment byte range', function() {
...@@ -163,6 +177,13 @@ QUnit.test('if buffered, will request second segment byte range', function() { ...@@ -163,6 +177,13 @@ QUnit.test('if buffered, will request second segment byte range', function() {
163 this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend'); 177 this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
164 this.clock.tick(10 * 1000); 178 this.clock.tick(10 * 1000);
165 QUnit.equal(this.requests[2].headers.Range, 'bytes=1823412-2299991'); 179 QUnit.equal(this.requests[2].headers.Range, 'bytes=1823412-2299991');
180
181 // verify stats
182 QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
183 QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
184 QUnit.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
185 16,
186 '16 bytes downloaded');
166 }); 187 });
167 188
168 QUnit.test('re-initializes the combined playlist loader when switching sources', 189 QUnit.test('re-initializes the combined playlist loader when switching sources',
...@@ -218,6 +239,8 @@ QUnit.test('updates the combined segment loader on live playlist refreshes', fun ...@@ -218,6 +239,8 @@ QUnit.test('updates the combined segment loader on live playlist refreshes', fun
218 239
219 this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist'); 240 this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist');
220 QUnit.equal(updates.length, 1, 'updated the segment list'); 241 QUnit.equal(updates.length, 1, 'updated the segment list');
242 // verify stats
243 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
221 }); 244 });
222 245
223 QUnit.test( 246 QUnit.test(
...@@ -240,6 +263,13 @@ function() { ...@@ -240,6 +263,13 @@ function() {
240 standardXHRResponse(this.requests.shift()); 263 standardXHRResponse(this.requests.shift());
241 this.masterPlaylistController.mainSegmentLoader_.trigger('progress'); 264 this.masterPlaylistController.mainSegmentLoader_.trigger('progress');
242 QUnit.equal(progressCount, 1, 'fired a progress event'); 265 QUnit.equal(progressCount, 1, 'fired a progress event');
266
267 // verify stats
268 QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
269 QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
270 QUnit.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
271 16,
272 '16 bytes downloaded');
243 }); 273 });
244 274
245 QUnit.test('blacklists switching from video+audio playlists to audio only', function() { 275 QUnit.test('blacklists switching from video+audio playlists to audio only', function() {
...@@ -264,6 +294,9 @@ QUnit.test('blacklists switching from video+audio playlists to audio only', func ...@@ -264,6 +294,9 @@ QUnit.test('blacklists switching from video+audio playlists to audio only', func
264 'selected video+audio'); 294 'selected video+audio');
265 audioPlaylist = this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0]; 295 audioPlaylist = this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0];
266 QUnit.equal(audioPlaylist.excludeUntil, Infinity, 'excluded incompatible playlist'); 296 QUnit.equal(audioPlaylist.excludeUntil, Infinity, 'excluded incompatible playlist');
297
298 // verify stats
299 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1e10, 'bandwidth we set above');
267 }); 300 });
268 301
269 QUnit.test('blacklists switching from audio-only playlists to video+audio', function() { 302 QUnit.test('blacklists switching from audio-only playlists to video+audio', function() {
...@@ -290,6 +323,9 @@ QUnit.test('blacklists switching from audio-only playlists to video+audio', func ...@@ -290,6 +323,9 @@ QUnit.test('blacklists switching from audio-only playlists to video+audio', func
290 QUnit.equal(videoAudioPlaylist.excludeUntil, 323 QUnit.equal(videoAudioPlaylist.excludeUntil,
291 Infinity, 324 Infinity,
292 'excluded incompatible playlist'); 325 'excluded incompatible playlist');
326
327 // verify stats
328 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
293 }); 329 });
294 330
295 QUnit.test('blacklists switching from video-only playlists to video+audio', function() { 331 QUnit.test('blacklists switching from video-only playlists to video+audio', function() {
...@@ -317,6 +353,9 @@ QUnit.test('blacklists switching from video-only playlists to video+audio', func ...@@ -317,6 +353,9 @@ QUnit.test('blacklists switching from video-only playlists to video+audio', func
317 QUnit.equal(videoAudioPlaylist.excludeUntil, 353 QUnit.equal(videoAudioPlaylist.excludeUntil,
318 Infinity, 354 Infinity,
319 'excluded incompatible playlist'); 355 'excluded incompatible playlist');
356
357 // verify stats
358 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
320 }); 359 });
321 360
322 QUnit.test('blacklists switching between playlists with incompatible audio codecs', 361 QUnit.test('blacklists switching between playlists with incompatible audio codecs',
...@@ -343,6 +382,8 @@ function() { ...@@ -343,6 +382,8 @@ function() {
343 alternatePlaylist = 382 alternatePlaylist =
344 this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1]; 383 this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1];
345 QUnit.equal(alternatePlaylist.excludeUntil, Infinity, 'excluded incompatible playlist'); 384 QUnit.equal(alternatePlaylist.excludeUntil, Infinity, 'excluded incompatible playlist');
385 // verify stats
386 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
346 }); 387 });
347 388
348 QUnit.test('updates the combined segment loader on media changes', function() { 389 QUnit.test('updates the combined segment loader on media changes', function() {
...@@ -369,6 +410,14 @@ QUnit.test('updates the combined segment loader on media changes', function() { ...@@ -369,6 +410,14 @@ QUnit.test('updates the combined segment loader on media changes', function() {
369 // media 410 // media
370 standardXHRResponse(this.requests.shift()); 411 standardXHRResponse(this.requests.shift());
371 QUnit.equal(updates.length, 1, 'updated the segment list'); 412 QUnit.equal(updates.length, 1, 'updated the segment list');
413
414 // verify stats
415 QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
416 QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
417 QUnit.equal(
418 this.player.tech_.hls.stats.mediaBytesTransferred,
419 16,
420 '16 bytes downloaded');
372 }); 421 });
373 422
374 QUnit.test('selects a playlist after main/combined segment downloads', function() { 423 QUnit.test('selects a playlist after main/combined segment downloads', function() {
...@@ -392,6 +441,8 @@ QUnit.test('selects a playlist after main/combined segment downloads', function( ...@@ -392,6 +441,8 @@ QUnit.test('selects a playlist after main/combined segment downloads', function(
392 // and another 441 // and another
393 this.masterPlaylistController.mainSegmentLoader_.trigger('progress'); 442 this.masterPlaylistController.mainSegmentLoader_.trigger('progress');
394 QUnit.strictEqual(calls, 3, 'selects after additional segments'); 443 QUnit.strictEqual(calls, 3, 'selects after additional segments');
444 // verify stats
445 QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
395 }); 446 });
396 447
397 QUnit.test('updates the duration after switching playlists', function() { 448 QUnit.test('updates the duration after switching playlists', function() {
...@@ -424,6 +475,13 @@ QUnit.test('updates the duration after switching playlists', function() { ...@@ -424,6 +475,13 @@ QUnit.test('updates the duration after switching playlists', function() {
424 QUnit.ok(selectedPlaylist, 'selected playlist'); 475 QUnit.ok(selectedPlaylist, 'selected playlist');
425 QUnit.ok(this.masterPlaylistController.mediaSource.duration !== 0, 476 QUnit.ok(this.masterPlaylistController.mediaSource.duration !== 0,
426 'updates the duration'); 477 'updates the duration');
478
479 // verify stats
480 QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
481 QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
482 QUnit.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
483 16,
484 '16 bytes downloaded');
427 }); 485 });
428 486
429 QUnit.test('seekable uses the intersection of alternate audio and combined tracks', 487 QUnit.test('seekable uses the intersection of alternate audio and combined tracks',
......
...@@ -94,7 +94,7 @@ QUnit.test('detects time range end-point changed by updates', function() { ...@@ -94,7 +94,7 @@ QUnit.test('detects time range end-point changed by updates', function() {
94 94
95 QUnit.module('Segment Percent Buffered Calculations'); 95 QUnit.module('Segment Percent Buffered Calculations');
96 96
97 QUnit.test('calculates the percent buffered for segments', function() { 97 QUnit.test('calculates the percent buffered for segments in the simple case', function() {
98 let segmentStart = 10; 98 let segmentStart = 10;
99 let segmentDuration = 10; 99 let segmentDuration = 10;
100 let currentTime = 0; 100 let currentTime = 0;
...@@ -108,8 +108,8 @@ QUnit.test('calculates the percent buffered for segments', function() { ...@@ -108,8 +108,8 @@ QUnit.test('calculates the percent buffered for segments', function() {
108 QUnit.equal(percentBuffered, 40, 'calculated the buffered amount correctly'); 108 QUnit.equal(percentBuffered, 40, 'calculated the buffered amount correctly');
109 }); 109 });
110 110
111 QUnit.test('calculates the percent buffered for segments taking into account ' + 111 QUnit.test('consider the buffer before currentTime to be filled if the segement begins at ' +
112 'currentTime', function() { 112 'or before the currentTime', function() {
113 let segmentStart = 10; 113 let segmentStart = 10;
114 let segmentDuration = 10; 114 let segmentDuration = 10;
115 let currentTime = 15; 115 let currentTime = 15;
...@@ -123,6 +123,21 @@ QUnit.test('calculates the percent buffered for segments taking into account ' + ...@@ -123,6 +123,21 @@ QUnit.test('calculates the percent buffered for segments taking into account ' +
123 QUnit.equal(percentBuffered, 90, 'calculated the buffered amount correctly'); 123 QUnit.equal(percentBuffered, 90, 'calculated the buffered amount correctly');
124 }); 124 });
125 125
126 QUnit.test('does not consider the buffer before currentTime as filled if the segment ' +
127 'begins after the currentTime', function() {
128 let segmentStart = 10;
129 let segmentDuration = 10;
130 let currentTime = 18;
131 let buffered = createTimeRanges([[19, 30]]);
132 let percentBuffered = Ranges.getSegmentBufferedPercent(
133 segmentStart,
134 segmentDuration,
135 currentTime,
136 buffered);
137
138 QUnit.equal(percentBuffered, 10, 'calculated the buffered amount correctly');
139 });
140
126 QUnit.test('calculates the percent buffered for segments with multiple buffered ' + 141 QUnit.test('calculates the percent buffered for segments with multiple buffered ' +
127 'regions', function() { 142 'regions', function() {
128 let segmentStart = 10; 143 let segmentStart = 10;
...@@ -166,3 +181,32 @@ QUnit.test('calculates the percent buffered as 0 for zero-length segments', func ...@@ -166,3 +181,32 @@ QUnit.test('calculates the percent buffered as 0 for zero-length segments', func
166 181
167 QUnit.equal(percentBuffered, 0, 'calculated the buffered amount correctly'); 182 QUnit.equal(percentBuffered, 0, 'calculated the buffered amount correctly');
168 }); 183 });
184
185 QUnit.test('calculates the percent buffered as 0 for segments that do not overlap ' +
186 'buffered regions taking into account currentTime', function() {
187 let segmentStart = 10;
188 let segmentDuration = 10;
189 let currentTime = 19;
190 let buffered = createTimeRanges([[20, 30]]);
191 let percentBuffered = Ranges.getSegmentBufferedPercent(
192 segmentStart,
193 segmentDuration,
194 currentTime,
195 buffered);
196
197 QUnit.equal(percentBuffered, 0, 'calculated the buffered amount correctly');
198 });
199
200 QUnit.test('calculates the percent buffered for segments that end before currentTime', function() {
201 let segmentStart = 10;
202 let segmentDuration = 10;
203 let currentTime = 19.6;
204 let buffered = createTimeRanges([[0, 19.5]]);
205 let percentBuffered = Ranges.getSegmentBufferedPercent(
206 segmentStart,
207 segmentDuration,
208 currentTime,
209 buffered);
210
211 QUnit.equal(percentBuffered, 95, 'calculated the buffered amount correctly');
212 });
......
...@@ -105,6 +105,11 @@ QUnit.test('calling load is idempotent', function() { ...@@ -105,6 +105,11 @@ QUnit.test('calling load is idempotent', function() {
105 this.requests.shift().respond(200, null, ''); 105 this.requests.shift().respond(200, null, '');
106 loader.load(); 106 loader.load();
107 QUnit.equal(this.requests.length, 0, 'load has no effect'); 107 QUnit.equal(this.requests.length, 0, 'load has no effect');
108
109 // verify stats
110 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
111 QUnit.equal(loader.mediaTransferDuration, 100, '100 ms (clock above)');
112 QUnit.equal(loader.mediaRequests, 1, '1 request');
108 }); 113 });
109 114
110 QUnit.test('calling load should unpause', function() { 115 QUnit.test('calling load should unpause', function() {
...@@ -135,6 +140,11 @@ QUnit.test('calling load should unpause', function() { ...@@ -135,6 +140,11 @@ QUnit.test('calling load should unpause', function() {
135 140
136 loader.load(); 141 loader.load();
137 QUnit.equal(loader.paused(), false, 'unpaused'); 142 QUnit.equal(loader.paused(), false, 'unpaused');
143
144 // verify stats
145 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
146 QUnit.equal(loader.mediaTransferDuration, 1, '1 ms (clock above)');
147 QUnit.equal(loader.mediaRequests, 1, '1 request');
138 }); 148 });
139 149
140 QUnit.test('regularly checks the buffer while unpaused', function() { 150 QUnit.test('regularly checks the buffer while unpaused', function() {
...@@ -159,6 +169,11 @@ QUnit.test('regularly checks the buffer while unpaused', function() { ...@@ -159,6 +169,11 @@ QUnit.test('regularly checks the buffer while unpaused', function() {
159 currentTime = Config.GOAL_BUFFER_LENGTH; 169 currentTime = Config.GOAL_BUFFER_LENGTH;
160 this.clock.tick(10 * 1000); 170 this.clock.tick(10 * 1000);
161 QUnit.equal(this.requests.length, 1, 'requested another segment'); 171 QUnit.equal(this.requests.length, 1, 'requested another segment');
172
173 // verify stats
174 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
175 QUnit.equal(loader.mediaTransferDuration, 1, '1 ms (clock above)');
176 QUnit.equal(loader.mediaRequests, 1, '1 request');
162 }); 177 });
163 178
164 QUnit.test('does not check the buffer while paused', function() { 179 QUnit.test('does not check the buffer while paused', function() {
...@@ -177,6 +192,11 @@ QUnit.test('does not check the buffer while paused', function() { ...@@ -177,6 +192,11 @@ QUnit.test('does not check the buffer while paused', function() {
177 192
178 this.clock.tick(10 * 1000); 193 this.clock.tick(10 * 1000);
179 QUnit.equal(this.requests.length, 0, 'did not make a request'); 194 QUnit.equal(this.requests.length, 0, 'did not make a request');
195
196 // verify stats
197 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
198 QUnit.equal(loader.mediaTransferDuration, 1, '1 ms (clock above)');
199 QUnit.equal(loader.mediaRequests, 1, '1 request');
180 }); 200 });
181 201
182 QUnit.test('calculates bandwidth after downloading a segment', function() { 202 QUnit.test('calculates bandwidth after downloading a segment', function() {
...@@ -191,7 +211,12 @@ QUnit.test('calculates bandwidth after downloading a segment', function() { ...@@ -191,7 +211,12 @@ QUnit.test('calculates bandwidth after downloading a segment', function() {
191 211
192 QUnit.equal(loader.bandwidth, (10 / 100) * 8 * 1000, 'calculated bandwidth'); 212 QUnit.equal(loader.bandwidth, (10 / 100) * 8 * 1000, 'calculated bandwidth');
193 QUnit.equal(loader.roundTrip, 100, 'saves request round trip time'); 213 QUnit.equal(loader.roundTrip, 100, 'saves request round trip time');
194 QUnit.equal(loader.bytesReceived, 10, 'saves bytes received'); 214
215 // TODO: Bandwidth Stat will be stale??
216 // verify stats
217 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
218 QUnit.equal(loader.mediaTransferDuration, 100, '100 ms (clock above)');
219 QUnit.equal(loader.mediaRequests, 1, '1 request');
195 }); 220 });
196 221
197 QUnit.test('segment request timeouts reset bandwidth', function() { 222 QUnit.test('segment request timeouts reset bandwidth', function() {
...@@ -223,6 +248,10 @@ QUnit.test('appending a segment triggers progress', function() { ...@@ -223,6 +248,10 @@ QUnit.test('appending a segment triggers progress', function() {
223 mediaSource.sourceBuffers[0].trigger('updateend'); 248 mediaSource.sourceBuffers[0].trigger('updateend');
224 249
225 QUnit.equal(progresses, 1, 'fired progress'); 250 QUnit.equal(progresses, 1, 'fired progress');
251
252 // verify stats
253 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
254 QUnit.equal(loader.mediaRequests, 1, '1 request');
226 }); 255 });
227 256
228 QUnit.test('only requests one segment at a time', function() { 257 QUnit.test('only requests one segment at a time', function() {
...@@ -251,6 +280,11 @@ QUnit.test('only appends one segment at a time', function() { ...@@ -251,6 +280,11 @@ QUnit.test('only appends one segment at a time', function() {
251 QUnit.equal(mediaSource.sourceBuffers[0].updates_.filter( 280 QUnit.equal(mediaSource.sourceBuffers[0].updates_.filter(
252 update => update.append).length, 1, 'only one append'); 281 update => update.append).length, 1, 'only one append');
253 QUnit.equal(this.requests.length, 0, 'only made one request'); 282 QUnit.equal(this.requests.length, 0, 'only made one request');
283
284 // verify stats
285 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
286 QUnit.equal(loader.mediaTransferDuration, 100, '100 ms (clock above)');
287 QUnit.equal(loader.mediaRequests, 1, '1 request');
254 }); 288 });
255 289
256 QUnit.test('adjusts the playlist offset if no buffering progress is made', function() { 290 QUnit.test('adjusts the playlist offset if no buffering progress is made', function() {
...@@ -288,6 +322,11 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made', funct ...@@ -288,6 +322,11 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made', funct
288 322
289 // so the loader should try the next segment 323 // so the loader should try the next segment
290 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment'); 324 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment');
325
326 // verify stats
327 QUnit.equal(loader.mediaBytesTransferred, 20, '20 bytes');
328 QUnit.equal(loader.mediaTransferDuration, 2, '2 ms (clocks above)');
329 QUnit.equal(loader.mediaRequests, 2, '2 requests');
291 }); 330 });
292 331
293 QUnit.test('never attempt to load a segment that ' + 332 QUnit.test('never attempt to load a segment that ' +
...@@ -319,6 +358,11 @@ QUnit.test('never attempt to load a segment that ' + ...@@ -319,6 +358,11 @@ QUnit.test('never attempt to load a segment that ' +
319 358
320 // the loader should move on to the next segment 359 // the loader should move on to the next segment
321 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment'); 360 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment');
361
362 // verify stats
363 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
364 QUnit.equal(loader.mediaTransferDuration, 1, '1 ms (clocks above)');
365 QUnit.equal(loader.mediaRequests, 1, '1 requests');
322 }); 366 });
323 367
324 QUnit.test('adjusts the playlist offset if no buffering progress is made', function() { 368 QUnit.test('adjusts the playlist offset if no buffering progress is made', function() {
...@@ -356,6 +400,11 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made', funct ...@@ -356,6 +400,11 @@ QUnit.test('adjusts the playlist offset if no buffering progress is made', funct
356 400
357 // so the loader should try the next segment 401 // so the loader should try the next segment
358 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment'); 402 QUnit.equal(this.requests[0].url, '1.ts', 'moved ahead a segment');
403
404 // verify stats
405 QUnit.equal(loader.mediaBytesTransferred, 20, '20 bytes');
406 QUnit.equal(loader.mediaTransferDuration, 2, '2 ms (clocks above)');
407 QUnit.equal(loader.mediaRequests, 2, '2 requests');
359 }); 408 });
360 409
361 QUnit.test('adjusts the playlist offset even when segment.end is set if no' + 410 QUnit.test('adjusts the playlist offset even when segment.end is set if no' +
...@@ -453,6 +502,10 @@ QUnit.test('abort does not cancel segment processing in progress', function() { ...@@ -453,6 +502,10 @@ QUnit.test('abort does not cancel segment processing in progress', function() {
453 502
454 loader.abort(); 503 loader.abort();
455 QUnit.equal(loader.state, 'APPENDING', 'still appending'); 504 QUnit.equal(loader.state, 'APPENDING', 'still appending');
505
506 // verify stats
507 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
508 QUnit.equal(loader.mediaRequests, 1, '1 request');
456 }); 509 });
457 510
458 QUnit.test('sets the timestampOffset on timeline change', function() { 511 QUnit.test('sets the timestampOffset on timeline change', function() {
...@@ -474,6 +527,10 @@ QUnit.test('sets the timestampOffset on timeline change', function() { ...@@ -474,6 +527,10 @@ QUnit.test('sets the timestampOffset on timeline change', function() {
474 this.requests[0].response = new Uint8Array(10).buffer; 527 this.requests[0].response = new Uint8Array(10).buffer;
475 this.requests.shift().respond(200, null, ''); 528 this.requests.shift().respond(200, null, '');
476 QUnit.equal(mediaSource.sourceBuffers[0].timestampOffset, 10, 'set timestampOffset'); 529 QUnit.equal(mediaSource.sourceBuffers[0].timestampOffset, 10, 'set timestampOffset');
530
531 // verify stats
532 QUnit.equal(loader.mediaBytesTransferred, 20, '20 bytes');
533 QUnit.equal(loader.mediaRequests, 2, '2 requests');
477 }); 534 });
478 535
479 QUnit.test('tracks segment end times as they are buffered', function() { 536 QUnit.test('tracks segment end times as they are buffered', function() {
...@@ -491,6 +548,10 @@ QUnit.test('tracks segment end times as they are buffered', function() { ...@@ -491,6 +548,10 @@ QUnit.test('tracks segment end times as they are buffered', function() {
491 ]); 548 ]);
492 mediaSource.sourceBuffers[0].trigger('updateend'); 549 mediaSource.sourceBuffers[0].trigger('updateend');
493 QUnit.equal(playlist.segments[0].end, 9.5, 'updated duration'); 550 QUnit.equal(playlist.segments[0].end, 9.5, 'updated duration');
551
552 // verify stats
553 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
554 QUnit.equal(loader.mediaRequests, 1, '1 request');
494 }); 555 });
495 556
496 QUnit.test('segment 404s should trigger an error', function() { 557 QUnit.test('segment 404s should trigger an error', function() {
...@@ -548,6 +609,10 @@ QUnit.test('fires ended at the end of a playlist', function() { ...@@ -548,6 +609,10 @@ QUnit.test('fires ended at the end of a playlist', function() {
548 mediaSource.sourceBuffers[0].buffered = videojs.createTimeRanges([[0, 10]]); 609 mediaSource.sourceBuffers[0].buffered = videojs.createTimeRanges([[0, 10]]);
549 mediaSource.sourceBuffers[0].trigger('updateend'); 610 mediaSource.sourceBuffers[0].trigger('updateend');
550 QUnit.equal(endOfStreams, 1, 'triggered ended'); 611 QUnit.equal(endOfStreams, 1, 'triggered ended');
612
613 // verify stats
614 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
615 QUnit.equal(loader.mediaRequests, 1, '1 request');
551 }); 616 });
552 617
553 QUnit.test('live playlists do not trigger ended', function() { 618 QUnit.test('live playlists do not trigger ended', function() {
...@@ -572,6 +637,10 @@ QUnit.test('live playlists do not trigger ended', function() { ...@@ -572,6 +637,10 @@ QUnit.test('live playlists do not trigger ended', function() {
572 mediaSource.sourceBuffers[0].buffered = videojs.createTimeRanges([[0, 10]]); 637 mediaSource.sourceBuffers[0].buffered = videojs.createTimeRanges([[0, 10]]);
573 mediaSource.sourceBuffers[0].trigger('updateend'); 638 mediaSource.sourceBuffers[0].trigger('updateend');
574 QUnit.equal(endOfStreams, 0, 'did not trigger ended'); 639 QUnit.equal(endOfStreams, 0, 'did not trigger ended');
640
641 // verify stats
642 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
643 QUnit.equal(loader.mediaRequests, 1, '1 request');
575 }); 644 });
576 645
577 QUnit.test('respects the global withCredentials option', function() { 646 QUnit.test('respects the global withCredentials option', function() {
...@@ -781,6 +850,10 @@ QUnit.test('the key is saved to the segment in the correct format', function() { ...@@ -781,6 +850,10 @@ QUnit.test('the key is saved to the segment in the correct format', function() {
781 QUnit.deepEqual(segment.key.bytes, 850 QUnit.deepEqual(segment.key.bytes,
782 new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]), 851 new Uint32Array([0, 0x01000000, 0x02000000, 0x03000000]),
783 'passed the specified segment key'); 852 'passed the specified segment key');
853
854 // verify stats
855 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
856 QUnit.equal(loader.mediaRequests, 1, '1 request was completed');
784 }); 857 });
785 858
786 QUnit.test('supplies media sequence of current segment as the IV by default, if no IV ' + 859 QUnit.test('supplies media sequence of current segment as the IV by default, if no IV ' +
...@@ -811,6 +884,10 @@ function() { ...@@ -811,6 +884,10 @@ function() {
811 884
812 QUnit.deepEqual(segment.key.iv, new Uint32Array([0, 0, 0, 5]), 885 QUnit.deepEqual(segment.key.iv, new Uint32Array([0, 0, 0, 5]),
813 'the IV for the segment is the media sequence'); 886 'the IV for the segment is the media sequence');
887
888 // verify stats
889 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
890 QUnit.equal(loader.mediaRequests, 1, '1 request');
814 }); 891 });
815 892
816 QUnit.test('segment with key has decrypted bytes appended during processing', function() { 893 QUnit.test('segment with key has decrypted bytes appended during processing', function() {
...@@ -839,6 +916,10 @@ QUnit.test('segment with key has decrypted bytes appended during processing', fu ...@@ -839,6 +916,10 @@ QUnit.test('segment with key has decrypted bytes appended during processing', fu
839 // Allow the decrypter's async stream to run the callback 916 // Allow the decrypter's async stream to run the callback
840 this.clock.tick(1); 917 this.clock.tick(1);
841 QUnit.ok(loader.pendingSegment_.bytes, 'decrypted bytes in segment'); 918 QUnit.ok(loader.pendingSegment_.bytes, 'decrypted bytes in segment');
919
920 // verify stats
921 QUnit.equal(loader.mediaBytesTransferred, 8, '8 bytes');
922 QUnit.equal(loader.mediaRequests, 1, '1 request');
842 }); 923 });
843 924
844 QUnit.test('calling load with an encrypted segment waits for both key and segment ' + 925 QUnit.test('calling load with an encrypted segment waits for both key and segment ' +
...@@ -864,6 +945,10 @@ QUnit.test('calling load with an encrypted segment waits for both key and segmen ...@@ -864,6 +945,10 @@ QUnit.test('calling load with an encrypted segment waits for both key and segmen
864 keyRequest.response = new Uint32Array([0, 0, 0, 0]).buffer; 945 keyRequest.response = new Uint32Array([0, 0, 0, 0]).buffer;
865 keyRequest.respond(200, null, ''); 946 keyRequest.respond(200, null, '');
866 QUnit.equal(loader.state, 'DECRYPTING', 'moves to decrypting state'); 947 QUnit.equal(loader.state, 'DECRYPTING', 'moves to decrypting state');
948
949 // verify stats
950 QUnit.equal(loader.mediaBytesTransferred, 10, '10 bytes');
951 QUnit.equal(loader.mediaRequests, 1, '1 request');
867 }); 952 });
868 953
869 QUnit.test('key request timeouts reset bandwidth', function() { 954 QUnit.test('key request timeouts reset bandwidth', function() {
......