Skip to content

Commit 99a6f25

Browse files
aveladtykus160
andauthored
feat: Create a new simple API for Video (#8454)
Co-authored-by: Wojciech Tyczyński <[email protected]>
1 parent 34b5b4a commit 99a6f25

File tree

6 files changed

+429
-140
lines changed

6 files changed

+429
-140
lines changed

externs/shaka/player.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,53 @@ shaka.extern.AudioTrack;
540540
shaka.extern.TextTrack;
541541

542542

543+
/**
544+
* @typedef {{
545+
* active: boolean,
546+
* bandwidth: number,
547+
* width: ?number,
548+
* height: ?number,
549+
* frameRate: ?number,
550+
* pixelAspectRatio: ?string,
551+
* hdr: ?string,
552+
* colorGamut: ?string,
553+
* videoLayout: ?string,
554+
* mimeType: ?string,
555+
* codecs: ?string
556+
* }}
557+
*
558+
* @description
559+
* An object describing a video track. This object should be treated as
560+
* read-only as changing any values does not have any effect.
561+
*
562+
* @property {boolean} active
563+
* If true, this is the track being streamed (another track may be
564+
* visible/audible in the buffer).
565+
* @property {number} bandwidth
566+
* The bandwidth required to play the track, in bits/sec.
567+
* @property {?number} width
568+
* The video width provided in the manifest, if present.
569+
* @property {?number} height
570+
* The video height provided in the manifest, if present.
571+
* @property {?number} frameRate
572+
* The video framerate provided in the manifest, if present.
573+
* @property {?string} pixelAspectRatio
574+
* The video pixel aspect ratio provided in the manifest, if present.
575+
* @property {?string} hdr
576+
* The video HDR provided in the manifest, if present.
577+
* @property {?string} colorGamut
578+
* The video color gamut provided in the manifest, if present.
579+
* @property {?string} videoLayout
580+
* The video layout provided in the manifest, if present.
581+
* @property {?string} mimeType
582+
* The video MIME type of the content provided in the manifest.
583+
* @property {?string} codecs
584+
* The video codecs string provided in the manifest, if present.
585+
* @exportDoc
586+
*/
587+
shaka.extern.VideoTrack;
588+
589+
543590
/**
544591
* @typedef {{
545592
* id: number,

lib/cast/cast_utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ shaka.cast.CastUtils.LargePlayerGetterMethods = new Map()
345345
.set('getConfigurationForLowLatency', 4)
346346
.set('getStats', 5)
347347
.set('getAudioTracks', 2)
348+
.set('getVideoTracks', 2)
348349
.set('getTextTracks', 2)
349350
.set('getVariantTracks', 2);
350351

@@ -408,6 +409,7 @@ shaka.cast.CastUtils.PlayerVoidMethods = [
408409
'selectTextTrack',
409410
'selectVariantTrack',
410411
'selectVariantsByLabel',
412+
'selectVideoTrack',
411413
'setTextTrackVisibility',
412414
'trickPlay',
413415
'updateStartTime',

lib/player.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5758,6 +5758,101 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
57585758
return Array.from(audioTracksMap.values());
57595759
}
57605760

5761+
5762+
/**
5763+
* Select a video track compatible with the current audio track.
5764+
* If the player has not loaded any content, this will be a no-op.
5765+
*
5766+
* @param {shaka.extern.VideoTrack} videoTrack
5767+
* @param {boolean=} clearBuffer
5768+
* @param {number=} safeMargin Optional amount of buffer (in seconds) to
5769+
* retain when clearing the buffer. Useful for switching quickly
5770+
* without causing a buffering event. Defaults to 0 if not provided. Can
5771+
* cause hiccups on some browsers if chosen too small, e.g. The amount of
5772+
* two segments is a fair minimum to consider as safeMargin value.
5773+
* @export
5774+
*/
5775+
selectVideoTrack(videoTrack, clearBuffer = false, safeMargin = 0) {
5776+
const variants = this.getVariantTracks();
5777+
if (!variants.length) {
5778+
return;
5779+
}
5780+
const active = variants.find((t) => t.active);
5781+
if (!active) {
5782+
return;
5783+
}
5784+
const validVariant = variants.find((t) => {
5785+
return t.audioId === active.audioId &&
5786+
(t.videoBandwidth || t.bandwidth) == videoTrack.bandwidth &&
5787+
t.width == videoTrack.width &&
5788+
t.height == videoTrack.height &&
5789+
t.frameRate == videoTrack.frameRate &&
5790+
t.pixelAspectRatio == videoTrack.pixelAspectRatio &&
5791+
t.hdr == videoTrack.hdr &&
5792+
t.colorGamut == videoTrack.colorGamut &&
5793+
t.videoLayout == videoTrack.videoLayout &&
5794+
t.videoMimeType == videoTrack.mimeType &&
5795+
t.videoCodec == videoTrack.codecs;
5796+
});
5797+
if (validVariant && !validVariant.active) {
5798+
this.selectVariantTrack(validVariant, clearBuffer, safeMargin);
5799+
}
5800+
}
5801+
5802+
5803+
/**
5804+
* Return a list of video tracks compatible with the current audio track.
5805+
*
5806+
* @return {!Array<shaka.extern.VideoTrack>}
5807+
* @export
5808+
*/
5809+
getVideoTracks() {
5810+
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
5811+
return [];
5812+
}
5813+
const variants = this.getVariantTracks();
5814+
if (!variants.length) {
5815+
return [];
5816+
}
5817+
const active = variants.find((t) => t.active);
5818+
if (!active) {
5819+
return [];
5820+
}
5821+
const filteredTracks = variants.filter((t) => {
5822+
return t.originalAudioId === active.originalAudioId &&
5823+
t.audioId === active.audioId &&
5824+
t.audioGroupId === active.audioGroupId &&
5825+
t.videoCodec;
5826+
});
5827+
if (!filteredTracks.length) {
5828+
return [];
5829+
}
5830+
5831+
/** @type {!Map<number, shaka.extern.VideoTrack>} */
5832+
const videoTracksMap = new Map();
5833+
for (const track of filteredTracks) {
5834+
if (track.videoId == null) {
5835+
continue;
5836+
}
5837+
/** @type {shaka.extern.VideoTrack} */
5838+
const videoTrack = {
5839+
active: track.active,
5840+
bandwidth: track.videoBandwidth || track.bandwidth,
5841+
width: track.width,
5842+
height: track.height,
5843+
frameRate: track.frameRate,
5844+
pixelAspectRatio: track.pixelAspectRatio,
5845+
hdr: track.hdr,
5846+
colorGamut: track.colorGamut,
5847+
videoLayout: track.videoLayout,
5848+
mimeType: track.videoMimeType,
5849+
codecs: track.videoCodec,
5850+
};
5851+
videoTracksMap.set(track.videoId, videoTrack);
5852+
}
5853+
return Array.from(videoTracksMap.values());
5854+
}
5855+
57615856
/**
57625857
* Return a list of audio language-role combinations available. If the
57635858
* player has not loaded any content, this will return an empty list.

test/player_unit.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,8 @@ describe('Player', () => {
15231523
let variantTracks;
15241524
/** @type {!Array<shaka.extern.AudioTrack>} */
15251525
let audioTracks;
1526+
/** @type {!Array<shaka.extern.VideoTrack>} */
1527+
let videoTracks;
15261528
/** @type {!Array<shaka.extern.Track>} */
15271529
let textTracks;
15281530
/** @type {!Array<shaka.extern.Track>} */
@@ -2166,6 +2168,35 @@ describe('Player', () => {
21662168
},
21672169
];
21682170

