Skip to content

Commit

Permalink
feat: Create a new simple API for Audio
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad committed Feb 3, 2025
1 parent bb7ab3c commit 17f4323
Show file tree
Hide file tree
Showing 13 changed files with 562 additions and 104 deletions.
1 change: 1 addition & 0 deletions docs/tutorials/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,4 @@ application:
- Player API Changes:
- The constructor no longer takes `mediaElement` as a parameter; use the `attach` method to attach to a media element instead. (Deprecated in v4.6)
- The `TimelineRegionInfo.eventElement` has been replaced with `TimelineRegionInfo.eventNode` property, the new property type is `shaka.externs.xml.Node` instead of `Element`
- New API for audio: `getAudioTracks` and `selectAudioTrack`, we also deprecated in v4.14 `getAudioLanguages`, `getAudioLanguagesAndRoles` and `selectAudioLanguage`.
67 changes: 67 additions & 0 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ shaka.extern.BufferedInfo;
* forced: boolean,
* videoId: ?number,
* audioId: ?number,
* audioGroupId: ?string,
* channelsCount: ?number,
* audioSamplingRate: ?number,
* tilesLayout: ?string,
Expand Down Expand Up @@ -361,6 +362,10 @@ shaka.extern.BufferedInfo;
* (only for variant tracks) The video stream id.
* @property {?number} audioId
* (only for variant tracks) The audio stream id.
* @property {?string} audioGroupId
* (only for variant tracks)
* The ID of the stream's parent element. In DASH, this will be a unique
* ID that represents the representation's parent adaptation element
* @property {?number} channelsCount
* The count of the audio track channels.
* @property {?number} audioSamplingRate
Expand Down Expand Up @@ -396,6 +401,68 @@ shaka.extern.BufferedInfo;
*/
shaka.extern.Track;

/**
* @typedef {{
* active: boolean,
* language: string,
* label: ?string,
* mimeType: ?string,
* codecs: ?string,
* primary: boolean,
* roles: !Array<string>,
* accessibilityPurpose: ?shaka.media.ManifestParser.AccessibilityPurpose,
* channelsCount: ?number,
* audioSamplingRate: ?number,
* spatialAudio: boolean,
* originalLanguage: ?string
* }}
*
* @description
* An object describing a audio track. This object should be treated as
* read-only as changing any values does not have any effect.
*
* @property {boolean} active
* If true, this is the track being streamed (another track may be
* visible/audible in the buffer).
*
* @property {string} language
* The language of the track, or <code>'und'</code> if not given. This value
* is normalized as follows - language part is always lowercase and translated
* to ISO-639-1 when possible, locale part is always uppercase,
* i.e. <code>'en-US'</code>.
* @property {?string} label
* The track label, which is unique text that should describe the track.
* @property {?string} mimeType
* The MIME type of the content provided in the manifest.
* @property {?string} codecs
* The audio codecs string provided in the manifest, if present.
* @property {boolean} primary
* True indicates that this in the primary language for the content.
* This flag is based on signals from the manifest.
* This can be a useful hint about which language should be the default, and
* indicates which track Shaka will use when the user's language preference
* cannot be satisfied.
* @property {!Array<string>} roles
* The roles of the track, e.g. <code>'main'</code>, <code>'caption'</code>,
* or <code>'commentary'</code>.
* @property {?shaka.media.ManifestParser.AccessibilityPurpose
* } accessibilityPurpose
* The DASH accessibility descriptor, if one was provided for this track.
* @property {?number} channelsCount
* The count of the audio track channels.
* @property {?number} audioSamplingRate
* Specifies the maximum sampling rate of the content.
* @property {boolean} spatialAudio
* True indicates that the content has spatial audio.
* This flag is based on signals from the manifest.
* @property {?string} originalLanguage
* The original language of the track, if any, as it appeared in the original
* manifest. This is the exact value provided in the manifest; for normalized
* value use <code>language</code> property.
* @exportDoc
*/
shaka.extern.AudioTrack;


/**
* @typedef {!Array<!shaka.extern.Track>}
Expand Down
2 changes: 2 additions & 0 deletions lib/cast/cast_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ shaka.cast.CastUtils.LargePlayerGetterMethods = new Map()
.set('getConfiguration', 4)
.set('getConfigurationForLowLatency', 4)
.set('getStats', 5)
.set('getAudioTracks', 2)
.set('getTextTracks', 2)
.set('getVariantTracks', 2);

Expand Down Expand Up @@ -402,6 +403,7 @@ shaka.cast.CastUtils.PlayerVoidMethods = [
'resetConfiguration',
'retryStreaming',
'selectAudioLanguage',
'selectAudioTrack',
'selectTextLanguage',
'selectTextTrack',
'selectVariantTrack',
Expand Down
165 changes: 165 additions & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,17 @@ goog.requireType('shaka.media.PresentationTimeline');
*/


