Skip to content

Commit

Permalink
feat: Create a new simple API for Audio (#8005)
Browse files Browse the repository at this point in the history
Close #3544

---------

Co-authored-by: Wojciech Tyczyński <[email protected]>
  • Loading branch information
avelad and tykus160 authored Feb 5, 2025
1 parent fc33928 commit f778713
Show file tree
Hide file tree
Showing 15 changed files with 582 additions and 106 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
159 changes: 159 additions & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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');
Expand Down Expand Up @@ -273,6 +274,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 @@ -5280,11 +5292,111 @@ 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 quickly
* without causing a buffering event. Defaults to 0 if not provided. 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 ArrayUtils = shaka.util.ArrayUtils;
const variants = this.getVariantTracks();
if (!variants.length) {
return;
}
const active = variants.find((t) => t.active);
if (!active) {
return;
}
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 &&
ArrayUtils.equal(t.audioRoles, audioTrack.roles) &&
t.accessibilityPurpose == audioTrack.accessibilityPurpose &&
t.channelsCount == audioTrack.channelsCount &&
t.audioSamplingRate == audioTrack.audioSamplingRate &&
t.spatialAudio == audioTrack.spatialAudio;
});
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 @@ -5306,7 +5418,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 @@ -5329,13 +5447,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 @@ -7433,6 +7557,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 @@ -7462,7 +7588,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 @@ -7673,6 +7803,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 @@ -7696,6 +7829,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
10 changes: 8 additions & 2 deletions lib/util/array_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,19 @@ shaka.util.ArrayUtils = class {
/**
* Determines if the given arrays contain equal elements in the same order.
*
* @param {!Array<T>} a
* @param {!Array<T>} b
* @param {Array<T>} a
* @param {Array<T>} b
* @param {function(T, T):boolean=} compareFn
* @return {boolean}
* @template T
*/
static equal(a, b, compareFn) {
if (a === b) {
return true;
}
if (!a || !b) {
return a == b;
}
if (!compareFn) {
compareFn = shaka.util.ArrayUtils.defaultEquals;
}
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
Loading

0 comments on commit f778713

Please sign in to comment.