Skip to content

Commit c970474

Browse files
gesingergkatsev
authored andcommitted
feat: Add getVideoPlaybackQuality API (#4286)
Provides a tech getter for getVideoPlaybackQuality as specified by the W3C's Media Playback Quality API: https://wicg.github.io/media-playback-quality/.
1 parent b855bfb commit c970474

9 files changed

Lines changed: 253 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"tsml": "1.0.1",
4242
"videojs-font": "2.0.0",
4343
"videojs-ie8": "1.1.2",
44-
"videojs-swf": "5.3.0",
44+
"videojs-swf": "5.4.0",
4545
"videojs-vtt.js": "0.12.3",
4646
"xhr": "2.2.2"
4747
},

src/js/player.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,6 +2993,20 @@ class Player extends Component {
29932993
}
29942994
}
29952995

2996+
/**
2997+
* Gets available media playback quality metrics as specified by the W3C's Media
2998+
* Playback Quality API.
2999+
*
3000+
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
3001+
*
3002+
* @return {Object|undefined}
3003+
* An object with supported media playback quality metrics or undefined if there
3004+
* is no tech or the tech does not support it.
3005+
*/
3006+
getVideoPlaybackQuality() {
3007+
return this.techGet_('getVideoPlaybackQuality');
3008+
}
3009+
29963010
/**
29973011
* Get video width
29983012
*

src/js/tech/flash.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,29 @@ class Flash extends Tech {
341341
return false;
342342
}
343343

344+
/**
345+
* Gets available media playback quality metrics as specified by the W3C's Media
346+
* Playback Quality API.
347+
*
348+
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
349+
*
350+
* @return {Object}
351+
* An object with supported media playback quality metrics
352+
*/
353+
getVideoPlaybackQuality() {
354+
const videoPlaybackQuality = this.el_.vjs_getProperty('getVideoPlaybackQuality');
355+
356+
if (window.performance && typeof window.performance.now === 'function') {
357+
videoPlaybackQuality.creationTime = window.performance.now();
358+
} else if (window.performance &&
359+
window.performance.timing &&
360+
typeof window.performance.timing.navigationStart === 'number') {
361+
videoPlaybackQuality.creationTime =
362+
window.Date.now() - window.performance.timing.navigationStart;
363+
}
364+
365+
return videoPlaybackQuality;
366+
}
344367
}
345368

346369
// Create setters and getters for attributes

src/js/tech/html5.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,40 @@ class Html5 extends Tech {
794794
}
795795
}
796796
}
797+
798+
/**
799+
* Gets available media playback quality metrics as specified by the W3C's Media
800+
* Playback Quality API.
801+
*
802+
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
803+
*
804+
* @return {Object}
805+
* An object with supported media playback quality metrics
806+
*/
807+
getVideoPlaybackQuality() {
808+
if (typeof this.el().getVideoPlaybackQuality === 'function') {
809+
return this.el().getVideoPlaybackQuality();
810+
}
811+
812+
const videoPlaybackQuality = {};
813+
814+
if (typeof this.el().webkitDroppedFrameCount !== 'undefined' &&
815+
typeof this.el().webkitDecodedFrameCount !== 'undefined') {
816+
videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
817+
videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
818+
}
819+
820+
if (window.performance && typeof window.performance.now === 'function') {
821+
videoPlaybackQuality.creationTime = window.performance.now();
822+
} else if (window.performance &&
823+
window.performance.timing &&
824+
typeof window.performance.timing.navigationStart === 'number') {
825+
videoPlaybackQuality.creationTime =
826+
window.Date.now() - window.performance.timing.navigationStart;
827+
}
828+
829+
return videoPlaybackQuality;
830+
}
797831
}
798832

799833
/* HTML5 Support Testing ---------------------------------------------------- */

src/js/tech/tech.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,21 @@ class Tech extends Component {
805805
this.autoRemoteTextTracks_.removeTrack_(track);
806806
}
807807