/**
* @event shaka.Player.AudioTracksChangedEvent
* @description Fired when the list of audio tracks changes.
* An app may want to look at <code>getAudioTracks()</code> to see what
* happened.
* @property {string} type
* 'audiotrackschanged'
* @exportDoc
*/


/**
* @event shaka.Player.TracksChangedEvent
* @description Fired when the list of tracks changes. For example, this will
Expand Down Expand Up @@ -5276,11 +5287,118 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
selectSrcEqualsMode();
}

/**
* Select an audio track compatible with the current video track.
* If the player has not loaded any content, this will be a no-op.
*
* @param {shaka.extern.AudioTrack} audioTrack
* @param {number=} safeMargin Optional amount of buffer (in seconds) to
* retain when clearing the buffer. Useful for switching variant quickly
* without causing a buffering event. Defaults to 0 if not provided. Ignored
* if clearBuffer is false. Can cause hiccups on some browsers if chosen too
* small, e.g. The amount of two segments is a fair minimum to consider as
* safeMargin value.
* @export
*/
selectAudioTrack(audioTrack, safeMargin = 0) {
const variants = this.getVariantTracks();
if (!variants.length) {
return;
}
const active = variants.find((t) => t.active);
if (!active) {
return;
}
const getRolesString = (roles) => {
if (!roles) {
return '';
}
return roles.join(',');
};
const validVariant = variants.find((t) => {
return t.videoId === active.videoId &&
t.language == audioTrack.language &&
t.label == audioTrack.label &&
t.audioMimeType == audioTrack.mimeType &&
t.audioCodec == audioTrack.codecs &&
t.primary == audioTrack.primary &&
getRolesString(t.audioRoles) == getRolesString(audioTrack.roles) &&
t.accessibilityPurpose == audioTrack.accessibilityPurpose &&
t.channelsCount == audioTrack.channelsCount &&
t.audioSamplingRate == audioTrack.audioSamplingRate &&
t.spatialAudio == audioTrack.spatialAudio &&
t.originalLanguage == audioTrack.originalLanguage;
});
if (validVariant && !validVariant.active) {
this.selectVariantTrack(validVariant,
/* clearBuffer= */ true, safeMargin);
}
}


/**
* Return a list of audio tracks compatible with the current video track.
*
* @return {!Array<shaka.extern.AudioTrack>}
* @export
*/
getAudioTracks() {
const variants = this.getVariantTracks();
if (!variants.length) {
return [];
}
const active = variants.find((t) => t.active);
if (!active) {
return [];
}
let filteredTracks = variants;
if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
// Filter by current videoId and has audio.
filteredTracks = variants.filter((t) => {
return t.originalVideoId === active.originalVideoId && t.audioCodec;
});
}
if (!filteredTracks.length) {
return [];
}

/** @type {!Set<shaka.extern.AudioTrack>} */
const audioTracksSet = new Set();
for (const track of filteredTracks) {
/** @type {shaka.extern.AudioTrack} */
const audioTrack = {
active: track.active,
language: track.language,
label: track.label,
mimeType: track.audioMimeType,
codecs: track.audioCodec,
primary: track.primary,
roles: track.audioRoles || [],
accessibilityPurpose: track.accessibilityPurpose,
channelsCount: track.channelsCount,
audioSamplingRate: track.audioSamplingRate,
spatialAudio: track.spatialAudio,
originalLanguage: track.originalLanguage,
};
audioTracksSet.add(audioTrack);
}
if (!audioTracksSet.size) {
return [];
}
return Array.from(audioTracksSet);
}

