Source: lib/util/stream_utils.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.util.StreamUtils');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.log');
  20. goog.require('shaka.media.DrmEngine');
  21. goog.require('shaka.media.MediaSourceEngine');
  22. goog.require('shaka.text.TextEngine');
  23. goog.require('shaka.util.ArrayUtils');
  24. goog.require('shaka.util.Functional');
  25. goog.require('shaka.util.LanguageUtils');
  26. goog.require('shaka.util.ManifestParserUtils');
  27. goog.require('shaka.util.MimeUtils');
  28. /**
  29. * @namespace shaka.util.StreamUtils
  30. * @summary A set of utility functions for dealing with Streams and Manifests.
  31. */
  32. /**
  33. * @param {shakaExtern.Variant} variant
  34. * @param {shakaExtern.Restrictions} restrictions
  35. * Configured restrictions from the user.
  36. * @param {{width: number, height: number}} maxHwRes
  37. * The maximum resolution the hardware can handle.
  38. * This is applied separately from user restrictions because the setting
  39. * should not be easily replaced by the user's configuration.
  40. * @return {boolean}
  41. */
  42. shaka.util.StreamUtils.meetsRestrictions = function(
  43. variant, restrictions, maxHwRes) {
  44. var video = variant.video;
  45. if (video) {
  46. if (video.width < restrictions.minWidth ||
  47. video.width > restrictions.maxWidth || video.width > maxHwRes.width ||
  48. video.height < restrictions.minHeight ||
  49. video.height > restrictions.maxHeight ||
  50. video.height > maxHwRes.height ||
  51. (video.width * video.height) < restrictions.minPixels ||
  52. (video.width * video.height) > restrictions.maxPixels) {
  53. return false;
  54. }
  55. }
  56. if (variant.bandwidth < restrictions.minBandwidth ||
  57. variant.bandwidth > restrictions.maxBandwidth) {
  58. return false;
  59. }
  60. return true;
  61. };
  62. /**
  63. * @param {shakaExtern.Period} period
  64. * @param {shakaExtern.Restrictions} restrictions
  65. * @param {{width: number, height: number}} maxHwRes
  66. * @return {boolean} Whether the tracks changed.
  67. */
  68. shaka.util.StreamUtils.applyRestrictions =
  69. function(period, restrictions, maxHwRes) {
  70. var tracksChanged = false;
  71. period.variants.forEach(function(variant) {
  72. var originalAllowed = variant.allowedByApplication;
  73. variant.allowedByApplication = shaka.util.StreamUtils.meetsRestrictions(
  74. variant, restrictions, maxHwRes);
  75. if (originalAllowed != variant.allowedByApplication) {
  76. tracksChanged = true;
  77. }
  78. });
  79. return tracksChanged;
  80. };
  81. /**
  82. * Alters the given Period to filter out any unplayable streams.
  83. *
  84. * @param {shaka.media.DrmEngine} drmEngine
  85. * @param {!Object.<string, shakaExtern.Stream>} activeStreams
  86. * @param {shakaExtern.Period} period
  87. */
  88. shaka.util.StreamUtils.filterNewPeriod = function(
  89. drmEngine, activeStreams, period) {
  90. var StreamUtils = shaka.util.StreamUtils;
  91. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  92. var activeVideo = activeStreams[ContentType.VIDEO];
  93. var activeAudio = activeStreams[ContentType.AUDIO];
  94. // Filter variants
  95. for (var i = 0; i < period.variants.length; ++i) {
  96. var variant = period.variants[i];
  97. if (!StreamUtils.variantIsCompatible_(
  98. variant, drmEngine, activeAudio, activeVideo)) {
  99. shaka.log.debug('Dropping Variant (not compatible with key system, ' +
  100. 'platform, or active Variant)', variant);
  101. period.variants.splice(i, 1);
  102. --i;
  103. continue;
  104. }
  105. }
  106. // Filter text streams
  107. for (var i = 0; i < period.textStreams.length; ++i) {
  108. var stream = period.textStreams[i];
  109. var fullMimeType = shaka.util.MimeUtils.getFullType(
  110. stream.mimeType, stream.codecs);
  111. if (!shaka.text.TextEngine.isTypeSupported(fullMimeType)) {
  112. shaka.log.debug('Dropping text stream. Is not supported by the ' +
  113. 'platform.', stream);
  114. period.textStreams.splice(i, 1);
  115. --i;
  116. }
  117. }
  118. };
  119. /**
  120. * Checks if a stream is compatible with the key system, platform,
  121. * and active stream.
  122. *
  123. * @param {?shakaExtern.Stream} stream
  124. * @param {shaka.media.DrmEngine} drmEngine
  125. * @param {?shakaExtern.Stream} activeStream
  126. * @return {boolean}
  127. * @private
  128. */
  129. shaka.util.StreamUtils.streamIsCompatible_ =
  130. function(stream, drmEngine, activeStream) {
  131. if (!stream) return true;
  132. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  133. goog.asserts.assert(stream.type != ContentType.TEXT,
  134. 'Should not be called on a text stream!');
  135. var drmSupportedMimeTypes = null;
  136. if (drmEngine && drmEngine.initialized()) {
  137. drmSupportedMimeTypes = drmEngine.getSupportedTypes();
  138. }
  139. // Check if stream can be played by the platform
  140. var fullMimeType = shaka.util.MimeUtils.getFullType(
  141. stream.mimeType, stream.codecs);
  142. if (!shaka.media.MediaSourceEngine.isStreamSupported(stream))
  143. return false;
  144. // Check if stream can be handled by the key system.
  145. // There's no need to check that the stream is supported by the
  146. // chosen key system since the caller has already verified that.
  147. if (drmSupportedMimeTypes && stream.encrypted &&
  148. drmSupportedMimeTypes.indexOf(fullMimeType) < 0) {
  149. return false;
  150. }
  151. // Lastly, check if active stream can switch to the stream
  152. // Basic mime types and basic codecs need to match.
  153. // For example, we can't adapt between WebM and MP4,
  154. // nor can we adapt between mp4a.* to ec-3.
  155. // We can switch between text types on the fly,
  156. // so don't run this check on text.
  157. if (activeStream) {
  158. if (stream.mimeType != activeStream.mimeType ||
  159. stream.codecs.split('.')[0] != activeStream.codecs.split('.')[0]) {
  160. return false;
  161. }
  162. }
  163. return true;
  164. };
  165. /**
  166. * Checks if a variant is compatible with the key system, platform,
  167. * and active stream.
  168. *
  169. * @param {!shakaExtern.Variant} variant
  170. * @param {shaka.media.DrmEngine} drmEngine
  171. * @param {shakaExtern.Stream} activeAudio
  172. * @param {shakaExtern.Stream} activeVideo
  173. * @return {boolean}
  174. * @private
  175. */
  176. shaka.util.StreamUtils.variantIsCompatible_ =
  177. function(variant, drmEngine, activeAudio, activeVideo) {
  178. var StreamUtils = shaka.util.StreamUtils;
  179. if (drmEngine && drmEngine.initialized()) {
  180. if (!drmEngine.isSupportedByKeySystem(variant)) return false;
  181. }
  182. return StreamUtils.streamIsCompatible_(variant.audio,
  183. drmEngine,
  184. activeAudio) &&
  185. StreamUtils.streamIsCompatible_(variant.video, drmEngine, activeVideo);
  186. };
  187. /**
  188. * Gets an array of Track objects for the given Period
  189. *
  190. * @param {shakaExtern.Period} period
  191. * @param {?number} activeAudioId
  192. * @param {?number} activeVideoId
  193. * @return {!Array.<shakaExtern.Track>}
  194. */
  195. shaka.util.StreamUtils.getVariantTracks =
  196. function(period, activeAudioId, activeVideoId) {
  197. var StreamUtils = shaka.util.StreamUtils;
  198. var variants = StreamUtils.getPlayableVariants(period.variants);
  199. var label = null;
  200. var tracks = variants.map(function(variant) {
  201. var isActive;
  202. if (variant.video && variant.audio) {
  203. isActive = activeVideoId == variant.video.id &&
  204. activeAudioId == variant.audio.id;
  205. } else {
  206. isActive = (variant.video && activeVideoId == variant.video.id) ||
  207. (variant.audio && activeAudioId == variant.audio.id);
  208. }
  209. var codecs = '';
  210. if (variant.video) codecs += variant.video.codecs;
  211. if (variant.audio) {
  212. if (codecs != '') codecs += ', ';
  213. codecs += variant.audio.codecs;
  214. label = variant.audio.label;
  215. }
  216. var audioCodec = variant.audio ? variant.audio.codecs : null;
  217. var videoCodec = variant.video ? variant.video.codecs : null;
  218. var mimeType = null;
  219. if (variant.video) mimeType = variant.video.mimeType;
  220. else if (variant.audio) mimeType = variant.audio.mimeType;
  221. var kind = null;
  222. if (variant.audio) kind = variant.audio.kind;
  223. else if (variant.video) kind = variant.video.kind;
  224. var audioRoles = variant.audio ? variant.audio.roles : [];
  225. var videoRoles = variant.video ? variant.video.roles : [];
  226. var roles = shaka.util.ArrayUtils.removeDuplicates(
  227. audioRoles.concat(videoRoles));
  228. return {
  229. id: variant.id,
  230. active: isActive,
  231. type: 'variant',
  232. bandwidth: variant.bandwidth,
  233. language: variant.language,
  234. label: label,
  235. kind: kind || null,
  236. width: variant.video ? variant.video.width : null,
  237. height: variant.video ? variant.video.height : null,
  238. frameRate: variant.video ? variant.video.frameRate : undefined,
  239. mimeType: mimeType,
  240. codecs: codecs,
  241. audioCodec: audioCodec,
  242. videoCodec: videoCodec,
  243. primary: variant.primary,
  244. roles: roles,
  245. videoId: variant.video ? variant.video.id : null,
  246. audioId: variant.audio ? variant.audio.id : null,
  247. channelsCount: variant.audio ? variant.audio.channelsCount : null,
  248. audioBandwidth: (variant.audio && variant.audio.bandwidth) ?
  249. variant.audio.bandwidth : null,
  250. videoBandwidth: (variant.video && variant.video.bandwidth) ?
  251. variant.video.bandwidth : null
  252. };
  253. });
  254. return tracks;
  255. };
  256. /**
  257. * Gets an array of text Track objects for the given Period.
  258. *
  259. * @param {shakaExtern.Period} period
  260. * @param {?number} activeStreamId
  261. * @return {!Array.<shakaExtern.Track>}
  262. */
  263. shaka.util.StreamUtils.getTextTracks = function(period, activeStreamId) {
  264. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  265. return period.textStreams.map(function(stream) {
  266. return {
  267. id: stream.id,
  268. active: activeStreamId == stream.id,
  269. type: ContentType.TEXT,
  270. language: stream.language,
  271. label: stream.label,
  272. kind: stream.kind,
  273. mimeType: stream.mimeType,
  274. codecs: stream.codecs || null,
  275. audioCodec: null,
  276. videoCodec: null,
  277. primary: stream.primary,
  278. roles: stream.roles,
  279. channelsCount: null,
  280. audioBandwidth: null,
  281. videoBandwidth: null
  282. };
  283. });
  284. };
  285. /**
  286. * Find the Variant for the given track.
  287. *
  288. * @param {shakaExtern.Period} period
  289. * @param {shakaExtern.Track} track
  290. * @return {?shakaExtern.Variant}
  291. */
  292. shaka.util.StreamUtils.findVariantForTrack = function(period, track) {
  293. for (var i = 0; i < period.variants.length; i++) {
  294. if (period.variants[i].id == track.id)
  295. return period.variants[i];
  296. }
  297. return null;
  298. };
  299. /**
  300. * Find the text stream for the given track.
  301. *
  302. * @param {shakaExtern.Period} period
  303. * @param {shakaExtern.Track} track
  304. * @return {?shakaExtern.Stream}
  305. */
  306. shaka.util.StreamUtils.findTextStreamForTrack = function(period, track) {
  307. for (var i = 0; i < period.textStreams.length; i++) {
  308. if (period.textStreams[i].id == track.id)
  309. return period.textStreams[i];
  310. }
  311. return null;
  312. };
  313. /**
  314. * Determines if the given variant is playable.
  315. * @param {!shakaExtern.Variant} variant
  316. * @return {boolean}
  317. */
  318. shaka.util.StreamUtils.isPlayable = function(variant) {
  319. return variant.allowedByApplication && variant.allowedByKeySystem;
  320. };
  321. /**
  322. * Filters out not playable variants.
  323. * @param {!Array.<!shakaExtern.Variant>} variants
  324. * @return {!Array.<!shakaExtern.Variant>}
  325. */
  326. shaka.util.StreamUtils.getPlayableVariants = function(variants) {
  327. return variants.filter(function(variant) {
  328. return shaka.util.StreamUtils.isPlayable(variant);
  329. });
  330. };
  331. /**
  332. * Chooses variants according to the given config.
  333. *
  334. * @param {shakaExtern.Period} period
  335. * @param {string} preferredLanguage
  336. * @param {string} preferredRole
  337. * @param {!Object=} opt_languageMatches
  338. * @return {!Array.<!shakaExtern.Variant>}
  339. */
  340. shaka.util.StreamUtils.filterVariantsByLanguageAndRole = function(
  341. period, preferredLanguage, preferredRole, opt_languageMatches) {
  342. var LanguageUtils = shaka.util.LanguageUtils;
  343. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  344. var variants = shaka.util.StreamUtils.getPlayableVariants(period.variants);
  345. // Start with the set of primary variants.
  346. /** @type {!Array.<!shakaExtern.Variant>} */
  347. var chosen = variants.filter(function(variant) {
  348. return variant.primary;
  349. });
  350. // If there were no primary variants, go back to all variants.
  351. if (!chosen.length) {
  352. chosen = variants;
  353. }
  354. // Now reduce the set to one language. This covers both arbitrary language
  355. // choice and the reduction of the "primary" variant set to one language.
  356. var firstLanguage = chosen.length ? chosen[0].language : '';
  357. chosen = chosen.filter(function(variant) {
  358. return variant.language == firstLanguage;
  359. });
  360. // Now search for matches based on language preference. If any language match
  361. // is found, it overrides the selection above. Favor exact matches, then base
  362. // matches, finally different subtags. Execute in reverse order so the later
  363. // steps override the previous ones.
  364. if (preferredLanguage) {
  365. var pref = LanguageUtils.normalize(preferredLanguage);
  366. [LanguageUtils.MatchType.OTHER_SUB_LANGUAGE_OKAY,
  367. LanguageUtils.MatchType.BASE_LANGUAGE_OKAY,
  368. LanguageUtils.MatchType.EXACT]
  369. .forEach(function(matchType) {
  370. var betterLangMatchFound = false;
  371. variants.forEach(function(variant) {
  372. pref = LanguageUtils.normalize(pref);
  373. var lang = LanguageUtils.normalize(variant.language);
  374. if (LanguageUtils.match(matchType, pref, lang)) {
  375. if (betterLangMatchFound) {
  376. chosen.push(variant);
  377. } else {
  378. chosen = [variant];
  379. betterLangMatchFound = true;
  380. }
  381. if (opt_languageMatches) {
  382. opt_languageMatches[ContentType.AUDIO] = true;
  383. }
  384. }
  385. }); // forEach(variant)
  386. }); // forEach(matchType)
  387. } // if (preferredLanguage)
  388. // Now refine the choice based on role preference.
  389. if (preferredRole) {
  390. var roleMatches = shaka.util.StreamUtils.filterVariantsByRole_(
  391. chosen, preferredRole);
  392. if (roleMatches.length) {
  393. return roleMatches;
  394. } else {
  395. shaka.log.warning('No exact match for the variant role could be found.');
  396. }
  397. }
  398. // Either there was no role preference, or it could not be satisfied.
  399. // Choose an arbitrary role, if there are any, and filter out any other roles.
  400. // This ensures we never adapt between roles.
  401. var allRoles = chosen.map(function(variant) {
  402. var audioRoles = variant.audio ? variant.audio.roles : [];
  403. var videoRoles = variant.video ? variant.video.roles : [];
  404. return audioRoles.concat(videoRoles);
  405. }).reduce(shaka.util.Functional.collapseArrays, []);
  406. if (!allRoles.length) {
  407. return chosen;
  408. }
  409. return shaka.util.StreamUtils.filterVariantsByRole_(chosen, allRoles[0]);
  410. };
  411. /**
  412. * Chooses text streams according to the given config.
  413. *
  414. * @param {shakaExtern.Period} period
  415. * @param {string} preferredLanguage
  416. * @param {string} preferredRole
  417. * @param {!Object=} opt_languageMatches
  418. * @return {!Array.<!shakaExtern.Stream>}
  419. */
  420. shaka.util.StreamUtils.filterTextStreamsByLanguageAndRole = function(
  421. period, preferredLanguage, preferredRole, opt_languageMatches) {
  422. var LanguageUtils = shaka.util.LanguageUtils;
  423. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  424. var streams = period.textStreams;
  425. // Start with the set of primary streams.
  426. /** @type {!Array.<!shakaExtern.Stream>} */
  427. var chosen = streams.filter(function(stream) {
  428. return stream.primary;
  429. });
  430. // If there were no primary streams, go back to all streams.
  431. if (!chosen.length) {
  432. chosen = streams;
  433. }
  434. // Now reduce the set to one language. This covers both arbitrary language
  435. // choice and the reduction of the "primary" stream set to one language.
  436. var firstLanguage = chosen.length ? chosen[0].language : '';
  437. chosen = chosen.filter(function(stream) {
  438. return stream.language == firstLanguage;
  439. });
  440. // Now search for matches based on language preference. If any language match
  441. // is found, it overrides the selection above. Favor exact matches, then base
  442. // matches, finally different subtags. Execute in reverse order so the later
  443. // steps override the previous ones.
  444. if (preferredLanguage) {
  445. var pref = LanguageUtils.normalize(preferredLanguage);
  446. [LanguageUtils.MatchType.OTHER_SUB_LANGUAGE_OKAY,
  447. LanguageUtils.MatchType.BASE_LANGUAGE_OKAY,
  448. LanguageUtils.MatchType.EXACT]
  449. .forEach(function(matchType) {
  450. var betterLangMatchFound = false;
  451. streams.forEach(function(stream) {
  452. var lang = LanguageUtils.normalize(stream.language);
  453. if (LanguageUtils.match(matchType, pref, lang)) {
  454. if (betterLangMatchFound) {
  455. chosen.push(stream);
  456. } else {
  457. chosen = [stream];
  458. betterLangMatchFound = true;
  459. }
  460. if (opt_languageMatches)
  461. opt_languageMatches[ContentType.TEXT] = true;
  462. }
  463. }); // forEach(stream)
  464. }); // forEach(matchType)
  465. } // if (preferredLanguage)
  466. // Now refine the choice based on role preference.
  467. if (preferredRole) {
  468. var roleMatches = shaka.util.StreamUtils.filterTextStreamsByRole_(
  469. chosen, preferredRole);
  470. if (roleMatches.length) {
  471. return roleMatches;
  472. } else {
  473. shaka.log.warning('No exact match for the text role could be found.');
  474. }
  475. }
  476. // Either there was no role preference, or it could not be satisfied.
  477. // Choose an arbitrary role, if there are any, and filter out any other roles.
  478. // This ensures we never adapt between roles.
  479. var allRoles = chosen.map(function(stream) {
  480. return stream.roles;
  481. }).reduce(shaka.util.Functional.collapseArrays, []);
  482. if (!allRoles.length) {
  483. return chosen;
  484. }
  485. return shaka.util.StreamUtils.filterTextStreamsByRole_(chosen, allRoles[0]);
  486. };
  487. /**
  488. * Filter Variants by role.
  489. *
  490. * @param {!Array.<shakaExtern.Variant>} variants
  491. * @param {string} preferredRole
  492. * @return {!Array.<shakaExtern.Variant>}
  493. * @private
  494. */
  495. shaka.util.StreamUtils.filterVariantsByRole_ =
  496. function(variants, preferredRole) {
  497. return variants.filter(function(variant) {
  498. return (variant.audio && variant.audio.roles.indexOf(preferredRole) >= 0) ||
  499. (variant.video && variant.video.roles.indexOf(preferredRole) >= 0);
  500. });
  501. };
  502. /**
  503. * Filter text Streams by role.
  504. *
  505. * @param {!Array.<shakaExtern.Stream>} textStreams
  506. * @param {string} preferredRole
  507. * @return {!Array.<shakaExtern.Stream>}
  508. * @private
  509. */
  510. shaka.util.StreamUtils.filterTextStreamsByRole_ =
  511. function(textStreams, preferredRole) {
  512. return textStreams.filter(function(stream) {
  513. return stream.roles.indexOf(preferredRole) >= 0;
  514. });
  515. };
  516. /**
  517. * Finds a Variant with given audio and video streams.
  518. * Returns null if none was found.
  519. *
  520. * @param {?shakaExtern.Stream} audio
  521. * @param {?shakaExtern.Stream} video
  522. * @param {!Array.<!shakaExtern.Variant>} variants
  523. * @return {?shakaExtern.Variant}
  524. */
  525. shaka.util.StreamUtils.getVariantByStreams = function(audio, video, variants) {
  526. for (var i = 0; i < variants.length; i++) {
  527. if (variants[i].audio == audio && variants[i].video == video)
  528. return variants[i];
  529. }
  530. return null;
  531. };
  532. /**
  533. * Finds a Variant with the given video and audio streams, by stream ID.
  534. * Returns null if none were found.
  535. *
  536. * @param {?number} audioId
  537. * @param {?number} videoId
  538. * @param {!Array.<shakaExtern.Variant>} variants
  539. * @return {?shakaExtern.Variant}
  540. */
  541. shaka.util.StreamUtils.getVariantByStreamIds = function(
  542. audioId, videoId, variants) {
  543. function matchesId(id, stream) {
  544. if (id == null)
  545. return stream == null;
  546. else
  547. return stream.id == id;
  548. }
  549. for (var i = 0; i < variants.length; i++) {
  550. if (matchesId(audioId, variants[i].audio) &&
  551. matchesId(videoId, variants[i].video)) {
  552. return variants[i];
  553. }
  554. }
  555. return null;
  556. };
  557. /**
  558. * Gets the index of the Period that contains the given time.
  559. * @param {shakaExtern.Manifest} manifest
  560. * @param {number} time The time in seconds from the start of the presentation.
  561. * @return {number}
  562. */
  563. shaka.util.StreamUtils.findPeriodContainingTime = function(manifest, time) {
  564. var threshold = shaka.util.ManifestParserUtils.GAP_OVERLAP_TOLERANCE_SECONDS;
  565. for (var i = manifest.periods.length - 1; i > 0; --i) {
  566. var period = manifest.periods[i];
  567. // The last segment may end right before the end of the Period because of
  568. // rounding issues.
  569. if (time + threshold >= period.startTime)
  570. return i;
  571. }
  572. return 0;
  573. };
  574. /**
  575. * @param {shakaExtern.Manifest} manifest
  576. * @param {shakaExtern.Stream} stream
  577. * @return {number} The index of the Period which contains |stream|, or -1 if
  578. * no Period contains |stream|.
  579. */
  580. shaka.util.StreamUtils.findPeriodContainingStream = function(manifest, stream) {
  581. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  582. for (var periodIdx = 0; periodIdx < manifest.periods.length; ++periodIdx) {
  583. var period = manifest.periods[periodIdx];
  584. if (stream.type == ContentType.TEXT) {
  585. for (var j = 0; j < period.textStreams.length; ++j) {
  586. var textStream = period.textStreams[j];
  587. if (textStream == stream)
  588. return periodIdx;
  589. }
  590. } else {
  591. for (var j = 0; j < period.variants.length; ++j) {
  592. var variant = period.variants[j];
  593. if (variant.audio == stream || variant.video == stream ||
  594. (variant.video && variant.video.trickModeVideo == stream)) {
  595. return periodIdx;
  596. }
  597. }
  598. }
  599. }
  600. return -1;
  601. };
  602. /**
  603. * @param {shakaExtern.Manifest} manifest
  604. * @param {shakaExtern.Variant} variant
  605. * @return {number} The index of the Period which contains |stream|, or -1 if
  606. * no Period contains |stream|.
  607. */
  608. shaka.util.StreamUtils.findPeriodContainingVariant =
  609. function(manifest, variant) {
  610. for (var periodIdx = 0; periodIdx < manifest.periods.length; ++periodIdx) {
  611. var period = manifest.periods[periodIdx];
  612. for (var j = 0; j < period.variants.length; ++j) {
  613. if (period.variants[j] == variant) {
  614. return periodIdx;
  615. }
  616. }
  617. }
  618. return -1;
  619. };
  620. /**
  621. * Gets the rebuffering goal from the manifest and configuration.
  622. *
  623. * @param {shakaExtern.Manifest} manifest
  624. * @param {shakaExtern.StreamingConfiguration} config
  625. * @param {number} scaleFactor
  626. *
  627. * @return {number}
  628. */
  629. shaka.util.StreamUtils.getRebufferingGoal = function(
  630. manifest, config, scaleFactor) {
  631. return scaleFactor *
  632. Math.max(manifest.minBufferTime || 0, config.rebufferingGoal);
  633. };