808+
/**
809+
* Gets available media playback quality metrics as specified by the W3C's Media
810+
* Playback Quality API.
811+
*
812+
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
813+
*
814+
* @return {Object}
815+
* An object with supported media playback quality metrics
816+
*
817+
* @abstract
818+
*/
819+
getVideoPlaybackQuality() {
820+
return {};
821+
}
822+
808823
/**
809824
* A method to set a poster from a `Tech`.
810825
*

test/api/api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ QUnit.test('should be able to access expected player API methods', function(asse
5959
assert.ok(player.userActive, 'userActive exists');
6060
assert.ok(player.usingNativeControls, 'usingNativeControls exists');
6161
assert.ok(player.isFullscreen, 'isFullscreen exists');
62+
assert.ok(player.getVideoPlaybackQuality, 'getVideoPlaybackQuality exists');
6263

6364
// Track methods
6465
assert.ok(player.audioTracks, 'audioTracks exists');

test/unit/tech/flash.test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import Flash from '../../../src/js/tech/flash.js';
33
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
44
import document from 'global/document';
5+
import window from 'global/window';
56
import sinon from 'sinon';
67

78
// fake out the <object> interaction but leave all the other logic intact
@@ -261,3 +262,81 @@ QUnit.test('duration returns NaN, Infinity or duration according to the HTML sta
261262
'duration returns duration property when readyState' +
262263
' and duration property are both higher than 0');
263264
});
265+
266+
QUnit.test('getVideoPlaybackQuality API exists', function(assert) {
267+
const propertyCalls = [];
268+
const videoPlaybackQuality = { test: 'test' };
269+
const mockFlash = {
270+
el_: {
271+
/* eslint-disable camelcase */
272+
vjs_getProperty(attr) {
273+
propertyCalls.push(attr);
274+
return videoPlaybackQuality;
275+
}
276+
/* eslint-enable camelcase */
277+
}
278+
};
279+
280+
assert.equal(typeof Flash.prototype.getVideoPlaybackQuality,
281+
'function',
282+
'getVideoPlaybackQuality is a function');
283+
assert.deepEqual(Flash.prototype.getVideoPlaybackQuality.call(mockFlash),
284+
videoPlaybackQuality,
285+
'called to get property from flash');
286+
assert.equal(propertyCalls.length, 1, 'only one property call');
287+
assert.equal(propertyCalls[0],
288+
'getVideoPlaybackQuality',
289+
'called for getVideoPlaybackQuality');
290+
});
291+
292+
QUnit.test('getVideoPlaybackQuality uses best available creationTime', function(assert) {
293+
const origPerformance = window.performance;
294+
const origDate = window.Date;
295+
const videoPlaybackQuality = {};
296+
const mockFlash = {
297+
el_: {
298+
/* eslint-disable camelcase */
299+
vjs_getProperty(attr) {
300+
return videoPlaybackQuality;
301+
}
302+
/* eslint-enable camelcase */
303+
}
304+
};
305+
306+
window.performance = void 0;
307+
assert.notOk(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
308+
'no creationTime when no performance API available');
309+
310+
window.performance = {
311+
timing: {}
312+
};
313+
assert.notOk(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
314+
'no creationTime when performance API insufficient');
315+
316+
window.performance = {
317+
now: () => 4
318+
};
319+
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
320+
4,
321+
'creationTime is performance.now when available');
322+
323+
window.Date = {
324+
now: () => 10
325+
};
326+
window.performance = {
327+
timing: {
328+
navigationStart: 3
329+
}
330+
};
331+
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
332+
7,
333+
'creationTime uses Date.now() - navigationStart when available');
334+
335+
window.performance.now = () => 4;
336+
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
337+
4,
338+
'creationTime prioritizes performance.now when available');
339+
340+
window.Date = origDate;
341+
window.performance = origPerformance;
342+
});

test/unit/tech/html5.test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,3 +667,83 @@ test('When Android Chrome reports Infinity duration with currentTime 0, return N
667667
browser.IS_CHROME = oldIsChrome;
668668
tech.el_ = oldEl;
669669
});
670+
671+
QUnit.test('supports getting available media playback quality metrics', function(assert) {
672+
const origPerformance = window.performance;
673+
const origDate = window.Date;
674+
const oldEl = tech.el_;
675+
const videoPlaybackQuality = {
676+
creationTime: 1,
677+
corruptedVideoFrames: 2,
678+
droppedVideoFrames: 3,
679+
totalVideoFrames: 5
680+
};
681+
682+
tech.el_ = {
683+
getVideoPlaybackQuality: () => videoPlaybackQuality
684+
};
685+
assert.deepEqual(tech.getVideoPlaybackQuality(),
686+
videoPlaybackQuality,
687+
'uses native implementation when supported');
688+
689+
tech.el_ = {
690+
webkitDroppedFrameCount: 1,
691+
webkitDecodedFrameCount: 2
692+
};
693+
window.performance = {
694+
now: () => 4
695+
};
696+
assert.deepEqual(tech.getVideoPlaybackQuality(),
697+
{ droppedVideoFrames: 1, totalVideoFrames: 2, creationTime: 4 },
698+
'uses webkit prefixed metrics and performance.now when supported');
699+
700+
tech.el_ = {
701+
webkitDroppedFrameCount: 1,
702+
webkitDecodedFrameCount: 2
703+
};
704+
window.Date = {
705+
now: () => 10
706+
};
707+
window.performance = {
708+
timing: {
709+
navigationStart: 3
710+
}
711+
};
712+
assert.deepEqual(tech.getVideoPlaybackQuality(),
713+
{ droppedVideoFrames: 1, totalVideoFrames: 2, creationTime: 7 },
714+
'uses webkit prefixed metrics and Date.now() - navigationStart when ' +
715+
'supported');
716+
717+
tech.el_ = {};
718+
window.performance = void 0;
719+
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'empty object when not supported');
720+
721+
window.performance = {
722+
now: () => 5
723+
};
724+
assert.deepEqual(tech.getVideoPlaybackQuality(),
725+
{ creationTime: 5 },
726+
'only creation time when it\'s the only piece available');
727+
728+
window.performance = {
729+
timing: {
730+
navigationStart: 3
731+
}
732+
};
733+
assert.deepEqual(tech.getVideoPlaybackQuality(),
734+
{ creationTime: 7 },
735+
'only creation time when it\'s the only piece available');
736+
737+
tech.el_ = {
738+
getVideoPlaybackQuality: () => videoPlaybackQuality,
739+
webkitDroppedFrameCount: 1,
740+
webkitDecodedFrameCount: 2
741+
};
742+
assert.deepEqual(tech.getVideoPlaybackQuality(),
743+
videoPlaybackQuality,
744+
'prefers native implementation when supported');
745+
746+
tech.el_ = oldEl;
747+
window.performance = origPerformance;
748+
window.Date = origDate;
749+
});

test/unit/tech/tech.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,3 +631,9 @@ QUnit.test('setSource after previous setSource should dispose source handler onc
631631

632632
});
633633

634+
QUnit.test('returns an empty object for getVideoPlaybackQuality', function(assert) {
635+
const tech = new Tech();
636+
637+
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'returns an empty object');
638+
});
639+

0 commit comments

Comments
 (0)