From 9be329243bf8bb269698752d5719966c896d2402 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:25:25 -0700 Subject: [PATCH 1/6] fix: separate gap jumping seeks from user seeks see: #8752 --- lib/media/gap_jumping_controller.js | 27 ++++++++++++++++++++++++++- lib/media/playhead.js | 17 +++++++++++++++++ lib/media/playhead_observer.js | 6 ++++-- lib/player.js | 2 +- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/media/gap_jumping_controller.js b/lib/media/gap_jumping_controller.js index 78599d9263..4f8c8f3aa6 100644 --- a/lib/media/gap_jumping_controller.js +++ b/lib/media/gap_jumping_controller.js @@ -62,6 +62,9 @@ shaka.media.GapJumpingController = class { /** @private {number} */ this.startTime_ = 0; + /** @private {boolean} */ + this.isJumpingGap_ = false; + /** @private {number} */ this.gapsJumped_ = 0; @@ -178,6 +181,15 @@ shaka.media.GapJumpingController = class { return this.stallsDetected_; } + /** + * Returns whether the player is currently jumping a gap. + * + * @return {boolean} + */ + getIsJumpingGap() { + return this.isJumpingGap_; + } + /** * Called on a recurring timer to check for gaps in the media. This is also @@ -273,7 +285,7 @@ shaka.media.GapJumpingController = class { buffered.end(gapIndex - 1), 'and ending at', jumpTo); } - this.video_.currentTime = jumpTo; + this.seek_(jumpTo); // This accounts for the possibility that we jump a gap at the start // position but we jump _into_ another gap. By setting the start // position to the new jumpTo we ensure that the check above will @@ -286,6 +298,19 @@ shaka.media.GapJumpingController = class { new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped)); } + /** + * Seek to a specific time in the video. + * @param {number} time The time to seek to, in seconds. + * @private + */ + seek_(time) { + this.isJumpingGap_ = true; + this.eventManager_.listenOnce(this.video_, 'seeked', () => { + this.isJumpingGap_ = false; + }); + this.video_.currentTime = time; + } + /** * Create and configure a stall detector using the player's streaming * configuration settings. If the player is configured to have no stall diff --git a/lib/media/playhead.js b/lib/media/playhead.js index 967ae7bf93..cb224d1f43 100644 --- a/lib/media/playhead.js +++ b/lib/media/playhead.js @@ -56,6 +56,13 @@ shaka.media.Playhead = class { */ getStallsDetected() {} + /** + * Get whether the playhead is currently jumping a gap. + * + * @return {boolean} + */ + getIsJumpingGap() {} + /** * Get the number of playback gaps jumped by the GapJumpingController. * @@ -209,6 +216,11 @@ shaka.media.SrcEqualsPlayhead = class { return 0; } + /** @override */ + getIsJumpingGap() { + return false; + } + /** @override */ notifyOfBufferingChange() {} @@ -401,6 +413,11 @@ shaka.media.MediaSourcePlayhead = class { return this.gapController_.getGapsJumped(); } + /** @override */ + getIsJumpingGap() { + return this.gapController_.getIsJumpingGap(); + } + /** * Gets the playhead's initial position in seconds. * diff --git a/lib/media/playhead_observer.js b/lib/media/playhead_observer.js index 341e243021..3529a1212b 100644 --- a/lib/media/playhead_observer.js +++ b/lib/media/playhead_observer.js @@ -113,9 +113,11 @@ shaka.media.PlayheadObserverManager = class { /** * Notify all the observers that we just seeked. + * + * @param {boolean} seeking */ - notifyOfSeek() { - this.pollAllObservers_(/* seeking= */ true); + notifyOfSeek(seeking) { + this.pollAllObservers_(seeking); } /** diff --git a/lib/player.js b/lib/player.js index 2eff7fe3af..d3d3c29c7a 100644 --- a/lib/player.js +++ b/lib/player.js @@ -8193,7 +8193,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { */ onSeek_() { if (this.playheadObservers_) { - this.playheadObservers_.notifyOfSeek(); + this.playheadObservers_.notifyOfSeek(!this.playhead_.getIsJumpingGap()); } if (this.streamingEngine_) { this.streamingEngine_.seeked(); From 955cc4972150157511528e2a9b1ef978b76dd9b0 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:03:18 -0700 Subject: [PATCH 2/6] chore: lint fix --- lib/media/playhead.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/media/playhead.js b/lib/media/playhead.js index cb224d1f43..8820cf703a 100644 --- a/lib/media/playhead.js +++ b/lib/media/playhead.js @@ -58,7 +58,7 @@ shaka.media.Playhead = class { /** * Get whether the playhead is currently jumping a gap. - * + * * @return {boolean} */ getIsJumpingGap() {} From 4967e5278a33dd0709fc81f368ba21acbd1dfa5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Thu, 31 Jul 2025 14:00:29 +0200 Subject: [PATCH 3/6] feat: Expose video roles in Track and VideoTrack (#8944) Related to https://github.com/shaka-project/shaka-player/issues/8943 --- externs/shaka/player.js | 10 +++++++++- lib/player.js | 6 +++++- lib/util/stream_utils.js | 3 +++ test/offline/storage_integration.js | 2 ++ test/player_unit.js | 12 ++++++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 1fa03f3e9b..208fd19027 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -269,6 +269,7 @@ shaka.extern.BufferedInfo; * primary: boolean, * roles: !Array, * audioRoles: Array, + * videoRoles: Array, * accessibilityPurpose: ?shaka.media.ManifestParser.AccessibilityPurpose, * forced: boolean, * videoId: ?number, @@ -353,6 +354,10 @@ shaka.extern.BufferedInfo; * The roles of the audio in the track, e.g. 'main' or * 'commentary'. Will be null for text tracks or variant tracks * without audio. + * @property {Array} videoRoles + * The roles of the video in the track, e.g. 'main' or + * 'sign'. Will be null for text tracks or variant tracks + * without video. * @property {?shaka.media.ManifestParser.AccessibilityPurpose * } accessibilityPurpose * The DASH accessibility descriptor, if one was provided for this track. @@ -552,7 +557,8 @@ shaka.extern.TextTrack; * colorGamut: ?string, * videoLayout: ?string, * mimeType: ?string, - * codecs: ?string + * codecs: ?string, + * roles: !Array, * }} * * @description @@ -582,6 +588,8 @@ shaka.extern.TextTrack; * The video MIME type of the content provided in the manifest. * @property {?string} codecs * The video codecs string provided in the manifest, if present. + * @property {!Array} roles + * The roles of the track, e.g. 'main', 'sign'. * @exportDoc */ shaka.extern.VideoTrack; diff --git a/lib/player.js b/lib/player.js index 54245edc99..a34ff6fcef 100644 --- a/lib/player.js +++ b/lib/player.js @@ -43,6 +43,7 @@ goog.require('shaka.text.TextEngine'); goog.require('shaka.text.Utils'); goog.require('shaka.text.UITextDisplayer'); goog.require('shaka.text.WebVttGenerator'); +goog.require('shaka.util.ArrayUtils'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.CmcdManager'); goog.require('shaka.util.CmsdManager'); @@ -5967,6 +5968,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { * @export */ selectVideoTrack(videoTrack, clearBuffer = false, safeMargin = 0) { + const ArrayUtils = shaka.util.ArrayUtils; const variants = this.getVariantTracks(); if (!variants.length) { return; @@ -5986,7 +5988,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { t.colorGamut == videoTrack.colorGamut && t.videoLayout == videoTrack.videoLayout && t.videoMimeType == videoTrack.mimeType && - t.videoCodec == videoTrack.codecs; + t.videoCodec == videoTrack.codecs && + ArrayUtils.equal(t.videoRoles, videoTrack.roles); }); if (validVariant && !validVariant.active) { this.selectVariantTrack(validVariant, clearBuffer, safeMargin); @@ -6046,6 +6049,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { videoLayout: track.videoLayout, mimeType: track.videoMimeType, codecs: track.videoCodec, + roles: track.videoRoles || [], }; videoTracksMap.set(id, videoTrack); } diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 4bd3740f80..23f0a290fc 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -1374,6 +1374,7 @@ shaka.util.StreamUtils = class { primary: variant.primary, roles: Array.from(roles), audioRoles: null, + videoRoles: null, forced: false, videoId: null, audioId: null, @@ -1403,6 +1404,7 @@ shaka.util.StreamUtils = class { track.hdr = video.hdr || null; track.colorGamut = video.colorGamut || null; track.videoLayout = video.videoLayout || null; + track.videoRoles = video.roles; const dependencyStream = video.dependencyStream; if (dependencyStream) { @@ -1675,6 +1677,7 @@ shaka.util.StreamUtils = class { roles: [], forced: false, audioRoles: null, + videoRoles: null, videoId: null, audioId: null, audioGroupId: null, diff --git a/test/offline/storage_integration.js b/test/offline/storage_integration.js index f7eeec07fd..4bda04a5fa 100644 --- a/test/offline/storage_integration.js +++ b/test/offline/storage_integration.js @@ -1393,6 +1393,7 @@ filterDescribe('Storage', storageSupport, () => { primary: false, roles: [], audioRoles: [], + videoRoles: [], forced: false, videoId: videoId, audioId: audioId, @@ -1442,6 +1443,7 @@ filterDescribe('Storage', storageSupport, () => { primary: false, roles: [], audioRoles: null, + videoRoles: null, forced: false, videoId: null, audioId: null, diff --git a/test/player_unit.js b/test/player_unit.js index 77ab7fb493..71c85c0aa2 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -1708,6 +1708,7 @@ describe('Player', () => { primary: false, roles: ['main'], audioRoles: ['main'], + videoRoles: ['main'], forced: false, videoId: 1, audioId: 3, @@ -1749,6 +1750,7 @@ describe('Player', () => { primary: false, roles: ['main'], audioRoles: ['main'], + videoRoles: [], forced: false, videoId: 2, audioId: 3, @@ -1790,6 +1792,7 @@ describe('Player', () => { primary: false, roles: ['main'], audioRoles: ['main'], + videoRoles: ['main'], forced: false, videoId: 1, audioId: 4, @@ -1831,6 +1834,7 @@ describe('Player', () => { primary: false, roles: ['main'], audioRoles: ['main'], + videoRoles: [], forced: false, videoId: 2, audioId: 4, @@ -1872,6 +1876,7 @@ describe('Player', () => { primary: false, roles: ['commentary', 'main'], audioRoles: ['commentary'], + videoRoles: ['main'], forced: false, videoId: 1, audioId: 5, @@ -1913,6 +1918,7 @@ describe('Player', () => { primary: false, roles: ['commentary'], audioRoles: ['commentary'], + videoRoles: [], forced: false, videoId: 2, audioId: 5, @@ -1954,6 +1960,7 @@ describe('Player', () => { primary: false, roles: ['main'], audioRoles: [], + videoRoles: ['main'], forced: false, videoId: 1, audioId: 6, @@ -1995,6 +2002,7 @@ describe('Player', () => { primary: false, roles: [], audioRoles: [], + videoRoles: [], forced: false, videoId: 2, audioId: 6, @@ -2036,6 +2044,7 @@ describe('Player', () => { primary: false, roles: ['main'], audioRoles: [], + videoRoles: ['main'], forced: false, videoId: 1, audioId: 7, @@ -2077,6 +2086,7 @@ describe('Player', () => { primary: false, roles: [], audioRoles: [], + videoRoles: [], forced: false, videoId: 2, audioId: 7, @@ -2181,6 +2191,7 @@ describe('Player', () => { videoLayout: null, mimeType: 'video/mp4', codecs: 'avc1.4d401f', + roles: ['main'], }, { active: false, @@ -2194,6 +2205,7 @@ describe('Player', () => { videoLayout: null, mimeType: 'video/mp4', codecs: 'avc1.4d401f', + roles: [], }, ]; From 570fec9d31ddb605c25831bf49fe31428b3b7fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Thu, 31 Jul 2025 14:38:09 +0200 Subject: [PATCH 4/6] feat: Support videoRole in AdaptationSetCriteria (#8945) Add preferredVideoRole config `preferredVariantRole` has been renamed to `preferredAudioRole` Related to https://github.com/shaka-project/shaka-player/issues/8943 --- demo/config.js | 3 +- docs/tutorials/upgrade.md | 1 + externs/shaka/player.js | 11 +++- lib/media/adaptation_set_criteria.js | 5 +- lib/media/preference_based_criteria.js | 51 +++++++++++++--- lib/media/preload_manager.js | 3 +- lib/player.js | 21 ++++++- lib/util/player_configuration.js | 3 +- test/media/adaptation_set_criteria_unit.js | 71 ++++++++++++++++++++++ test/player_unit.js | 13 ++-- 10 files changed, 158 insertions(+), 24 deletions(-) diff --git a/demo/config.js b/demo/config.js index dc0cbde3f7..0cc0076b91 100644 --- a/demo/config.js +++ b/demo/config.js @@ -751,7 +751,8 @@ shakaDemo.Config = class { .addTextInput_('Preferred Audio Language', 'preferredAudioLanguage') .addTextInput_('Preferred Audio Label', 'preferredAudioLabel') .addTextInput_('Preferred Video Label', 'preferredVideoLabel') - .addTextInput_('Preferred Variant Role', 'preferredVariantRole') + .addTextInput_('Preferred Audio Role', 'preferredAudioRole') + .addTextInput_('Preferred Video Role', 'preferredVideoRole') .addTextInput_('Preferred Text Language', 'preferredTextLanguage') .addTextInput_('Preferred Text Role', 'preferredTextRole') .addSelectInput_('Auto-Show Text', diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md index 9be049028d..e6622deda0 100644 --- a/docs/tutorials/upgrade.md +++ b/docs/tutorials/upgrade.md @@ -121,6 +121,7 @@ application: - `streaming.forceHTTPS` has been renamed to `networking.forceHTTPS` (deprecated in v4.15.0) - `streaming.minBytesForProgressEvents` has been renamed to `networking.minBytesForProgressEvents` (deprecated in v4.15.0) - `manifest.dash.enableAudioGroups` has been renamed to `manifest.enableAudioGroups` + - `preferredVariantRole` has been renamed to `preferredAudioRole` (deprecated in v4.16.0) - Plugin changes: - `TextDisplayer` plugins must implement the `configure()` method. diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 208fd19027..3f30596bf4 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -2755,7 +2755,8 @@ shaka.extern.TextDisplayerConfiguration; * preferredAudioLanguage: string, * preferredAudioLabel: string, * preferredTextLanguage: string, - * preferredVariantRole: string, + * preferredAudioRole: string, + * preferredVideoRole: string, * preferredTextRole: string, * preferredVideoCodecs: !Array, * preferredAudioCodecs: !Array, @@ -2834,8 +2835,12 @@ shaka.extern.TextDisplayerConfiguration; * Changing this during playback will not affect the current playback. *
* Defaults to ''. - * @property {string} preferredVariantRole - * The preferred role to use for variants. + * @property {string} preferredAudioRole + * The preferred audio role to use for variants. + *
+ * Defaults to ''. + * @property {string} preferredVideoRole + * The preferred video role to use for variants. *
* Defaults to ''. * @property {string} preferredTextRole diff --git a/lib/media/adaptation_set_criteria.js b/lib/media/adaptation_set_criteria.js index 13475a302e..732a7d3f03 100644 --- a/lib/media/adaptation_set_criteria.js +++ b/lib/media/adaptation_set_criteria.js @@ -58,6 +58,7 @@ shaka.media.AdaptationSetCriteria.Factory; * @typedef {{ * language: string, * role: string, + * videoRole: string, * channelCount: number, * hdrLevel: string, * spatialAudio: boolean, @@ -75,7 +76,9 @@ shaka.media.AdaptationSetCriteria.Factory; * @property {string} language * The language used to filter variants. * @property {string} role - * The adaptation role used to filter variants. + * The adaptation audio role used to filter variants. + * @property {string} videoRole + * The adaptation video role used to filter variants. * @property {string} channelCount * The audio channel count used to filter variants. * @property {string} hdrLevel diff --git a/lib/media/preference_based_criteria.js b/lib/media/preference_based_criteria.js index c5c8c89725..f06fc0dd08 100644 --- a/lib/media/preference_based_criteria.js +++ b/lib/media/preference_based_criteria.js @@ -64,13 +64,26 @@ shaka.media.PreferenceBasedCriteria = class { } } - // Now refine the choice based on role preference. Even the empty string - // works here, and will match variants without any roles. - const byRole = Class.filterVariantsByRole_(current, this.config_.role); - if (byRole.length) { - current = byRole; + // Now refine the choice based on audio role preference. Even the empty + // string works here, and will match variants without any roles. + const byAudioRole = + Class.filterVariantsByAudioRole_(current, this.config_.role); + if (byAudioRole.length) { + current = byAudioRole; } else { - shaka.log.warning('No exact match for variant role could be found.'); + shaka.log.warning( + 'No exact match for variant audio role could be found.'); + } + + // Now refine the choice based on video role preference. Even the empty + // string works here, and will match variants without any roles. + const byVideoRole = + Class.filterVariantsByVideoRole_(current, this.config_.videoRole); + if (byVideoRole.length) { + current = byVideoRole; + } else { + shaka.log.warning( + 'No exact match for variant video role could be found.'); } if (this.config_.videoLayout) { @@ -210,14 +223,14 @@ shaka.media.PreferenceBasedCriteria = class { } /** - * Filter Variants by role. + * Filter Variants by audio role. * * @param {!Array} variants * @param {string} preferredRole * @return {!Array} * @private */ - static filterVariantsByRole_(variants, preferredRole) { + static filterVariantsByAudioRole_(variants, preferredRole) { return variants.filter((variant) => { if (!variant.audio) { return false; @@ -231,6 +244,28 @@ shaka.media.PreferenceBasedCriteria = class { }); } + /** + * Filter Variants by video role. + * + * @param {!Array} variants + * @param {string} preferredRole + * @return {!Array} + * @private + */ + static filterVariantsByVideoRole_(variants, preferredRole) { + return variants.filter((variant) => { + if (!variant.video) { + return false; + } + + if (preferredRole) { + return variant.video.roles.includes(preferredRole); + } else { + return variant.video.roles.length == 0; + } + }); + } + /** * Filter Variants by audio label. * diff --git a/lib/media/preload_manager.js b/lib/media/preload_manager.js index d0da1f6a4d..02605e2e40 100644 --- a/lib/media/preload_manager.js +++ b/lib/media/preload_manager.js @@ -651,7 +651,8 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget { this.config_.adaptationSetCriteriaFactory(); this.currentAdaptationSetCriteria_.configure({ language: this.config_.preferredAudioLanguage, - role: this.config_.preferredVariantRole, + role: this.config_.preferredAudioRole, + videoRole: this.config_.preferredVideoRole, channelCount: this.config_.preferredAudioChannelCount, hdrLevel: this.config_.preferredVideoHdrLevel, spatialAudio: this.config_.preferSpatialAudio, diff --git a/lib/player.js b/lib/player.js index a34ff6fcef..68fe5d1e75 100644 --- a/lib/player.js +++ b/lib/player.js @@ -884,7 +884,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.config_.adaptationSetCriteriaFactory(); this.currentAdaptationSetCriteria_.configure({ language: this.config_.preferredAudioLanguage, - role: this.config_.preferredVariantRole, + role: this.config_.preferredAudioRole, + videoRole: this.config_.preferredVideoRole, channelCount: 0, hdrLevel: this.config_.preferredVideoHdrLevel, spatialAudio: this.config_.preferSpatialAudio, @@ -3449,8 +3450,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { return; } - const preferredVariantRole = this.config_.preferredVariantRole; - this.selectAudioLanguage(preferredAudioLanguage, preferredVariantRole); + const preferredAudioRole = this.config_.preferredAudioRole; + this.selectAudioLanguage(preferredAudioLanguage, preferredAudioRole); } /** @@ -4242,6 +4243,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget { goog.asserts.assert(typeof(config) == 'object', 'Should be an object!'); + // Deprecate 'preferredVariantRole' configuration. + if ('preferredVariantRole' in config) { + shaka.Deprecate.deprecateFeature(5, + 'preferredVariantRole configuration', + 'Please Use preferredAudioRole instead.'); + config['preferredAudioRole'] = config['preferredVariantRole']; + delete config['preferredVariantRole']; + } + // Deprecate 'streaming.forceTransmuxTS' configuration. if (config['streaming'] && 'forceTransmuxTS' in config['streaming']) { shaka.Deprecate.deprecateFeature(5, @@ -5778,6 +5788,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { language: variant.language, role: (variant.audio && variant.audio.roles && variant.audio.roles[0]) || '', + videoRole: (variant.video && variant.video.roles && + variant.video.roles[0]) || '', channelCount: variant.audio && variant.audio.channelsCount ? variant.audio.channelsCount : 0, hdrLevel: variant.video && variant.video.hdr ? variant.video.hdr : '', @@ -6153,6 +6165,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.currentAdaptationSetCriteria_.configure({ language, role: role || '', + videoRole: (active.video && active.video.roles && + active.video.roles[0]) || '', channelCount: channelsCount || 0, hdrLevel: '', spatialAudio: spatialAudio || false, @@ -6328,6 +6342,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.currentAdaptationSetCriteria_.configure({ language: firstVariantWithLabel.language, role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index d870346133..ca0acf4c8f 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -434,7 +434,8 @@ shaka.util.PlayerConfiguration = class { preferredAudioLanguage: '', preferredAudioLabel: '', preferredTextLanguage: '', - preferredVariantRole: '', + preferredAudioRole: '', + preferredVideoRole: '', preferredTextRole: '', preferredAudioChannelCount: 2, preferredVideoHdrLevel: 'AUTO', diff --git a/test/media/adaptation_set_criteria_unit.js b/test/media/adaptation_set_criteria_unit.js index b3faeec10b..3ef3743e74 100644 --- a/test/media/adaptation_set_criteria_unit.js +++ b/test/media/adaptation_set_criteria_unit.js @@ -23,6 +23,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -60,6 +61,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -122,6 +124,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -177,6 +180,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -221,6 +225,46 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: 'main', + videoRole: '', + channelCount: 0, + hdrLevel: '', + spatialAudio: false, + videoLayout: '', + audioLabel: '', + videoLabel: '', + codecSwitchingStrategy: shaka.config.CodecSwitchingStrategy.RELOAD, + audioCodec: '', + activeAudioCodec: '', + activeAudioChannelCount: 0, + preferredAudioCodecs: [], + preferredAudioChannelCount: 0, + }); + const set = builder.create(manifest.variants); + + checkSet(set, [ + manifest.variants[0], + ]); + }); + + it('chooses variants in preferred video role', () => { + const manifest = shaka.test.ManifestGenerator.generate((manifest) => { + manifest.addVariant(1, (variant) => { + variant.addVideo(10, (stream) => { + stream.roles = ['sign']; + }); + }); + manifest.addVariant(2, (variant) => { + variant.addVideo(20, (stream) => { + stream.roles = ['main']; + }); + }); + }); + + const builder = new shaka.media.PreferenceBasedCriteria(); + builder.configure({ + language: 'en', + role: 'main', + videoRole: 'sign', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -286,6 +330,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -360,6 +405,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'zh', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -412,6 +458,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'zh', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -484,6 +531,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'zh', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -557,6 +605,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'zh', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -601,6 +650,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: 'PQ', spatialAudio: false, @@ -645,6 +695,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -689,6 +740,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -732,6 +784,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 2, hdrLevel: '', spatialAudio: false, @@ -782,6 +835,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 6, hdrLevel: '', spatialAudio: false, @@ -827,6 +881,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 2, hdrLevel: '', spatialAudio: false, @@ -872,6 +927,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -917,6 +973,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 6, hdrLevel: '', spatialAudio: false, @@ -961,6 +1018,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 6, hdrLevel: '', spatialAudio: false, @@ -1004,6 +1062,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1043,6 +1102,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: true, @@ -1081,6 +1141,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1137,6 +1198,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'zh', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1181,6 +1243,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: '', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1231,6 +1294,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1270,6 +1334,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1308,6 +1373,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1346,6 +1412,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1385,6 +1452,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1424,6 +1492,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1463,6 +1532,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, @@ -1502,6 +1572,7 @@ describe('AdaptationSetCriteria', () => { builder.configure({ language: 'en', role: '', + videoRole: '', channelCount: 0, hdrLevel: '', spatialAudio: false, diff --git a/test/player_unit.js b/test/player_unit.js index 71c85c0aa2..3abcf4e13b 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -1685,7 +1685,7 @@ describe('Player', () => { variantTracks = [ { id: 100, - active: true, + active: false, type: 'variant', bandwidth: 1300, language: 'en', @@ -1727,7 +1727,7 @@ describe('Player', () => { }, { id: 101, - active: false, + active: true, type: 'variant', bandwidth: 2300, language: 'en', @@ -2180,7 +2180,7 @@ describe('Player', () => { videoTracks = [ { - active: true, + active: false, bandwidth: 1000, width: 100, height: 200, @@ -2194,7 +2194,7 @@ describe('Player', () => { roles: ['main'], }, { - active: false, + active: true, bandwidth: 2000, width: 200, height: 400, @@ -2423,10 +2423,11 @@ describe('Player', () => { it('selectAudioLanguage() applies role only to audio', () => { expect(getActiveVariantTrack().roles).not.toContain('commentary'); + const videoRoles = getActiveVariantTrack().videoRoles; player.selectAudioLanguage('en', 'commentary'); let args = streamingEngine.switchVariant.calls.argsFor(0); expect(args[0].audio.roles).toContain('commentary'); - expect(args[0].video.roles).toContain('main'); + expect(args[0].video.roles).toBe(videoRoles); // Switch audio role from 'commentary' to 'main'. streamingEngine.switchVariant.calls.reset(); @@ -2434,7 +2435,7 @@ describe('Player', () => { expect(streamingEngine.switchVariant).toHaveBeenCalled(); args = streamingEngine.switchVariant.calls.argsFor(0); expect(args[0].audio.roles).toContain('main'); - expect(args[0].video.roles).toContain('main'); + expect(args[0].video.roles).toBe(videoRoles); }); it('selectAudioLanguage() does not change selected text track', () => { From 5654aea94600408a3e62bc1b335b4109c9a725ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Thu, 31 Jul 2025 16:15:47 +0200 Subject: [PATCH 5/6] feat: Expose video label in Track and VideoTrack (#8947) --- externs/shaka/player.js | 7 +++++++ lib/player.js | 7 +++++-- lib/util/stream_utils.js | 3 +++ test/offline/storage_integration.js | 2 ++ test/player_unit.js | 12 ++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 3f30596bf4..fb78d163d1 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -252,6 +252,7 @@ shaka.extern.BufferedInfo; * * language: string, * label: ?string, + * videoLabel: ?string, * kind: ?string, * width: ?number, * height: ?number, @@ -312,6 +313,9 @@ shaka.extern.BufferedInfo; * i.e. 'en-US'. * @property {?string} label * The track label, which is unique text that should describe the track. + * @property {?string} videoLabel + * The video track label, which is unique text that should describe the video + * track. * @property {?string} kind * (only for text tracks) The kind of text track, either * 'caption' or 'subtitle'. @@ -559,6 +563,7 @@ shaka.extern.TextTrack; * mimeType: ?string, * codecs: ?string, * roles: !Array, + * label: ?string, * }} * * @description @@ -590,6 +595,8 @@ shaka.extern.TextTrack; * The video codecs string provided in the manifest, if present. * @property {!Array} roles * The roles of the track, e.g. 'main', 'sign'. + * @property {?string} label + * The track label, which is unique text that should describe the track. * @exportDoc */ shaka.extern.VideoTrack; diff --git a/lib/player.js b/lib/player.js index 68fe5d1e75..f0bdcebe35 100644 --- a/lib/player.js +++ b/lib/player.js @@ -5799,7 +5799,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { variant.video.videoLayout : '', audioLabel: variant.audio && variant.audio.label ? variant.audio.label : '', - videoLabel: '', + videoLabel: variant.video && variant.video.label ? + variant.video.label : '', codecSwitchingStrategy: this.config_.mediaSource.codecSwitchingStrategy, audioCodec: variant.audio && variant.audio.codecs ? variant.audio.codecs : '', @@ -6001,7 +6002,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { t.videoLayout == videoTrack.videoLayout && t.videoMimeType == videoTrack.mimeType && t.videoCodec == videoTrack.codecs && - ArrayUtils.equal(t.videoRoles, videoTrack.roles); + ArrayUtils.equal(t.videoRoles, videoTrack.roles) && + t.videoLabel == videoTrack.label; }); if (validVariant && !validVariant.active) { this.selectVariantTrack(validVariant, clearBuffer, safeMargin); @@ -6062,6 +6064,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { mimeType: track.videoMimeType, codecs: track.videoCodec, roles: track.videoRoles || [], + label: track.videoLabel, }; videoTracksMap.set(id, videoTrack); } diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 23f0a290fc..d7b5c6ab60 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -1357,6 +1357,7 @@ shaka.util.StreamUtils = class { bandwidth: variant.bandwidth, language: variant.language, label: null, + videoLabel: null, kind: kind, width: null, height: null, @@ -1405,6 +1406,7 @@ shaka.util.StreamUtils = class { track.colorGamut = video.colorGamut || null; track.videoLayout = video.videoLayout || null; track.videoRoles = video.roles; + track.videoLabel = video.label; const dependencyStream = video.dependencyStream; if (dependencyStream) { @@ -1659,6 +1661,7 @@ shaka.util.StreamUtils = class { bandwidth: 0, language: LanguageUtils.normalize(language || 'und'), label: audioTrack ? audioTrack.label : null, + videoLabel: null, kind: audioTrack ? audioTrack.kind : null, width: null, height: null, diff --git a/test/offline/storage_integration.js b/test/offline/storage_integration.js index 4bda04a5fa..d1f013ffe9 100644 --- a/test/offline/storage_integration.js +++ b/test/offline/storage_integration.js @@ -1376,6 +1376,7 @@ filterDescribe('Storage', storageSupport, () => { language: language, originalLanguage: language, label: null, + videoLabel: null, kind: null, width: height * (16 / 9), height: height, @@ -1426,6 +1427,7 @@ filterDescribe('Storage', storageSupport, () => { language: language, originalLanguage: language, label: null, + videoLabel: null, kind: null, width: null, height: null, diff --git a/test/player_unit.js b/test/player_unit.js index 3abcf4e13b..9c13fca798 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -1691,6 +1691,7 @@ describe('Player', () => { language: 'en', originalLanguage: 'en', label: null, + videoLabel: null, kind: null, width: 100, height: 200, @@ -1733,6 +1734,7 @@ describe('Player', () => { language: 'en', originalLanguage: 'en', label: null, + videoLabel: null, kind: null, width: 200, height: 400, @@ -1775,6 +1777,7 @@ describe('Player', () => { language: 'en', originalLanguage: 'en', label: null, + videoLabel: null, kind: null, width: 100, height: 200, @@ -1817,6 +1820,7 @@ describe('Player', () => { language: 'en', originalLanguage: 'en', label: null, + videoLabel: null, kind: null, width: 200, height: 400, @@ -1859,6 +1863,7 @@ describe('Player', () => { language: 'en', originalLanguage: 'en', label: null, + videoLabel: null, kind: null, width: 100, height: 200, @@ -1901,6 +1906,7 @@ describe('Player', () => { language: 'en', originalLanguage: 'en', label: null, + videoLabel: null, kind: null, width: 200, height: 400, @@ -1942,6 +1948,7 @@ describe('Player', () => { bandwidth: 1100, language: 'es', label: 'es-label', + videoLabel: null, originalLanguage: 'es', kind: null, width: 100, @@ -1984,6 +1991,7 @@ describe('Player', () => { bandwidth: 2100, language: 'es', label: 'es-label', + videoLabel: null, originalLanguage: 'es', kind: null, width: 200, @@ -2027,6 +2035,7 @@ describe('Player', () => { language: 'es', originalLanguage: 'es', label: null, + videoLabel: null, kind: null, width: 100, height: 200, @@ -2069,6 +2078,7 @@ describe('Player', () => { language: 'es', originalLanguage: 'es', label: null, + videoLabel: null, kind: null, width: 200, height: 400, @@ -2192,6 +2202,7 @@ describe('Player', () => { mimeType: 'video/mp4', codecs: 'avc1.4d401f', roles: ['main'], + label: null, }, { active: true, @@ -2206,6 +2217,7 @@ describe('Player', () => { mimeType: 'video/mp4', codecs: 'avc1.4d401f', roles: [], + label: null, }, ]; From 036d878a1cc1cd5ce57160b73c7441da91f21f51 Mon Sep 17 00:00:00 2001 From: Casey Occhialini <1508707+littlespex@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:53:57 -0700 Subject: [PATCH 6/6] chore: add gap jump comment --- lib/player.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/player.js b/lib/player.js index e2a50ae984..fbd54535e0 100644 --- a/lib/player.js +++ b/lib/player.js @@ -8200,6 +8200,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { */ onSeek_() { if (this.playheadObservers_) { + // Gap jump is a seek that is not caused by user interaction and needs + // to be handled differently for things like event streams and timeline + // regions. this.playheadObservers_.notifyOfSeek(!this.playhead_.getIsJumpingGap()); } if (this.streamingEngine_) {