Skip to content

Commit

Permalink
feat: stats for timeToLoadedData, appendsToLoadedData, mainAppendsToL…
Browse files Browse the repository at this point in the history
…oadedData, audioAppendsToLoadedData, and mediaAppends (#1106)
  • Loading branch information
brandonocasey authored May 27, 2021
1 parent 82b6781 commit 3124fbc
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 1 deletion.
44 changes: 43 additions & 1 deletion src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const loaderStats = [
'mediaRequestsTimedout',
'mediaRequestsErrored',
'mediaTransferDuration',
'mediaBytesTransferred'
'mediaBytesTransferred',
'mediaAppends'
];
const sumLoaderStat = function(stat) {
return this.audioSegmentLoader_[stat] +
Expand Down Expand Up @@ -270,6 +271,7 @@ export class MasterPlaylistController extends videojs.EventTarget {
// mediaRequestsErrored_
// mediaTransferDuration_
// mediaBytesTransferred_
// mediaAppends_
loaderStats.forEach((stat) => {
this[stat + '_'] = sumLoaderStat.bind(this, stat);
});
Expand All @@ -287,6 +289,46 @@ export class MasterPlaylistController extends videojs.EventTarget {
} else {
this.masterPlaylistLoader_.load();
}

this.timeToLoadedData__ = -1;
this.mainAppendsToLoadedData__ = -1;
this.audioAppendsToLoadedData__ = -1;

const event = this.tech_.preload() === 'none' ? 'play' : 'loadstart';

// start the first frame timer on loadstart or play (for preload none)
this.tech_.one(event, () => {
const timeToLoadedDataStart = Date.now();

this.tech_.one('loadeddata', () => {
this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart;
this.mainAppendsToLoadedData__ = this.mainSegmentLoader_.mediaAppends;
this.audioAppendsToLoadedData__ = this.audioSegmentLoader_.mediaAppends;
});
});
}

mainAppendsToLoadedData_() {
return this.mainAppendsToLoadedData__;
}

audioAppendsToLoadedData_() {
return this.audioAppendsToLoadedData__;
}

appendsToLoadedData_() {
const main = this.mainAppendsToLoadedData_();
const audio = this.audioAppendsToLoadedData_();

if (main === -1 || audio === -1) {
return -1;
}

return main + audio;
}

timeToLoadedData_() {
return this.timeToLoadedData__;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ export default class SegmentLoader extends videojs.EventTarget {
this.mediaRequestsErrored = 0;
this.mediaTransferDuration = 0;
this.mediaSecondsLoaded = 0;
this.mediaAppends = 0;
}

/**
Expand Down Expand Up @@ -3004,6 +3005,10 @@ export default class SegmentLoader extends videojs.EventTarget {
// used for testing
this.trigger('appended');

if (segmentInfo.hasAppendedData_) {
this.mediaAppends++;
}

if (!this.paused()) {
this.monitorBuffer_();
}
Expand Down
20 changes: 20 additions & 0 deletions src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,26 @@ class VhsHandler extends Component {
get: () => this.masterPlaylistController_.mediaSecondsLoaded_() || 0,
enumerable: true
},
mediaAppends: {
get: () => this.masterPlaylistController_.mediaAppends_() || 0,
enumerable: true
},
mainAppendsToLoadedData: {
get: () => this.masterPlaylistController_.mainAppendsToLoadedData_() || 0,
enumerable: true
},
audioAppendsToLoadedData: {
get: () => this.masterPlaylistController_.audioAppendsToLoadedData_() || 0,
enumerable: true
},
appendsToLoadedData: {
get: () => this.masterPlaylistController_.appendsToLoadedData_() || 0,
enumerable: true
},
timeToLoadedData: {
get: () => this.masterPlaylistController_.timeToLoadedData_() || 0,
enumerable: true
},
buffered: {
get: () => timeRangesToArray(this.tech_.buffered()),
enumerable: true
Expand Down
173 changes: 173 additions & 0 deletions test/master-playlist-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,179 @@ QUnit.test('seeks in place for fast quality switch on non-IE/Edge browsers', fun
});
});

QUnit.test('basic timeToLoadedData, mediaAppends, appendsToLoadedData stats', function(assert) {
this.player.tech_.trigger('loadstart');
this.masterPlaylistController.mediaSource.trigger('sourceopen');
// master
this.standardXHRResponse(this.requests.shift());
// media
this.standardXHRResponse(this.requests.shift());

const segmentLoader = this.masterPlaylistController.mainSegmentLoader_;

return requestAndAppendSegment({
request: this.requests.shift(),
segmentLoader,
clock: this.clock
}).then(() => {
this.player.tech_.trigger('loadeddata');
const vhs = this.player.tech_.vhs;

assert.equal(vhs.stats.mediaAppends, 1, 'one media append');
assert.equal(vhs.stats.appendsToLoadedData, 1, 'appends to first frame is also 1');
assert.equal(vhs.stats.mainAppendsToLoadedData, 1, 'main appends to first frame is also 1');
assert.equal(vhs.stats.audioAppendsToLoadedData, 0, 'audio appends to first frame is 0');
assert.ok(vhs.stats.timeToLoadedData > 0, 'time to first frame is valid');
});
});

QUnit.test('timeToLoadedData, mediaAppends, appendsToLoadedData stats with 0 length appends', function(assert) {
this.player.tech_.trigger('loadstart');
this.masterPlaylistController.mediaSource.trigger('sourceopen');
// master
this.standardXHRResponse(this.requests.shift());
// media
this.standardXHRResponse(this.requests.shift());

const segmentLoader = this.masterPlaylistController.mainSegmentLoader_;

return requestAndAppendSegment({
request: this.requests.shift(),
segmentLoader,
clock: this.clock
}).then(() => {
// mock a zero length segment, by setting hasAppendedData_ to false.
segmentLoader.one('appendsdone', () => {
segmentLoader.pendingSegment_.hasAppendedData_ = false;
});
return requestAndAppendSegment({
request: this.requests.shift(),
segmentLoader,
clock: this.clock
});
}).then(() => {

this.player.tech_.trigger('loadeddata');
const vhs = this.player.tech_.vhs;

// only one media append as the second was zero length.
assert.equal(vhs.stats.mediaAppends, 1, 'one media append');
assert.equal(vhs.stats.appendsToLoadedData, 1, 'appends to first frame is also 1');
assert.equal(vhs.stats.mainAppendsToLoadedData, 1, 'main appends to first frame is also 1');
assert.equal(vhs.stats.audioAppendsToLoadedData, 0, 'audio appends to first frame is 0');
assert.ok(vhs.stats.timeToLoadedData > 0, 'time to first frame is valid');
});
});

QUnit.test('preload none timeToLoadedData, mediaAppends, appendsToLoadedData stats', function(assert) {
this.requests.length = 0;
this.player.dispose();
this.player = createPlayer();
this.player.tech_.preload = () => 'none';

this.player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});

this.clock.tick(1);
const vhs = this.player.tech_.vhs;

this.masterPlaylistController = vhs.masterPlaylistController_;
this.masterPlaylistController.mediaSource.trigger('sourceopen');

assert.equal(this.requests.length, 0, 'no requests request');
assert.equal(vhs.stats.mediaAppends, 0, 'one media append');
assert.equal(vhs.stats.appendsToLoadedData, -1, 'appends to first frame is -1');
assert.equal(vhs.stats.mainAppendsToLoadedData, -1, 'main appends to first frame is -1');
assert.equal(vhs.stats.audioAppendsToLoadedData, -1, 'audio appends to first frame is -1');
assert.equal(vhs.stats.timeToLoadedData, -1, 'time to first frame is -1');

this.player.tech_.paused = () => false;
this.player.tech_.trigger('play');

// master
this.standardXHRResponse(this.requests.shift());

// media
this.standardXHRResponse(this.requests.shift());

const segmentLoader = this.masterPlaylistController.mainSegmentLoader_;

return requestAndAppendSegment({
request: this.requests.shift(),
segmentLoader,
clock: this.clock
}).then(() => {
this.player.tech_.trigger('loadeddata');

assert.equal(vhs.stats.mediaAppends, 1, 'one media append');
assert.equal(vhs.stats.appendsToLoadedData, 1, 'appends to first frame is also 1');
assert.equal(vhs.stats.mainAppendsToLoadedData, 1, 'main appends to first frame is also 1');
assert.equal(vhs.stats.audioAppendsToLoadedData, 0, 'audio appends to first frame is 0');
assert.ok(vhs.stats.timeToLoadedData > 0, 'time to first frame is valid');
});
});

QUnit.test('demuxed timeToLoadedData, mediaAppends, appendsToLoadedData stats', function(assert) {
this.player.tech_.trigger('loadstart');
const mpc = this.masterPlaylistController;

const videoMedia = '#EXTM3U\n' +
'#EXT-X-VERSION:3\n' +
'#EXT-X-PLAYLIST-TYPE:VOD\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXT-X-TARGETDURATION:10\n' +
'#EXTINF:10,\n' +
'video-0.ts\n' +
'#EXTINF:10,\n' +
'video-1.ts\n' +
'#EXT-X-ENDLIST\n';

const audioMedia = '#EXTM3U\n' +
'#EXT-X-VERSION:3\n' +
'#EXT-X-PLAYLIST-TYPE:VOD\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXT-X-TARGETDURATION:10\n' +
'#EXTINF:10,\n' +
'audio-0.ts\n' +
'#EXTINF:10,\n' +
'audio-1.ts\n' +
'#EXT-X-ENDLIST\n';

mpc.mediaSource.trigger('sourceopen');
// master
this.standardXHRResponse(this.requests.shift(), manifests.demuxed);

// video media
this.standardXHRResponse(this.requests.shift(), videoMedia);

// audio media
this.standardXHRResponse(this.requests.shift(), audioMedia);
return Promise.all([requestAndAppendSegment({
request: this.requests.shift(),
segment: videoSegment(),
isOnlyVideo: true,
segmentLoader: mpc.mainSegmentLoader_,
clock: this.clock
}), requestAndAppendSegment({
request: this.requests.shift(),
segment: audioSegment(),
isOnlyAudio: true,
segmentLoader: mpc.audioSegmentLoader_,
clock: this.clock
})]).then(() => {
this.player.tech_.trigger('loadeddata');
const vhs = this.player.tech_.vhs;

assert.equal(vhs.stats.mediaAppends, 2, 'two media append');
assert.equal(vhs.stats.appendsToLoadedData, 2, 'appends to first frame is also 2');
assert.equal(vhs.stats.mainAppendsToLoadedData, 1, 'main appends to first frame is 1');
assert.equal(vhs.stats.audioAppendsToLoadedData, 1, 'audio appends to first frame is 1');
assert.ok(vhs.stats.timeToLoadedData > 0, 'time to first frame is valid');
});
});

QUnit.test('seeks forward 0.04 sec for fast quality switch on Edge', function(assert) {
const oldIEVersion = videojs.browser.IE_VERSION;
const oldIsEdge = videojs.browser.IS_EDGE;
Expand Down

0 comments on commit 3124fbc

Please sign in to comment.