Skip to content
This repository has been archived by the owner on Jan 12, 2019. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
jrivera committed May 31, 2016
2 parents 9ee2a7d + e45497b commit b0fe834
Show file tree
Hide file tree
Showing 8 changed files with 473 additions and 17 deletions.
37 changes: 36 additions & 1 deletion src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,40 @@ export default class MasterPlaylistController extends videojs.EventTarget {
this.masterPlaylistLoader_.load();
}

/**
* get the total number of media requests from the `audiosegmentloader_`
* and the `mainSegmentLoader_`
*
* @private
*/
mediaRequests_() {
return this.audioSegmentLoader_.mediaRequests +
this.mainSegmentLoader_.mediaRequests;
}

/**
* get the total time that media requests have spent trnasfering
* from the `audiosegmentloader_` and the `mainSegmentLoader_`
*
* @private
*/
mediaTransferDuration_() {
return this.audioSegmentLoader_.mediaTransferDuration +
this.mainSegmentLoader_.mediaTransferDuration;

}

/**
* get the total number of bytes transfered during media requests
* from the `audiosegmentloader_` and the `mainSegmentLoader_`
*
* @private
*/
mediaBytesTransferred_() {
return this.audioSegmentLoader_.mediaBytesTransferred +
this.mainSegmentLoader_.mediaBytesTransferred;
}

/**
* fill our internal list of HlsAudioTracks with data from
* the master playlist or use a default
Expand Down Expand Up @@ -350,7 +384,8 @@ export default class MasterPlaylistController extends videojs.EventTarget {

if (media !== this.masterPlaylistLoader_.media()) {
this.masterPlaylistLoader_.media(media);
this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5, Infinity);
this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5,
Infinity);
}
}

Expand Down
48 changes: 40 additions & 8 deletions src/ranges.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,27 +198,58 @@ const bufferIntersection = function(bufferA, bufferB) {
* covers adjusted according to currentTime
* @param {TimeRanges} referenceRange - the original time range that the
* segment covers
* @param {Number} currentTime - time in seconds where the current playback
* is at
* @param {TimeRanges} buffered - the currently buffered time ranges
* @returns {Number} percent of the segment currently buffered
*/
const calculateBufferedPercent = function(segmentRange, referenceRange, buffered) {
const calculateBufferedPercent = function(adjustedRange,
referenceRange,
currentTime,
buffered) {
let referenceDuration = referenceRange.end(0) - referenceRange.start(0);
let segmentDuration = segmentRange.end(0) - segmentRange.start(0);
let intersection = bufferIntersection(segmentRange, buffered);
let count = intersection.length;
let adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0);
let bufferMissingFromAdjusted = referenceDuration - adjustedDuration;
let adjustedIntersection = bufferIntersection(adjustedRange, buffered);
let referenceIntersection = bufferIntersection(referenceRange, buffered);
let adjustedOverlap = 0;
let referenceOverlap = 0;

let count = adjustedIntersection.length;

while (count--) {
segmentDuration -= intersection.end(count) - intersection.start(count);
adjustedOverlap += adjustedIntersection.end(count) -
adjustedIntersection.start(count);

// If the current overlap segment starts at currentTime, then increase the
// overlap duration so that it actually starts at the beginning of referenceRange
// by including the difference between the two Range's durations
// This is a work around for the way Flash has no buffer before currentTime
if (adjustedIntersection.start(count) === currentTime) {
adjustedOverlap += bufferMissingFromAdjusted;
}
}
return (referenceDuration - segmentDuration) / referenceDuration * 100;

count = referenceIntersection.length;

while (count--) {
referenceOverlap += referenceIntersection.end(count) -
referenceIntersection.start(count);
}

// Use whichever value is larger for the percentage-buffered since that value
// is likely more accurate because the only way
return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100;
};

