Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getVideoPlaybackQuality API #4286

Merged
merged 5 commits into from May 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -41,7 +41,7 @@
"tsml": "1.0.1",
"videojs-font": "2.0.0",
"videojs-ie8": "1.1.2",
"videojs-swf": "5.3.0",
"videojs-swf": "5.4.0",
"videojs-vtt.js": "0.12.3",
"xhr": "2.2.2"
},
Expand Down
14 changes: 14 additions & 0 deletions src/js/player.js
Expand Up @@ -2988,6 +2988,20 @@ class Player extends Component {
}
}

/**
* Gets available media playback quality metrics as specified by the W3C's Media
* Playback Quality API.
*
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
*
* @return {Object|undefined}
* An object with supported media playback quality metrics or undefined if there
* is no tech or the tech does not support it.
*/
getVideoPlaybackQuality() {
return this.techGet_('getVideoPlaybackQuality');
}

/**
* Get video width
*
Expand Down
23 changes: 23 additions & 0 deletions src/js/tech/flash.js
Expand Up @@ -341,6 +341,29 @@ class Flash extends Tech {
return false;
}

/**
* Gets available media playback quality metrics as specified by the W3C's Media
* Playback Quality API.
*
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
*
* @return {Object}
* An object with supported media playback quality metrics
*/
getVideoPlaybackQuality() {
const videoPlaybackQuality = this.el_.vjs_getProperty('getVideoPlaybackQuality');

if (window.performance && typeof window.performance.now === 'function') {
videoPlaybackQuality.creationTime = window.performance.now();
} else if (window.performance &&
window.performance.timing &&
typeof window.performance.timing.navigationStart === 'number') {
videoPlaybackQuality.creationTime =
window.Date.now() - window.performance.timing.navigationStart;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we decide to have no value for creationTime if timing.navigationStart isn't available?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By looking at the tests, I guess the answer is yes.

}

return videoPlaybackQuality;
}
}

// Create setters and getters for attributes
Expand Down
34 changes: 34 additions & 0 deletions src/js/tech/html5.js
Expand Up @@ -794,6 +794,40 @@ class Html5 extends Tech {
}
}
}

/**
* Gets available media playback quality metrics as specified by the W3C's Media
* Playback Quality API.
*
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
*
* @return {Object}
* An object with supported media playback quality metrics
*/
getVideoPlaybackQuality() {
if (typeof this.el().getVideoPlaybackQuality === 'function') {
return this.el().getVideoPlaybackQuality();
}

const videoPlaybackQuality = {};

if (typeof this.el().webkitDroppedFrameCount !== 'undefined' &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible for only one to be available but not the other?
Also, do we want similar thing for the firefox mozilla prefixed items?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by https://bugs.webkit.org/attachment.cgi?id=80515&action=diff (which I think is the right patch), the webkit prefixed properties were added at the same time.

As for the mozilla prefixed properties, there do seem to be some:mozParsedFrames, mozDecodedFrames, mozPresentedFrames, mozPaintedFrames, and mozFrameDelay, but since they aren't 1:1 matches, I'm not sure it would be worth trying to use them.

typeof this.el().webkitDecodedFrameCount !== 'undefined') {
videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
}

if (window.performance && typeof window.performance.now === 'function') {
videoPlaybackQuality.creationTime = window.performance.now();
} else if (window.performance &&
window.performance.timing &&
typeof window.performance.timing.navigationStart === 'number') {
videoPlaybackQuality.creationTime =
window.Date.now() - window.performance.timing.navigationStart;
}

return videoPlaybackQuality;
}
}

/* HTML5 Support Testing ---------------------------------------------------- */
Expand Down
15 changes: 15 additions & 0 deletions src/js/tech/tech.js
Expand Up @@ -805,6 +805,21 @@ class Tech extends Component {
this.autoRemoteTextTracks_.removeTrack_(track);
}

/**
* Gets available media playback quality metrics as specified by the W3C's Media
* Playback Quality API.
*
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
*
* @return {Object}
* An object with supported media playback quality metrics
*
* @abstract
*/
getVideoPlaybackQuality() {
return {};
}

/**
* A method to set a poster from a `Tech`.
*
Expand Down
1 change: 1 addition & 0 deletions test/api/api.js
Expand Up @@ -59,6 +59,7 @@ QUnit.test('should be able to access expected player API methods', function(asse
assert.ok(player.userActive, 'userActive exists');
assert.ok(player.usingNativeControls, 'usingNativeControls exists');
assert.ok(player.isFullscreen, 'isFullscreen exists');
assert.ok(player.getVideoPlaybackQuality, 'getVideoPlaybackQuality exists');

// Track methods
assert.ok(player.audioTracks, 'audioTracks exists');
Expand Down
79 changes: 79 additions & 0 deletions test/unit/tech/flash.test.js
Expand Up @@ -2,6 +2,7 @@
import Flash from '../../../src/js/tech/flash.js';
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
import document from 'global/document';
import window from 'global/window';
import sinon from 'sinon';

// fake out the <object> interaction but leave all the other logic intact
Expand Down Expand Up @@ -261,3 +262,81 @@ QUnit.test('duration returns NaN, Infinity or duration according to the HTML sta
'duration returns duration property when readyState' +
' and duration property are both higher than 0');
});

QUnit.test('getVideoPlaybackQuality API exists', function(assert) {
const propertyCalls = [];
const videoPlaybackQuality = { test: 'test' };
const mockFlash = {
el_: {
/* eslint-disable camelcase */
vjs_getProperty(attr) {
propertyCalls.push(attr);
return videoPlaybackQuality;
}
/* eslint-enable camelcase */
}
};

