Skip to content

Commit

Permalink
Don't include video roles in audio-language-role pairs
Browse files Browse the repository at this point in the history
Rather than using tracks (that merge the roles) use the streams when
creating the audio-language-role pairs.

The reason we tried using tracks was to remove the need to handle
audio muxed into video streams. With this change, that problem is back
but we fixed the broken behaviour.

Issue #1731

Change-Id: I837406469195956089f33f93f4a9e3c64ffbeab2
  • Loading branch information
vaage committed Dec 18, 2018
1 parent 275a68c commit 41f0f0e
Showing 1 changed file with 145 additions and 48 deletions.
193 changes: 145 additions & 48 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,9 @@ shaka.Player.prototype.cancelTrickPlay = function() {
* @export
*/
shaka.Player.prototype.getVariantTracks = function() {
// TODO: Update to use |getSelectableVariants| so that this and the other
// methods are all getting the variants from the same source.

if (!this.manifest_ || !this.playhead_) {
return [];
}
Expand All @@ -1649,6 +1652,9 @@ shaka.Player.prototype.getVariantTracks = function() {
* @export
*/
shaka.Player.prototype.getTextTracks = function() {
// TODO: Update this to use |getSelectableText| so that we are sourcing our
// text streams from a common source.

if (!this.manifest_ || !this.playhead_) {
return [];
}
Expand Down Expand Up @@ -1832,7 +1838,18 @@ shaka.Player.prototype.selectVariantTrack = function(
* @export
*/
shaka.Player.prototype.getAudioLanguagesAndRoles = function() {
return shaka.Player.getLanguageAndRolesFromTracks_(this.getVariantTracks());
// TODO: This assumes that language is always on the audio stream. This is not
// true when audio and video are muxed together.
// TODO: If the language is on the video stream, how do roles affect the
// the language-role pairing?

/** @type {!Array.<?shaka.extern.Stream>} */
const audioStreams = [];
for (const variant of this.getSelectableVariants_()) {
audioStreams.push(variant.audio);
}

return shaka.Player.getLanguageAndRolesFrom_(audioStreams);
};


Expand All @@ -1844,7 +1861,7 @@ shaka.Player.prototype.getAudioLanguagesAndRoles = function() {
* @export
*/
shaka.Player.prototype.getTextLanguagesAndRoles = function() {
return shaka.Player.getLanguageAndRolesFromTracks_(this.getTextTracks());
return shaka.Player.getLanguageAndRolesFrom_(this.getSelectableText_());
};


Expand All @@ -1855,8 +1872,16 @@ shaka.Player.prototype.getTextLanguagesAndRoles = function() {
* @export
*/
shaka.Player.prototype.getAudioLanguages = function() {
const tracks = this.getVariantTracks();
return Array.from(shaka.Player.getLanguagesFromTracks_(tracks));
// TODO: This assumes that language is always on the audio stream. This is not
// true when audio and video are muxed together.

/** @type {!Array.<?shaka.extern.Stream>} */
const audioStreams = [];
for (const variant of this.getSelectableVariants_()) {
audioStreams.push(variant.audio);
}

return Array.from(shaka.Player.getLanguagesFrom_(audioStreams));
};


Expand All @@ -1867,8 +1892,7 @@ shaka.Player.prototype.getAudioLanguages = function() {
* @export
*/
shaka.Player.prototype.getTextLanguages = function() {
const tracks = this.getTextTracks();
return Array.from(shaka.Player.getLanguagesFromTracks_(tracks));
return Array.from(shaka.Player.getLanguagesFrom_(this.getSelectableText_()));
};


Expand Down Expand Up @@ -3525,71 +3549,144 @@ shaka.Player.prototype.waitNextTick_ = function() {
};

/**
* Return a list of text language-role combinations available for the given
* tracks.
* Get the normalized languages for a group of streams. If a stream is |null|,
* it means that there is a variant but no audio stream and the language should
* be "und".
*
* @param {!Iterable.<shaka.extern.Track>} tracks
* @param {!Array.<?shaka.extern.Stream>} streams
* @return {!Set.<string>}
* @private
*/
shaka.Player.getLanguagesFrom_ = function(streams) {
const languages = new Set();

for (const stream of streams) {
if (stream && stream.language) {
languages.add(shaka.util.LanguageUtils.normalize(stream.language));
} else {
languages.add('und');
}
}

return languages;
};


/**
* Get all permutations of normalized languages and role for a group of streams.
* If a stream is |null|, it means that there is a variant but no audio stream
* and the language should be "und".
*
* @param {!Array.<?shaka.extern.Stream>} streams
* @return {!Array.<shaka.extern.LanguageRole>}
* @private
*/
shaka.Player.getLanguageAndRolesFromTracks_ = function(tracks) {
/**
* Group together all the roles that are used with each language. Use a set
* for the roles so that we don't track duplicates.
*
* @type {!Map.<string, !Set.<string>>}
**/
const rolesByLanguage = new Map();

for (const track of tracks) {
/** @type {string} */
const language = shaka.util.LanguageUtils.normalize(track.language);
/** @type {!Set.<string>} */
const roles = rolesByLanguage.get(language) || new Set();

for (const role of track.roles) {
roles.add(role);
shaka.Player.getLanguageAndRolesFrom_ = function(streams) {
/** @type {!Map.<string, !Set>} */
const languageToRoles = new Map();

// We must have an empty role so that we will still get a language-role entry.
const noRoles = [''];

for (const stream of streams) {
let language = 'und';
let roles = noRoles;

if (stream && stream.language) {
language = shaka.util.LanguageUtils.normalize(stream.language);
}

rolesByLanguage.set(language, roles);
}
if (stream && stream.roles.length) {
roles = stream.roles;
}

// If there are no roles, add an empty one so that combos will still be
// made.
rolesByLanguage.forEach((roles, language) => {
if (roles.size == 0) {
roles.add('');
if (!languageToRoles.has(language)) {
languageToRoles.set(language, new Set());
}
});

/** @type {!Array.<shaka.extern.LanguageRole>} */
const combos = [];
rolesByLanguage.forEach((roles, language) => {
for (const role of roles) {
combos.push({
languageToRoles.get(language).add(role);
}
}

// Flatten our map to an array of language-role pairs.
const pairings = [];
languageToRoles.forEach((roles, language) => {
for (const role of roles) {
pairings.push({
language: language,
role: role,
});
}
});
return pairings;
};


/**
* Get the variants that the user can select. The variants will be based on
* the period that the playhead is in and what variants are playable.
*
* @return {!Array.<shaka.extern.Variant>}
* @private
*/
shaka.Player.prototype.getSelectableVariants_ = function() {
// If we have been called before we load content or after we have unloaded
// content, then we should return no streams.
if (!this.manifest_ || !this.playhead_) {
return [];
}

// Use the period that is currently playing, allowing the change to affect
// the "now".
const currentPeriodIndex = shaka.util.StreamUtils.findPeriodContainingTime(
this.manifest_,
this.playhead_.getTime());

const currentPeriod = this.manifest_.periods[currentPeriodIndex];

return combos;
const variants = [];

for (const variant of currentPeriod.variants) {
if (shaka.util.StreamUtils.isPlayable(variant)) {
variants.push(variant);
}
}

return variants;
};


/**
* @param {!Iterable.<shaka.extern.Track>} tracks
* @return {!Set.<string>}
* Get the text streams that the user can select. The streams will be based on
* the period that the playhead is in and what streams have finished loading.
*
* @return {!Array.<shaka.extern.Stream>}
* @private
*/
shaka.Player.getLanguagesFromTracks_ = function(tracks) {
/** @type {!Set.<string>} */
const languages = new Set();
shaka.Player.prototype.getSelectableText_ = function() {
// If we have been called before we load content or after we have unloaded
// content, then we should return no streams.
if (!this.manifest_ || !this.playhead_) {
return [];
}

// Use the period that is currently playing, allowing the change to affect
// the "now".
const currentPeriodIndex = shaka.util.StreamUtils.findPeriodContainingTime(
this.manifest_,
this.playhead_.getTime());

const currentPeriod = this.manifest_.periods[currentPeriodIndex];

for (const track of tracks) {
const language = shaka.util.LanguageUtils.normalize(track.language);
languages.add(language);
const streams = [];

for (const stream of currentPeriod.textStreams) {
// Don't show return streams that are still loading.
if (!this.loadingTextStreamIds_.includes(stream.id)) {
streams.push(stream);
}
}

return languages;
return streams;
};

0 comments on commit 41f0f0e

Please sign in to comment.