/**
* Return the amount of a segment specified by the mediaIndex overlaps
* the current buffered content.
* Return the amount of a range specified by the startOfSegment and segmentDuration
* overlaps the current buffered content.
*
* @param {Number} startOfSegment - the time where the segment begins
* @param {Number} segmentDuration - the duration of the segment in seconds
* @param {Number} currentTime - time in seconds where the current playback
* is at
* @param {TimeRanges} buffered - the state of the buffer
* @returns {Number} percentage of the segment's time range that is
* already in `buffered`
Expand Down Expand Up @@ -254,6 +285,7 @@ const getSegmentBufferedPercent = function(startOfSegment,

let percent = calculateBufferedPercent(adjustedSegmentRange,
originalSegmentRange,
currentTime,
buffered);

// If the segment is reported as having a zero duration, return 0%
Expand Down
18 changes: 16 additions & 2 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default class SegmentLoader extends videojs.EventTarget {
this.state = 'INIT';
this.bandwidth = settings.bandwidth;
this.roundTrip = NaN;
this.bytesReceived = 0;
this.resetStats_();

// private properties
this.hasPlayed_ = settings.hasPlayed;
Expand All @@ -152,6 +152,17 @@ export default class SegmentLoader extends videojs.EventTarget {
this.hls_ = settings.hls;
}

/**
* reset all of our media stats
*
* @private
*/
resetStats_() {
this.mediaBytesTransferred = 0;
this.mediaRequests = 0;
this.mediaTransferDuration = 0;
}

/**
* dispose of the SegmentLoader and reset to the default state
*/
Expand All @@ -161,6 +172,7 @@ export default class SegmentLoader extends videojs.EventTarget {
if (this.sourceUpdater_) {
this.sourceUpdater_.dispose();
}
this.resetStats_();
}

