From 72c07b35e2ba1a3c3790ba199b18883eaaab52ba Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Thu, 3 Aug 2017 15:39:15 -0700 Subject: [PATCH] Never adapt across roles or languages This adds additional logic and tests to filtering in StreamUtils. Closes #918 Closes #947 Closes #949 Change-Id: I4cce0e0dd5d247e1eaadf7401116cdafb72c7259 --- lib/player.js | 26 ++- lib/util/stream_utils.js | 169 ++++++++++----- test/util/stream_utils_unit.js | 361 ++++++++++++++++++++++++++++++--- 3 files changed, 468 insertions(+), 88 deletions(-) diff --git a/lib/player.js b/lib/player.js index 6a08460e3d..150fcf6196 100644 --- a/lib/player.js +++ b/lib/player.js @@ -655,8 +655,8 @@ shaka.Player.prototype.load = function(manifestUri, opt_startTime, // active one, update abr manager with filtered variants for the current // period. var currentPeriod = this.streamingEngine_.getCurrentPeriod(); - var variants = shaka.util.StreamUtils.filterVariantsByRoleAndLanguage( - currentPeriod, this.currentAudioLanguage_); + var variants = shaka.util.StreamUtils.filterVariantsByLanguageAndRole( + currentPeriod, this.currentAudioLanguage_, this.currentVariantRole_); this.abrManager_.setVariants(variants); var hasPrimary = currentPeriod.variants.some(function(variant) { @@ -2396,12 +2396,10 @@ shaka.Player.prototype.chooseVariant_ = function(variants) { shaka.Player.prototype.chooseStreamsAndSwitch_ = function(period) { goog.asserts.assert(this.config_, 'Must not be destroyed'); - var variants = shaka.util.StreamUtils.filterVariantsByRoleAndLanguage( - period, this.currentAudioLanguage_, /* opt_languageMatches */ undefined, - this.currentVariantRole_); - var textStreams = shaka.util.StreamUtils.filterTextStreamsByRoleAndLanguage( - period, this.currentTextLanguage_, /* opt_languageMatches */ undefined, - this.currentTextRole_); + var variants = shaka.util.StreamUtils.filterVariantsByLanguageAndRole( + period, this.currentAudioLanguage_, this.currentVariantRole_); + var textStreams = shaka.util.StreamUtils.filterTextStreamsByLanguageAndRole( + period, this.currentTextLanguage_, this.currentTextRole_); // Because we're running this after a config change (manual language change), // a new text stream, or a key status event, and because switching to an @@ -2453,13 +2451,13 @@ shaka.Player.prototype.onChooseStreams_ = function(period) { languageMatches[ContentType.AUDIO] = false; languageMatches[ContentType.TEXT] = false; - var variants = StreamUtils.filterVariantsByRoleAndLanguage( - period, this.currentAudioLanguage_, languageMatches, - this.currentVariantRole_); + var variants = StreamUtils.filterVariantsByLanguageAndRole( + period, this.currentAudioLanguage_, this.currentVariantRole_, + languageMatches); - var textStreams = StreamUtils.filterTextStreamsByRoleAndLanguage( - period, this.currentTextLanguage_, languageMatches, - this.currentTextRole_); + var textStreams = StreamUtils.filterTextStreamsByLanguageAndRole( + period, this.currentTextLanguage_, this.currentTextRole_, + languageMatches); shaka.log.v2('onChooseStreams_, variants and text streams: ', variants, textStreams); diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js index 63cef45fd7..99134147dc 100644 --- a/lib/util/stream_utils.js +++ b/lib/util/stream_utils.js @@ -23,6 +23,7 @@ goog.require('shaka.media.DrmEngine'); goog.require('shaka.media.MediaSourceEngine'); goog.require('shaka.text.TextEngine'); goog.require('shaka.util.ArrayUtils'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.LanguageUtils'); goog.require('shaka.util.ManifestParserUtils'); @@ -373,31 +374,38 @@ shaka.util.StreamUtils.getPlayableVariants = function(variants) { * * @param {shakaExtern.Period} period * @param {string} preferredLanguage + * @param {string} preferredRole * @param {!Object=} opt_languageMatches - * @param {string=} opt_role * @return {!Array.} */ -shaka.util.StreamUtils.filterVariantsByRoleAndLanguage = function( - period, preferredLanguage, opt_languageMatches, opt_role) { +shaka.util.StreamUtils.filterVariantsByLanguageAndRole = function( + period, preferredLanguage, preferredRole, opt_languageMatches) { var LanguageUtils = shaka.util.LanguageUtils; var ContentType = shaka.util.ManifestParserUtils.ContentType; var variants = shaka.util.StreamUtils.getPlayableVariants(period.variants); - // Initially choose the first language in the list. + // Start with the set of primary variants. /** @type {!Array.} */ var chosen = variants.filter(function(variant) { - return variant.language == variants[0].language; + return variant.primary; }); - // Prefer primary variants. - var primaryVariants = variants.filter(function(variant) { - return variant.primary; + // If there were no primary variants, go back to all variants. + if (!chosen.length) { + chosen = variants; + } + + // Now reduce the set to one language. This covers both arbitrary language + // choice and the reduction of the "primary" variant set to one language. + var firstLanguage = chosen.length ? chosen[0].language : ''; + chosen = chosen.filter(function(variant) { + return variant.language == firstLanguage; }); - if (primaryVariants.length) chosen = primaryVariants; - // Choose based on language preference. Favor exact matches, then - // base matches, finally different subtags. Execute in reverse order so - // the later steps override the previous ones. + // Now search for matches based on language preference. If any language match + // is found, it overrides the selection above. Favor exact matches, then base + // matches, finally different subtags. Execute in reverse order so the later + // steps override the previous ones. if (preferredLanguage) { var pref = LanguageUtils.normalize(preferredLanguage); [LanguageUtils.MatchType.OTHER_SUB_LANGUAGE_OKAY, @@ -423,22 +431,30 @@ shaka.util.StreamUtils.filterVariantsByRoleAndLanguage = function( }); // forEach(matchType) } // if (preferredLanguage) - // Choose based on role preference. If there's no exact match, return - // what was chosen based on the language preference. - var role = opt_role || ''; - if (role) { - var chosenWithRoles = chosen.filter(function(variant) { - return (variant.audio && (variant.audio.roles.indexOf(role) > - 1)) || - (variant.video && (variant.video.roles.indexOf(role) > - 1)); - }); - if (chosenWithRoles.length) return chosenWithRoles; - else { - shaka.log.warning( - 'No exact match for the role is found. Returning the selection ' + - 'based on language preference.'); + // Now refine the choice based on role preference. + if (preferredRole) { + var roleMatches = shaka.util.StreamUtils.filterVariantsByRole_( + chosen, preferredRole); + if (roleMatches.length) { + return roleMatches; + } else { + shaka.log.warning('No exact match for the variant role could be found.'); } } - return chosen; + + // Either there was no role preference, or it could not be satisfied. + // Choose an arbitrary role, if there are any, and filter out any other roles. + // This ensures we never adapt between roles. + var allRoles = chosen.map(function(variant) { + var audioRoles = variant.audio ? variant.audio.roles : []; + var videoRoles = variant.video ? variant.video.roles : []; + return audioRoles.concat(videoRoles); + }).reduce(shaka.util.Functional.collapseArrays, []); + + if (!allRoles.length) { + return chosen; + } + return shaka.util.StreamUtils.filterVariantsByRole_(chosen, allRoles[0]); }; @@ -447,29 +463,38 @@ shaka.util.StreamUtils.filterVariantsByRoleAndLanguage = function( * * @param {shakaExtern.Period} period * @param {string} preferredLanguage + * @param {string} preferredRole * @param {!Object=} opt_languageMatches - * @param {string=} opt_role * @return {!Array.} */ -shaka.util.StreamUtils.filterTextStreamsByRoleAndLanguage = function( - period, preferredLanguage, opt_languageMatches, opt_role) { +shaka.util.StreamUtils.filterTextStreamsByLanguageAndRole = function( + period, preferredLanguage, preferredRole, opt_languageMatches) { var LanguageUtils = shaka.util.LanguageUtils; var ContentType = shaka.util.ManifestParserUtils.ContentType; var streams = period.textStreams; - // Choose all the streams. + // Start with the set of primary streams. /** @type {!Array.} */ - var chosen = streams; - - // Prefer primary text streams. - var primaryStreams = streams.filter(function(stream) { + var chosen = streams.filter(function(stream) { return stream.primary; }); - if (primaryStreams.length) chosen = primaryStreams; - // Override based on language preference. Favor exact matches, then - // base matches, finally different subtags. Execute in reverse order so - // the later steps override the previous ones. + // If there were no primary streams, go back to all streams. + if (!chosen.length) { + chosen = streams; + } + + // Now reduce the set to one language. This covers both arbitrary language + // choice and the reduction of the "primary" stream set to one language. + var firstLanguage = chosen.length ? chosen[0].language : ''; + chosen = chosen.filter(function(stream) { + return stream.language == firstLanguage; + }); + + // Now search for matches based on language preference. If any language match + // is found, it overrides the selection above. Favor exact matches, then base + // matches, finally different subtags. Execute in reverse order so the later + // steps override the previous ones. if (preferredLanguage) { var pref = LanguageUtils.normalize(preferredLanguage); [LanguageUtils.MatchType.OTHER_SUB_LANGUAGE_OKAY, @@ -492,21 +517,63 @@ shaka.util.StreamUtils.filterTextStreamsByRoleAndLanguage = function( }); // forEach(stream) }); // forEach(matchType) } // if (preferredLanguage) - // Choose based on role preference. If there's no exact match, return - // what was chosen based on the language preference. - var role = opt_role || ''; - if (role) { - var chosenWithRoles = chosen.filter(function(stream) { - return (stream && (stream.roles.indexOf(role) > - 1)); - }); - if (chosenWithRoles.length) return chosenWithRoles; - else { - shaka.log.warning( - 'No exact match for the role is found. Returning the selection ' + - 'based on language preference.'); + + // Now refine the choice based on role preference. + if (preferredRole) { + var roleMatches = shaka.util.StreamUtils.filterTextStreamsByRole_( + chosen, preferredRole); + if (roleMatches.length) { + return roleMatches; + } else { + shaka.log.warning('No exact match for the text role could be found.'); } } - return chosen; + + // Either there was no role preference, or it could not be satisfied. + // Choose an arbitrary role, if there are any, and filter out any other roles. + // This ensures we never adapt between roles. + + var allRoles = chosen.map(function(stream) { + return stream.roles; + }).reduce(shaka.util.Functional.collapseArrays, []); + + if (!allRoles.length) { + return chosen; + } + return shaka.util.StreamUtils.filterTextStreamsByRole_(chosen, allRoles[0]); +}; + + +/** + * Filter Variants by role. + * + * @param {!Array.} variants + * @param {string} preferredRole + * @return {!Array.} + * @private + */ +shaka.util.StreamUtils.filterVariantsByRole_ = + function(variants, preferredRole) { + return variants.filter(function(variant) { + return (variant.audio && variant.audio.roles.indexOf(preferredRole) >= 0) || + (variant.video && variant.video.roles.indexOf(preferredRole) >= 0); + }); +}; + + +/** + * Filter text Streams by role. + * + * @param {!Array.} textStreams + * @param {string} preferredRole + * @return {!Array.} + * @private + */ +shaka.util.StreamUtils.filterTextStreamsByRole_ = + function(textStreams, preferredRole) { + return textStreams.filter(function(stream) { + return stream.roles.indexOf(preferredRole) >= 0; + }); }; diff --git a/test/util/stream_utils_unit.js b/test/util/stream_utils_unit.js index a9f75b439a..620cb284e2 100644 --- a/test/util/stream_utils_unit.js +++ b/test/util/stream_utils_unit.js @@ -17,12 +17,12 @@ describe('StreamUtils', function() { var manifest; - var preferredAudioLanguage = 'en'; - var preferredTextLanguage = 'en'; - var preferredAudioRole = 'main'; - var preferredTextRole = 'main'; + var filterVariantsByLanguageAndRole = + shaka.util.StreamUtils.filterVariantsByLanguageAndRole; + var filterTextStreamsByLanguageAndRole = + shaka.util.StreamUtils.filterTextStreamsByLanguageAndRole; - describe('filterVariantsByRoleAndLanguage', function() { + describe('filterVariantsByLanguageAndRole', function() { it("chooses variants in user's preferred language", function() { manifest = new shaka.test.ManifestGenerator() .addPeriod(0) @@ -34,14 +34,14 @@ describe('StreamUtils', function() { .language('en') .build(); - var chosen = shaka.util.StreamUtils.filterVariantsByRoleAndLanguage( - manifest.periods[0], preferredAudioLanguage); + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'en', ''); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.periods[0].variants[1]); expect(chosen[1]).toBe(manifest.periods[0].variants[2]); }); - it('chooses primary variants', function() { + it('prefers primary variants', function() { manifest = new shaka.test.ManifestGenerator() .addPeriod(0) .addVariant(0) @@ -52,8 +52,8 @@ describe('StreamUtils', function() { .primary() .build(); - var chosen = shaka.util.StreamUtils.filterVariantsByRoleAndLanguage( - manifest.periods[0], preferredAudioLanguage); + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'en', ''); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.periods[0].variants[0]); expect(chosen[1]).toBe(manifest.periods[0].variants[3]); @@ -70,8 +70,8 @@ describe('StreamUtils', function() { manifest.periods[0].variants[0].allowedByKeySystem = false; manifest.periods[0].variants[1].allowedByApplication = false; - var chosen = shaka.util.StreamUtils.filterVariantsByRoleAndLanguage( - manifest.periods[0], preferredAudioLanguage); + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'en', ''); expect(chosen.length).toBe(1); expect(chosen[0]).toBe(manifest.periods[0].variants[2]); }); @@ -90,15 +90,174 @@ describe('StreamUtils', function() { .addAudio(2).roles(['main']) .build(); - var chosen = shaka.util.StreamUtils.filterVariantsByRoleAndLanguage( - manifest.periods[0], - preferredAudioLanguage, undefined, preferredAudioRole); + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'en', 'main'); expect(chosen.length).toBe(1); expect(chosen[0]).toBe(manifest.periods[0].variants[0]); }); + + it('chooses only one role, even if none is preferred', function() { + // Regression test for https://github.com/google/shaka-player/issues/949 + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addVariant(0) + .language('en') + .addAudio(0).roles(['commentary']) + .addVariant(1) + .language('en') + .addAudio(1).roles(['commentary']) + .addVariant(2) + .language('en') + .addAudio(2).roles(['secondary']) + .addVariant(3) + .language('en') + .addAudio(3).roles(['secondary']) + .addVariant(4) + .language('en') + .addAudio(4).roles(['main']) + .addVariant(5) + .language('en') + .addAudio(5).roles(['main']) + .build(); + + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'en', ''); + // Which role is chosen is an implementation detail. + // Each role is found on two variants, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].audio.roles[0]).toEqual(chosen[1].audio.roles[0]); + }); + + it('chooses only one role, even if all are primary', function() { + // Regression test for https://github.com/google/shaka-player/issues/949 + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addVariant(0) + .language('en').primary() + .addAudio(0).roles(['commentary']) + .addVariant(1) + .language('en').primary() + .addAudio(1).roles(['commentary']) + .addVariant(2) + .language('en').primary() + .addAudio(2).roles(['secondary']) + .addVariant(3) + .language('en').primary() + .addAudio(3).roles(['secondary']) + .addVariant(4) + .language('en').primary() + .addAudio(4).roles(['main']) + .addVariant(5) + .language('en').primary() + .addAudio(5).roles(['main']) + .build(); + + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + // Which role is chosen is an implementation detail. + // Each role is found on two variants, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].audio.roles[0]).toEqual(chosen[1].audio.roles[0]); + }); + + it('chooses only one language, even if all are primary', function() { + // Regression test for https://github.com/google/shaka-player/issues/918 + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addVariant(0) + .language('en').primary() + .addAudio(0) + .addVariant(1) + .language('en').primary() + .addAudio(1) + .addVariant(2) + .language('es').primary() + .addAudio(2) + .addVariant(3) + .language('es').primary() + .addAudio(3) + .build(); + + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + // Which language is chosen is an implementation detail. + // Each role is found on two variants, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].language).toEqual(chosen[1].language); + }); + + it('chooses a role from among primary variants without language match', + function() { + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addVariant(0) + .language('en').primary() + .addAudio(0).roles(['commentary']) + .addVariant(1) + .language('en').primary() + .addAudio(1).roles(['commentary']) + .addVariant(2) + .language('en') + .addAudio(2).roles(['secondary']) + .addVariant(3) + .language('en') + .addAudio(3).roles(['secondary']) + .addVariant(4) + .language('en').primary() + .addAudio(4).roles(['main']) + .addVariant(5) + .language('en').primary() + .addAudio(5).roles(['main']) + .build(); + + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + // Which role is chosen is an implementation detail. + // Each role is found on two variants, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].audio.roles[0]).toEqual(chosen[1].audio.roles[0]); + + // Since nothing matches our language preference, we chose primary + // variants. + expect(chosen[0].primary).toBe(true); + expect(chosen[1].primary).toBe(true); + }); + + it('chooses a role from best language match, in spite of primary', + function() { + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addVariant(0) + .language('en').primary() + .addAudio(0).roles(['commentary']) + .addVariant(1) + .language('en').primary() + .addAudio(1).roles(['commentary']) + .addVariant(2) + .language('zh') + .addAudio(2).roles(['secondary']) + .addVariant(3) + .language('zh') + .addAudio(3).roles(['secondary']) + .addVariant(4) + .language('en').primary() + .addAudio(4).roles(['main']) + .addVariant(5) + .language('en').primary() + .addAudio(5).roles(['main']) + .build(); + + var chosen = filterVariantsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + expect(chosen.length).toBe(2); + expect(chosen[0].language).toBe('zh'); + expect(chosen[1].language).toBe('zh'); + expect(chosen[0].primary).toBe(false); + expect(chosen[1].primary).toBe(false); + }); }); - describe('filterTextStreamsByRoleAndLanguage', function() { + describe('filterTextStreamsByLanguageAndRole', function() { it("chooses text streams in user's preferred language", function() { manifest = new shaka.test.ManifestGenerator() .addPeriod(0) @@ -110,8 +269,8 @@ describe('StreamUtils', function() { .language('en') .build(); - var chosen = shaka.util.StreamUtils.filterTextStreamsByRoleAndLanguage( - manifest.periods[0], preferredTextLanguage); + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'en', ''); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.periods[0].textStreams[0]); expect(chosen[1]).toBe(manifest.periods[0].textStreams[2]); @@ -127,8 +286,8 @@ describe('StreamUtils', function() { .primary() .build(); - var chosen = shaka.util.StreamUtils.filterTextStreamsByRoleAndLanguage( - manifest.periods[0], preferredTextLanguage); + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'en', ''); expect(chosen.length).toBe(2); expect(chosen[0]).toBe(manifest.periods[0].textStreams[1]); expect(chosen[1]).toBe(manifest.periods[0].textStreams[2]); @@ -147,12 +306,168 @@ describe('StreamUtils', function() { .roles(['caption']) .build(); - var chosen = shaka.util.StreamUtils.filterTextStreamsByRoleAndLanguage( - manifest.periods[0], - preferredTextLanguage, undefined, preferredTextRole); + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'en', 'main'); expect(chosen.length).toBe(1); expect(chosen[0]).toBe(manifest.periods[0].textStreams[0]); }); + + it('chooses only one role, even if none is preferred', function() { + // Regression test for https://github.com/google/shaka-player/issues/949 + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addTextStream(0) + .language('en') + .roles(['commentary']) + .addTextStream(1) + .language('en') + .roles(['commentary']) + .addTextStream(2) + .language('en') + .roles(['secondary']) + .addTextStream(3) + .language('en') + .roles(['secondary']) + .addTextStream(4) + .language('en') + .roles(['main']) + .addTextStream(5) + .language('en') + .roles(['main']) + .build(); + + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'en', ''); + // Which role is chosen is an implementation detail. + // Each role is found on two text streams, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].roles[0]).toEqual(chosen[1].roles[0]); + }); + + it('chooses only one role, even if all are primary', function() { + // Regression test for https://github.com/google/shaka-player/issues/949 + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addTextStream(0) + .language('en').primary() + .roles(['commentary']) + .addTextStream(1) + .language('en').primary() + .roles(['commentary']) + .addTextStream(2) + .language('en').primary() + .roles(['secondary']) + .addTextStream(3) + .language('en').primary() + .roles(['secondary']) + .addTextStream(4) + .language('en').primary() + .roles(['main']) + .addTextStream(5) + .language('en').primary() + .roles(['main']) + .build(); + + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + // Which role is chosen is an implementation detail. + // Each role is found on two text streams, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].roles[0]).toEqual(chosen[1].roles[0]); + }); + + it('chooses only one language, even if all are primary', function() { + // Regression test for https://github.com/google/shaka-player/issues/918 + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addTextStream(0) + .language('en').primary() + .addTextStream(1) + .language('en').primary() + .addTextStream(2) + .language('es').primary() + .addTextStream(3) + .language('es').primary() + .build(); + + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + // Which language is chosen is an implementation detail. + // Each role is found on two variants, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].language).toEqual(chosen[1].language); + }); + + it('chooses a role from among primary streams without language match', + function() { + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addTextStream(0) + .language('en').primary() + .roles(['commentary']) + .addTextStream(1) + .language('en').primary() + .roles(['commentary']) + .addTextStream(2) + .language('en') + .roles(['secondary']) + .addTextStream(3) + .language('en') + .roles(['secondary']) + .addTextStream(4) + .language('en').primary() + .roles(['main']) + .addTextStream(5) + .language('en').primary() + .roles(['main']) + .build(); + + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + // Which role is chosen is an implementation detail. + // Each role is found on two text streams, so we should have two. + expect(chosen.length).toBe(2); + expect(chosen[0].roles[0]).toEqual(chosen[1].roles[0]); + + // Since nothing matches our language preference, we chose primary + // text streams. + expect(chosen[0].primary).toBe(true); + expect(chosen[1].primary).toBe(true); + }); + + it('chooses a role from best language match, in spite of primary', + function() { + manifest = new shaka.test.ManifestGenerator() + .addPeriod(0) + .addTextStream(0) + .language('en').primary() + .roles(['commentary']) + .addTextStream(1) + .language('en').primary() + .roles(['commentary']) + .addTextStream(2) + .language('zh') + .roles(['secondary']) + .addTextStream(3) + .language('zh') + .roles(['secondary']) + .addTextStream(4) + .language('en').primary() + .roles(['main']) + .addTextStream(5) + .language('en').primary() + .roles(['main']) + .build(); + + var chosen = filterTextStreamsByLanguageAndRole(manifest.periods[0], + 'zh', ''); + expect(chosen.length).toBe(2); + expect(chosen[0].language).toBe('zh'); + expect(chosen[1].language).toBe('zh'); + expect(chosen[0].primary).toBe(false); + expect(chosen[1].primary).toBe(false); + }); + }); describe('filterNewPeriod', function() {