76a27259 by David LaPalomento

Merge pull request #96 from videojs/feature/metrics

Expose more diagnostic info
2 parents 56043011 9edf081a
......@@ -116,6 +116,11 @@ The number of bits downloaded per second in the last segment download.
This value is used by the default implementation of `selectPlaylist`
to select an appropriate bitrate to play.
#### player.hls.bytesReceived
Type: `number`
The total number of content bytes downloaded by the HLS tech.
#### player.hls.selectPlaylist
Type: `function`
......@@ -136,6 +141,13 @@ Fired immediately after a new master or media playlist has been
downloaded. By default, the tech only downloads playlists as they
are needed.
#### mediachange
Fired when a new playlist becomes the active media playlist. Note that
the actual rendering quality change does not occur simultaneously with
this event; a new segment must be requested and the existing buffer
depleted first.
### Testing
For testing, you can either run `npm test` or use `grunt` directly.
......
......@@ -131,6 +131,7 @@
* object to switch to
*/
loader.media = function(playlist) {
var mediaChange = false;
// getter
if (!playlist) {
return media;
......@@ -150,19 +151,27 @@
playlist = loader.master.playlists[playlist];
}
mediaChange = playlist.uri !== media.uri;
// switch to fully loaded playlists immediately
if (loader.master.playlists[playlist.uri].endList) {
// abort outstanding playlist requests
if (request) {
request.abort();
request = null;
}
loader.state = 'HAVE_METADATA';
media = playlist;
// trigger media change if the active media has been updated
if (mediaChange) {
loader.trigger('mediachange');
}
return;
}
// switching to the active playlist is a no-op
if (playlist.uri === media.uri) {
if (!mediaChange) {
return;
}
......@@ -185,6 +194,7 @@
withCredentials: withCredentials
}, function(error) {
haveMetadata(error, this, playlist.uri);
loader.trigger('mediachange');
});
};
......
......@@ -433,6 +433,7 @@ var
// calculate the download bandwidth
player.hls.segmentXhrTime = (+new Date()) - startTime;
player.hls.bandwidth = (this.response.byteLength / player.hls.segmentXhrTime) * 8 * 1000;
player.hls.bytesReceived += this.response.byteLength;
// transmux the segment data from MP2T to FLV
segmentParser.parseSegmentBinaryData(new Uint8Array(this.response));
......@@ -575,6 +576,9 @@ var
updatedPlaylist);
oldMediaPlaylist = updatedPlaylist;
});
player.hls.playlists.on('mediachange', function() {
player.trigger('mediachange');
});
});
};
......@@ -591,6 +595,8 @@ videojs.Hls = videojs.Flash.extend({
options.swf = settings.flash.swf;
videojs.Flash.call(this, player, options, ready);
options.source = source;
this.bytesReceived = 0;
videojs.Hls.prototype.src.call(this, options.source && options.source.src);
}
});
......
......@@ -510,4 +510,45 @@
strictEqual(errors, 1, 'fired one error');
strictEqual(loader.error.code, 2, 'fired a network error');
});
test('triggers an event when the active media changes', function() {
var
loader = new videojs.Hls.PlaylistLoader('master.m3u8'),
mediaChanges = 0;
loader.on('mediachange', function() {
mediaChanges++;
});
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
'low.m3u8\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=2\n' +
'high.m3u8\n');
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXTINF:10,\n' +
'low-0.ts\n' +
'#EXT-X-ENDLIST\n');
strictEqual(mediaChanges, 0, 'initial selection is not a media change');
loader.media('high.m3u8');
strictEqual(mediaChanges, 0, 'mediachange does not fire immediately');
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXTINF:10,\n' +
'high-0.ts\n' +
'#EXT-X-ENDLIST\n');
strictEqual(mediaChanges, 1, 'fired a mediachange');
// switch back to an already loaded playlist
loader.media('low.m3u8');
strictEqual(mediaChanges, 2, 'fired a mediachange');
// trigger a no-op switch
loader.media('low.m3u8');
strictEqual(mediaChanges, 2, 'ignored a no-op media change');
});
})(window);
......
......@@ -1227,4 +1227,55 @@ test('has no effect if native HLS is available', function() {
player.dispose();
});
test('tracks the bytes downloaded', function() {
player.src({
src: 'http://example.com/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
strictEqual(player.hls.bytesReceived, 0, 'no bytes received');
requests.shift().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n' +
'#EXTINF:10,\n' +
'1.ts\n' +
'#EXT-X-ENDLIST\n');
// transmit some segment bytes
requests[0].response = new ArrayBuffer(17);
requests.shift().respond(200, null, '');
strictEqual(player.hls.bytesReceived, 17, 'tracked bytes received');
player.trigger('timeupdate');
// transmit some more
requests[0].response = new ArrayBuffer(5);
requests.shift().respond(200, null, '');
strictEqual(player.hls.bytesReceived, 22, 'tracked more bytes');
});
test('re-emits mediachange events', function() {
var mediaChanges = 0;
player.on('mediachange', function() {
mediaChanges++;
});
player.src({
src: 'http://example.com/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
player.hls.playlists.trigger('mediachange');
strictEqual(mediaChanges, 1, 'fired mediachange');
});
})(window, window.videojs);
......