/**
Expand Down Expand Up @@ -625,7 +637,9 @@ export default class SegmentLoader extends videojs.EventTarget {
// calculate the download bandwidth based on segment request
this.roundTrip = request.roundTripTime;
this.bandwidth = request.bandwidth;
this.bytesReceived += request.bytesReceived || 0;
this.mediaBytesTransferred += request.bytesReceived || 0;
this.mediaRequests += 1;
this.mediaTransferDuration += request.roundTripTime || 0;

if (segment.key) {
segmentInfo.encryptedBytes = new Uint8Array(request.response);
Expand Down
20 changes: 18 additions & 2 deletions src/videojs-contrib-hls.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,16 @@ class HlsHandler extends Component {

this.tech_ = tech;
this.source_ = source;
this.stats = {};

// handle global & Source Handler level options
this.options_ = videojs.mergeOptions(videojs.options.hls || {}, options.hls);
this.setOptions_();

this.bytesReceived = 0;
// start playlist selection at a reasonable bandwidth for
// broadband internet
// 0.5 Mbps
this.bandwidth = this.options_.bandwidth || 4194304;

// listen for fullscreenchange events for this player so that we
// can adjust our quality selection quickly
Expand Down Expand Up @@ -406,6 +410,19 @@ class HlsHandler extends Component {
}
});

Object.defineProperty(this.stats, 'bandwidth', {
get: () => this.bandwidth || 0
});
Object.defineProperty(this.stats, 'mediaRequests', {
get: () => this.masterPlaylistController_.mediaRequests_() || 0
});
Object.defineProperty(this.stats, 'mediaTransferDuration', {
get: () => this.masterPlaylistController_.mediaTransferDuration_() || 0
});
Object.defineProperty(this.stats, 'mediaBytesTransferred', {
get: () => this.masterPlaylistController_.mediaBytesTransferred_() || 0
});

this.tech_.one('canplay',
this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));

Expand Down Expand Up @@ -527,7 +544,6 @@ class HlsHandler extends Component {
this.masterPlaylistController_.dispose();
}
this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_);

super.dispose();
}
}
Expand Down
58 changes: 58 additions & 0 deletions test/master-playlist-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ QUnit.test('obeys none preload option', function() {
openMediaSource(this.player, this.clock);

QUnit.equal(this.requests.length, 0, 'no segment requests');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('obeys auto preload option', function() {
Expand All @@ -76,6 +79,9 @@ QUnit.test('obeys auto preload option', function() {
openMediaSource(this.player, this.clock);

QUnit.equal(this.requests.length, 1, '1 segment request');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('obeys metadata preload option', function() {
Expand All @@ -88,6 +94,9 @@ QUnit.test('obeys metadata preload option', function() {
openMediaSource(this.player, this.clock);

QUnit.equal(this.requests.length, 1, '1 segment request');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('clears some of the buffer for a fast quality change', function() {
Expand All @@ -114,6 +123,9 @@ QUnit.test('clears some of the buffer for a fast quality change', function() {
QUnit.equal(removes.length, 1, 'removed buffered content');
QUnit.equal(removes[0].start, 7 + 5, 'removed from a bit after current time');
QUnit.equal(removes[0].end, Infinity, 'removed to the end');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('does not clear the buffer when no fast quality change occurs', function() {
Expand All @@ -134,6 +146,8 @@ QUnit.test('does not clear the buffer when no fast quality change occurs', funct
this.masterPlaylistController.fastQualityChange_();

QUnit.equal(removes.length, 0, 'did not remove content');
// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('if buffered, will request second segment byte range', function() {
Expand Down Expand Up @@ -163,6 +177,13 @@ QUnit.test('if buffered, will request second segment byte range', function() {
this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
this.clock.tick(10 * 1000);
QUnit.equal(this.requests[2].headers.Range, 'bytes=1823412-2299991');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
QUnit.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
16,
'16 bytes downloaded');
});

QUnit.test('re-initializes the combined playlist loader when switching sources',
Expand Down Expand Up @@ -218,6 +239,8 @@ QUnit.test('updates the combined segment loader on live playlist refreshes', fun

this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist');
QUnit.equal(updates.length, 1, 'updated the segment list');
// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test(
Expand All @@ -240,6 +263,13 @@ function() {
standardXHRResponse(this.requests.shift());
this.masterPlaylistController.mainSegmentLoader_.trigger('progress');
QUnit.equal(progressCount, 1, 'fired a progress event');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
QUnit.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
16,
'16 bytes downloaded');
});

QUnit.test('blacklists switching from video+audio playlists to audio only', function() {
Expand All @@ -264,6 +294,9 @@ QUnit.test('blacklists switching from video+audio playlists to audio only', func
'selected video+audio');
audioPlaylist = this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0];
QUnit.equal(audioPlaylist.excludeUntil, Infinity, 'excluded incompatible playlist');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1e10, 'bandwidth we set above');
});

QUnit.test('blacklists switching from audio-only playlists to video+audio', function() {
Expand All @@ -290,6 +323,9 @@ QUnit.test('blacklists switching from audio-only playlists to video+audio', func
QUnit.equal(videoAudioPlaylist.excludeUntil,
Infinity,
'excluded incompatible playlist');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
});

QUnit.test('blacklists switching from video-only playlists to video+audio', function() {
Expand Down Expand Up @@ -317,6 +353,9 @@ QUnit.test('blacklists switching from video-only playlists to video+audio', func
QUnit.equal(videoAudioPlaylist.excludeUntil,
Infinity,
'excluded incompatible playlist');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
});

QUnit.test('blacklists switching between playlists with incompatible audio codecs',
Expand All @@ -343,6 +382,8 @@ function() {
alternatePlaylist =
this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1];
QUnit.equal(alternatePlaylist.excludeUntil, Infinity, 'excluded incompatible playlist');
// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
});

QUnit.test('updates the combined segment loader on media changes', function() {
Expand All @@ -369,6 +410,14 @@ QUnit.test('updates the combined segment loader on media changes', function() {
// media
standardXHRResponse(this.requests.shift());
QUnit.equal(updates.length, 1, 'updated the segment list');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
QUnit.equal(
this.player.tech_.hls.stats.mediaBytesTransferred,
16,
'16 bytes downloaded');
});

QUnit.test('selects a playlist after main/combined segment downloads', function() {
Expand All @@ -392,6 +441,8 @@ QUnit.test('selects a playlist after main/combined segment downloads', function(
// and another
this.masterPlaylistController.mainSegmentLoader_.trigger('progress');
QUnit.strictEqual(calls, 3, 'selects after additional segments');
// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
});

QUnit.test('updates the duration after switching playlists', function() {
Expand Down Expand Up @@ -424,6 +475,13 @@ QUnit.test('updates the duration after switching playlists', function() {
QUnit.ok(selectedPlaylist, 'selected playlist');
QUnit.ok(this.masterPlaylistController.mediaSource.duration !== 0,
'updates the duration');

// verify stats
QUnit.equal(this.player.tech_.hls.stats.bandwidth, Infinity, 'Live stream');
QUnit.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
QUnit.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
16,
'16 bytes downloaded');
});

QUnit.test('seekable uses the intersection of alternate audio and combined tracks',
Expand Down
Loading

0 comments on commit b0fe834

Please sign in to comment.