2171+
videoTracks = [
2172+
{
2173+
active: true,
2174+
bandwidth: 1000,
2175+
width: 100,
2176+
height: 200,
2177+
frameRate: 1000000 / 42000,
2178+
pixelAspectRatio: '59:54',
2179+
hdr: null,
2180+
colorGamut: null,
2181+
videoLayout: null,
2182+
mimeType: 'video/mp4',
2183+
codecs: 'avc1.4d401f',
2184+
},
2185+
{
2186+
active: false,
2187+
bandwidth: 2000,
2188+
width: 200,
2189+
height: 400,
2190+
frameRate: 24,
2191+
pixelAspectRatio: '59:54',
2192+
hdr: null,
2193+
colorGamut: null,
2194+
videoLayout: null,
2195+
mimeType: 'video/mp4',
2196+
codecs: 'avc1.4d401f',
2197+
},
2198+
];
2199+
21692200
textTracks = [
21702201
{
21712202
id: 50,
@@ -2252,6 +2283,7 @@ describe('Player', () => {
22522283
it('returns the correct tracks', () => {
22532284
expect(player.getVariantTracks()).toEqual(variantTracks);
22542285
expect(player.getAudioTracks()).toEqual(audioTracks);
2286+
expect(player.getVideoTracks()).toEqual(videoTracks);
22552287
expect(player.getTextTracks()).toEqual(textTracks);
22562288
expect(player.getImageTracks()).toEqual(imageTracks);
22572289
});
@@ -2263,6 +2295,7 @@ describe('Player', () => {
22632295
// The player does not yet have a manifest.
22642296
expect(player.getVariantTracks()).toEqual([]);
22652297
expect(player.getAudioTracks()).toEqual([]);
2298+
expect(player.getVideoTracks()).toEqual([]);
22662299
expect(player.getTextTracks()).toEqual([]);
22672300
expect(player.getImageTracks()).toEqual([]);
22682301

@@ -2274,6 +2307,7 @@ describe('Player', () => {
22742307

22752308
expect(player.getVariantTracks()).toEqual(variantTracks);
22762309
expect(player.getAudioTracks()).toEqual(audioTracks);
2310+
expect(player.getVideoTracks()).toEqual(videoTracks);
22772311
expect(player.getTextTracks()).toEqual(textTracks);
22782312
expect(player.getImageTracks()).toEqual(imageTracks);
22792313
});
@@ -2695,6 +2729,23 @@ describe('Player', () => {
26952729
expect(variantChanged).not.toHaveBeenCalled();
26962730
});
26972731

2732+
it('in selectVideoTrack', async () => {
2733+
// Any variant track we're not already streaming.
2734+
const newTrack = player.getVideoTracks().filter((t) => !t.active)[0];
2735+
2736+
// Call selectVariantTrack with a new track. Expect an event to fire.
2737+
player.selectVideoTrack(newTrack);
2738+
await shaka.test.Util.shortDelay();
2739+
expect(variantChanged).toHaveBeenCalled();
2740+
variantChanged.calls.reset();
2741+
2742+
// Call again with the same track, and expect no event to fire, since
2743+
// nothing changed this time.
2744+
player.selectVideoTrack(newTrack);
2745+
await shaka.test.Util.shortDelay();
2746+
expect(variantChanged).not.toHaveBeenCalled();
2747+
});
2748+
26982749
it('in selectTextLanguage', async () => {
26992750
// The current text language.
27002751
const currentLanguage = player.getTextTracks()
@@ -2737,7 +2788,7 @@ describe('Player', () => {
27372788
expect(variantChanged).not.toHaveBeenCalled();
27382789
});
27392790

2740-
it('in selectAudioLanguage', async () => {
2791+
it('in selectAudioTrack', async () => {
27412792
// New audio track.
27422793
const newAudioTrack = player.getAudioTracks().find((t) => !t.active);
27432794
goog.asserts.assert(newAudioTrack, 'audio track must be non-null');

test/ui/ui_unit.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -589,19 +589,19 @@ describe('UI', () => {
589589
await player.load(
590590
/* uri= */ 'fake', /* startTime= */ 0, fakeMimeType);
591591

592-
const selectVariantTrack = spyOn(player, 'selectVariantTrack');
592+
const selectVideoTrack = spyOn(player, 'selectVideoTrack');
593593

594594
// There should be at least one explicit quality button.
595595
const qualityButton =
596596
videoContainer.querySelectorAll('button.explicit-resolution')[0];
597597
expect(qualityButton).toBeDefined();
598598

599599
// Clicking this should select a track and clear the buffer.
600-
expect(selectVariantTrack).not.toHaveBeenCalled();
600+
expect(selectVideoTrack).not.toHaveBeenCalled();
601601
qualityButton.click();
602602

603603
// The second argument is "clearBuffer", and should be true.
604-
expect(selectVariantTrack).toHaveBeenCalledWith(
604+
expect(selectVideoTrack).toHaveBeenCalledWith(
605605
jasmine.any(Object), true);
606606
});
607607

@@ -745,7 +745,7 @@ describe('UI', () => {
745745
if (elem instanceof shaka.ui.OverflowMenu) {
746746
for (const child of elem.children_) {
747747
if (child instanceof shaka.ui.ResolutionSelection) {
748-
child.updateResolutionSelection_();
748+
child.updateSelection_();
749749
found = true;
750750
}
751751
}

0 commit comments

Comments
 (0)