/**
* Return a list of audio language-role combinations available. If the
* player has not loaded any content, this will return an empty list.
*
* <br>
*
* This API is deprecated and will be removed in version 5.0, please migrate
* to using `getAudioTracks` and `selectAudioTrack`.
*
* @return {!Array<shaka.extern.LanguageRole>}
* @deprecated
* @export
*/
getAudioLanguagesAndRoles() {
Expand All @@ -5302,7 +5420,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* Return a list of audio languages available. If the player has not loaded
* any content, this will return an empty list.
*
* <br>
*
* This API is deprecated and will be removed in version 5.0, please migrate
* to using `getAudioTracks` and `selectAudioTrack`.
*
* @return {!Array<string>}
* @deprecated
* @export
*/
getAudioLanguages() {
Expand All @@ -5325,13 +5449,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* language, role and channel count, and chooses a new variant if need be.
* If the player has not loaded any content, this will be a no-op.
*
* <br>
*
* This API is deprecated and will be removed in version 5.0, please migrate
* to using `getAudioTracks` and `selectAudioTrack`.
*
* @param {string} language
* @param {string=} role
* @param {number=} channelsCount
* @param {number=} safeMargin
* @param {string=} codec
* @param {boolean=} spatialAudio
* @param {string=} label
* @deprecated
* @export
*/
selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0,
Expand Down Expand Up @@ -7428,6 +7558,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
// Dispatch a 'variantchanged' event
this.onVariantChanged_(oldTrack, newTrack);
}
// Dispatch a 'audiotrackschanged' event if necessary
this.checkAudioTracksChanged_(oldTrack, newTrack);
}

/**
Expand Down Expand Up @@ -7457,7 +7589,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
shaka.util.StreamUtils.html5AudioTrackToTrack(currentTrack);
const newTrack =
shaka.util.StreamUtils.html5AudioTrackToTrack(track);
// Dispatch a 'variantchanged' event
this.onVariantChanged_(oldTrack, newTrack);

// Dispatch a 'audiotrackschanged' event if necessary
this.checkAudioTracksChanged_(oldTrack, newTrack);
}

/**
Expand Down Expand Up @@ -7668,6 +7804,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
const event = shaka.Player.makeEvent_(
shaka.util.FakeEvent.EventName.TracksChanged);
this.delayDispatchEvent_(event);

// Also fire 'audiotrackschanged' event.
this.onAudioTracksChanged_();
}

/**
Expand All @@ -7691,6 +7830,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.delayDispatchEvent_(event);
}

/**
* Dispatches a 'audiotrackschanged' event if necessary
* @param {?shaka.extern.Track} from
* @param {shaka.extern.Track} to
* @private
*/
checkAudioTracksChanged_(from, to) {
let dispatchEvent = false;
if (!from || from.audioId != to.audioId ||
from.audioGroupId != to.audioGroupId) {
dispatchEvent = true;
}
if (dispatchEvent) {
this.onAudioTracksChanged_();
}
}

/** @private */
onAudioTracksChanged_() {
// Delay the 'audiotrackschanged' event so StreamingEngine has time to
// absorb the changes before the user tries to query it.
const event = shaka.Player.makeEvent_(
shaka.util.FakeEvent.EventName.AudioTracksChanged);
this.delayDispatchEvent_(event);
}

/**
* Dispatches a 'textchanged' event.
* @private
Expand Down
1 change: 1 addition & 0 deletions lib/util/fake_event.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ shaka.util.FakeEvent.EventName = {
AbrStatusChanged: 'abrstatuschanged',
Adaptation: 'adaptation',
AudioTrackChanged: 'audiotrackchanged',
AudioTracksChanged: 'audiotrackschanged',
Buffering: 'buffering',
Complete: 'complete',
DownloadCompleted: 'downloadcompleted',
Expand Down
7 changes: 7 additions & 0 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,9 @@ shaka.util.StreamUtils = class {
/** @type {?string} */
const videoCodec = video ? video.codecs : null;

/** @type {?string} */
const audioGroupId = audio ? audio.groupId : null;

/** @type {!Array<string>} */
const codecs = [];
if (videoCodec) {
Expand Down Expand Up @@ -1357,6 +1360,7 @@ shaka.util.StreamUtils = class {
forced: false,
videoId: null,
audioId: null,
audioGroupId: audioGroupId,
channelsCount: null,
audioSamplingRate: null,
spatialAudio: false,
Expand Down Expand Up @@ -1453,6 +1457,7 @@ shaka.util.StreamUtils = class {
forced: stream.forced,
videoId: null,
audioId: null,
audioGroupId: null,
channelsCount: null,
audioSamplingRate: null,
spatialAudio: false,
Expand Down Expand Up @@ -1531,6 +1536,7 @@ shaka.util.StreamUtils = class {
forced: false,
videoId: null,
audioId: null,
audioGroupId: null,
channelsCount: null,
audioSamplingRate: null,
spatialAudio: false,
Expand Down Expand Up @@ -1659,6 +1665,7 @@ shaka.util.StreamUtils = class {
audioRoles: null,
videoId: null,
audioId: null,
audioGroupId: null,
channelsCount: null,
audioSamplingRate: null,
spatialAudio: false,
Expand Down
Loading

0 comments on commit 17f4323

Please sign in to comment.