assert.equal(typeof Flash.prototype.getVideoPlaybackQuality,
'function',
'getVideoPlaybackQuality is a function');
assert.deepEqual(Flash.prototype.getVideoPlaybackQuality.call(mockFlash),
videoPlaybackQuality,
'called to get property from flash');
assert.equal(propertyCalls.length, 1, 'only one property call');
assert.equal(propertyCalls[0],
'getVideoPlaybackQuality',
'called for getVideoPlaybackQuality');
});

QUnit.test('getVideoPlaybackQuality uses best available creationTime', function(assert) {
const origPerformance = window.performance;
const origDate = window.Date;
const videoPlaybackQuality = {};
const mockFlash = {
el_: {
/* eslint-disable camelcase */
vjs_getProperty(attr) {
return videoPlaybackQuality;
}
/* eslint-enable camelcase */
}
};

window.performance = void 0;
assert.notOk(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
'no creationTime when no performance API available');

window.performance = {
timing: {}
};
assert.notOk(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
'no creationTime when performance API insufficient');

window.performance = {
now: () => 4
};
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
4,
'creationTime is performance.now when available');

window.Date = {
now: () => 10
};
window.performance = {
timing: {
navigationStart: 3
}
};
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
7,
'creationTime uses Date.now() - navigationStart when available');

window.performance.now = () => 4;
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
4,
'creationTime prioritizes performance.now when available');

window.Date = origDate;
window.performance = origPerformance;
});
80 changes: 80 additions & 0 deletions test/unit/tech/html5.test.js
Expand Up @@ -641,3 +641,83 @@ test('When Android Chrome reports Infinity duration with currentTime 0, return N
browser.IS_CHROME = oldIsChrome;
tech.el_ = oldEl;
});

QUnit.test('supports getting available media playback quality metrics', function(assert) {
const origPerformance = window.performance;
const origDate = window.Date;
const oldEl = tech.el_;
const videoPlaybackQuality = {
creationTime: 1,
corruptedVideoFrames: 2,
droppedVideoFrames: 3,
totalVideoFrames: 5
};

tech.el_ = {
getVideoPlaybackQuality: () => videoPlaybackQuality
};
assert.deepEqual(tech.getVideoPlaybackQuality(),
videoPlaybackQuality,
'uses native implementation when supported');

tech.el_ = {
webkitDroppedFrameCount: 1,
webkitDecodedFrameCount: 2
};
window.performance = {
now: () => 4
};
assert.deepEqual(tech.getVideoPlaybackQuality(),
{ droppedVideoFrames: 1, totalVideoFrames: 2, creationTime: 4 },
'uses webkit prefixed metrics and performance.now when supported');

tech.el_ = {
webkitDroppedFrameCount: 1,
webkitDecodedFrameCount: 2
};
window.Date = {
now: () => 10
};
window.performance = {
timing: {
navigationStart: 3
}
};
assert.deepEqual(tech.getVideoPlaybackQuality(),
{ droppedVideoFrames: 1, totalVideoFrames: 2, creationTime: 7 },
'uses webkit prefixed metrics and Date.now() - navigationStart when ' +
'supported');

tech.el_ = {};
window.performance = void 0;
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'empty object when not supported');

window.performance = {
now: () => 5
};
assert.deepEqual(tech.getVideoPlaybackQuality(),
{ creationTime: 5 },
'only creation time when it\'s the only piece available');

window.performance = {
timing: {
navigationStart: 3
}
};
assert.deepEqual(tech.getVideoPlaybackQuality(),
{ creationTime: 7 },
'only creation time when it\'s the only piece available');

tech.el_ = {
getVideoPlaybackQuality: () => videoPlaybackQuality,
webkitDroppedFrameCount: 1,
webkitDecodedFrameCount: 2
};
assert.deepEqual(tech.getVideoPlaybackQuality(),
videoPlaybackQuality,
'prefers native implementation when supported');

tech.el_ = oldEl;
window.performance = origPerformance;
window.Date = origDate;
});
6 changes: 6 additions & 0 deletions test/unit/tech/tech.test.js
Expand Up @@ -631,3 +631,9 @@ QUnit.test('setSource after previous setSource should dispose source handler onc

});

QUnit.test('returns an empty object for getVideoPlaybackQuality', function(assert) {
const tech = new Tech();

assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'returns an empty object');
});