Source: lib/util/player_configuration.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.PlayerConfiguration');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.abr.SimpleAbrManager');
  9. goog.require('shaka.config.AutoShowText');
  10. goog.require('shaka.config.CodecSwitchingStrategy');
  11. goog.require('shaka.log');
  12. goog.require('shaka.media.Capabilities');
  13. goog.require('shaka.media.PreferenceBasedCriteria');
  14. goog.require('shaka.net.NetworkingEngine');
  15. goog.require('shaka.util.ConfigUtils');
  16. goog.require('shaka.util.FairPlayUtils');
  17. goog.require('shaka.util.LanguageUtils');
  18. goog.require('shaka.util.ManifestParserUtils');
  19. goog.require('shaka.util.Platform');
  20. /**
  21. * @final
  22. * @export
  23. */
  24. shaka.util.PlayerConfiguration = class {
  25. /**
  26. * @return {shaka.extern.PlayerConfiguration}
  27. * @export
  28. */
  29. static createDefault() {
  30. // This is a relatively safe default in the absence of clues from the
  31. // browser. For slower connections, the default estimate may be too high.
  32. const bandwidthEstimate = 1e6; // 1Mbps
  33. const minBytes = 16e3;
  34. let abrMaxHeight = Infinity;
  35. // Some browsers implement the Network Information API, which allows
  36. // retrieving information about a user's network connection.
  37. if (navigator.connection) {
  38. // If the user has checked a box in the browser to ask it to use less
  39. // data, the browser will expose this intent via connection.saveData.
  40. // When that is true, we will default the max ABR height to 360p. Apps
  41. // can override this if they wish.
  42. //
  43. // The decision to use 360p was somewhat arbitrary. We needed a default
  44. // limit, and rather than restrict to a certain bandwidth, we decided to
  45. // restrict resolution. This will implicitly restrict bandwidth and
  46. // therefore save data. We (Shaka+Chrome) judged that:
  47. // - HD would be inappropriate
  48. // - If a user is asking their browser to save data, 360p it reasonable
  49. // - 360p would not look terrible on small mobile device screen
  50. // We also found that:
  51. // - YouTube's website on mobile defaults to 360p (as of 2018)
  52. // - iPhone 6, in portrait mode, has a physical resolution big enough
  53. // for 360p widescreen, but a little smaller than 480p widescreen
  54. // (https://apple.co/2yze4es)
  55. // If the content's lowest resolution is above 360p, AbrManager will use
  56. // the lowest resolution.
  57. if (navigator.connection.saveData) {
  58. abrMaxHeight = 360;
  59. }
  60. }
  61. const drm = {
  62. retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
  63. // These will all be verified by special cases in mergeConfigObjects_():
  64. servers: {}, // key is arbitrary key system ID, value must be string
  65. clearKeys: {}, // key is arbitrary key system ID, value must be string
  66. advanced: {}, // key is arbitrary key system ID, value is a record type
  67. delayLicenseRequestUntilPlayed: false,
  68. persistentSessionOnlinePlayback: false,
  69. persistentSessionsMetadata: [],
  70. initDataTransform: (initData, initDataType, drmInfo) => {
  71. if (shaka.util.Platform.isMediaKeysPolyfilled('apple') &&
  72. initDataType == 'skd') {
  73. const cert = drmInfo.serverCertificate;
  74. const contentId =
  75. shaka.util.FairPlayUtils.defaultGetContentId(initData);
  76. initData = shaka.util.FairPlayUtils.initDataTransform(
  77. initData, contentId, cert);
  78. }
  79. return initData;
  80. },
  81. logLicenseExchange: false,
  82. updateExpirationTime: 1,
  83. preferredKeySystems: [],
  84. keySystemsMapping: {},
  85. // The Xbox One browser does not detect DRM key changes signalled by a
  86. // change in the PSSH in media segments. We need to parse PSSH from media
  87. // segments to detect key changes.
  88. parseInbandPsshEnabled: shaka.util.Platform.isXboxOne(),
  89. minHdcpVersion: '',
  90. ignoreDuplicateInitData: !shaka.util.Platform.isTizen2(),
  91. defaultAudioRobustnessForWidevine: 'SW_SECURE_CRYPTO',
  92. defaultVideoRobustnessForWidevine: 'SW_SECURE_DECODE',
  93. };
  94. // The Xbox One and PS4 only support the Playready DRM, so they should
  95. // prefer that key system by default to improve startup performance.
  96. if (shaka.util.Platform.isXboxOne() ||
  97. shaka.util.Platform.isPS4()) {
  98. drm.preferredKeySystems.push('com.microsoft.playready');
  99. }
  100. let codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.RELOAD;
  101. let multiTypeVariantsAllowed = false;
  102. if (shaka.media.Capabilities.isChangeTypeSupported() &&
  103. shaka.util.Platform.supportsSmoothCodecSwitching()) {
  104. codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.SMOOTH;
  105. multiTypeVariantsAllowed = true;
  106. }
  107. const manifest = {
  108. retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
  109. availabilityWindowOverride: NaN,
  110. disableAudio: false,
  111. disableVideo: false,
  112. disableText: false,
  113. disableThumbnails: false,
  114. disableIFrames: false,
  115. defaultPresentationDelay: 0,
  116. segmentRelativeVttTiming: false,
  117. raiseFatalErrorOnManifestUpdateRequestFailure: false,
  118. continueLoadingWhenPaused: true,
  119. dash: {
  120. clockSyncUri: '',
  121. ignoreDrmInfo: false,
  122. disableXlinkProcessing: true,
  123. xlinkFailGracefully: false,
  124. ignoreMinBufferTime: false,
  125. autoCorrectDrift: true,
  126. initialSegmentLimit: 1000,
  127. ignoreSuggestedPresentationDelay: false,
  128. ignoreEmptyAdaptationSet: false,
  129. ignoreMaxSegmentDuration: false,
  130. keySystemsByURI: {
  131. 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b':
  132. 'org.w3.clearkey',
  133. 'urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e':
  134. 'org.w3.clearkey',
  135. 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
  136. 'com.widevine.alpha',
  137. 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95':
  138. 'com.microsoft.playready',
  139. 'urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95':
  140. 'com.microsoft.playready',
  141. 'urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2':
  142. 'com.apple.fps',
  143. 'urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c':
  144. 'com.huawei.wiseplay',
  145. },
  146. manifestPreprocessor:
  147. shaka.util.PlayerConfiguration.defaultManifestPreprocessor,
  148. manifestPreprocessorTXml:
  149. shaka.util.PlayerConfiguration.defaultManifestPreprocessorTXml,
  150. sequenceMode: false,
  151. multiTypeVariantsAllowed,
  152. useStreamOnceInPeriodFlattening: false,
  153. updatePeriod: -1,
  154. enableFastSwitching: true,
  155. ignoreSupplementalCodecs: false,
  156. },
  157. hls: {
  158. ignoreTextStreamFailures: false,
  159. ignoreImageStreamFailures: false,
  160. defaultAudioCodec: 'mp4a.40.2',
  161. defaultVideoCodec: 'avc1.42E01E',
  162. ignoreManifestProgramDateTime: false,
  163. ignoreManifestProgramDateTimeForTypes: [],
  164. mediaPlaylistFullMimeType:
  165. 'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"',
  166. liveSegmentsDelay: 3,
  167. sequenceMode: shaka.util.Platform.supportsSequenceMode(),
  168. ignoreManifestTimestampsInSegmentsMode: false,
  169. disableCodecGuessing: false,
  170. disableClosedCaptionsDetection: false,
  171. updatePeriod: -1,
  172. allowLowLatencyByteRangeOptimization: true,
  173. ignoreSupplementalCodecs: false,
  174. },
  175. mss: {
  176. manifestPreprocessor:
  177. shaka.util.PlayerConfiguration.defaultManifestPreprocessor,
  178. manifestPreprocessorTXml:
  179. shaka.util.PlayerConfiguration.defaultManifestPreprocessorTXml,
  180. sequenceMode: false,
  181. keySystemsBySystemId: {
  182. '9a04f079-9840-4286-ab92-e65be0885f95':
  183. 'com.microsoft.playready',
  184. '79f0049a-4098-8642-ab92-e65be0885f95':
  185. 'com.microsoft.playready',
  186. },
  187. },
  188. };
  189. const streaming = {
  190. retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
  191. // Need some operation in the callback or else closure may remove calls
  192. // to the function as it would be a no-op. The operation can't just be a
  193. // log message, because those are stripped in the compiled build.
  194. failureCallback: (error) => {
  195. shaka.log.error('Unhandled streaming error', error);
  196. return shaka.util.ConfigUtils.referenceParametersAndReturn(
  197. [error],
  198. undefined);
  199. },
  200. rebufferingGoal: 0,
  201. bufferingGoal: 10,
  202. bufferBehind: 30,
  203. evictionGoal: 1,
  204. ignoreTextStreamFailures: false,
  205. alwaysStreamText: false,
  206. startAtSegmentBoundary: false,
  207. gapDetectionThreshold: 0.5,
  208. gapPadding: 0.01,
  209. gapJumpTimerTime: 0.25 /* seconds */,
  210. durationBackoff: 1,
  211. // Offset by 5 seconds since Chromecast takes a few seconds to start
  212. // playing after a seek, even when buffered.
  213. safeSeekOffset: 5,
  214. safeSeekEndOffset: 0,
  215. stallEnabled: true,
  216. stallThreshold: 1 /* seconds */,
  217. stallSkip: 0.1 /* seconds */,
  218. useNativeHlsForFairPlay: true,
  219. // If we are within 2 seconds of the start of a live segment, fetch the
  220. // previous one. This allows for segment drift, but won't download an
  221. // extra segment if we aren't close to the start.
  222. // When low latency streaming is enabled, inaccurateManifestTolerance
  223. // will default to 0 if not specified.
  224. inaccurateManifestTolerance: 2,
  225. lowLatencyMode: false,
  226. forceHTTP: false,
  227. forceHTTPS: false,
  228. minBytesForProgressEvents: minBytes,
  229. preferNativeDash: false,
  230. preferNativeHls: false,
  231. updateIntervalSeconds: 1,
  232. observeQualityChanges: false,
  233. maxDisabledTime: 30,
  234. // When low latency streaming is enabled, segmentPrefetchLimit will
  235. // default to 2 if not specified.
  236. segmentPrefetchLimit: 0,
  237. prefetchAudioLanguages: [],
  238. disableAudioPrefetch: false,
  239. disableTextPrefetch: false,
  240. disableVideoPrefetch: false,
  241. liveSync: {
  242. enabled: false,
  243. targetLatency: 0.5,
  244. targetLatencyTolerance: 0.5,
  245. maxPlaybackRate: 1.1,
  246. minPlaybackRate: 0.95,
  247. panicMode: false,
  248. panicThreshold: 60,
  249. dynamicTargetLatency: {
  250. enabled: false,
  251. stabilityThreshold: 60,
  252. rebufferIncrement: 0.5,
  253. maxAttempts: 10,
  254. maxLatency: 4,
  255. minLatency: 1,
  256. },
  257. },
  258. allowMediaSourceRecoveries: true,
  259. minTimeBetweenRecoveries: 5,
  260. vodDynamicPlaybackRate: false,
  261. vodDynamicPlaybackRateLowBufferRate: 0.95,
  262. vodDynamicPlaybackRateBufferRatio: 0.5,
  263. preloadNextUrlWindow: 30,
  264. loadTimeout: 30,
  265. clearDecodingCache: shaka.util.Platform.isPS4() ||
  266. shaka.util.Platform.isPS5(),
  267. dontChooseCodecs: false,
  268. shouldFixTimestampOffset: shaka.util.Platform.isWebOS() ||
  269. shaka.util.Platform.isTizen(),
  270. };
  271. // WebOS, Tizen, Chromecast and Hisense have long hardware pipelines
  272. // that respond slowly to seeking.
  273. // Therefore we should not seek when we detect a stall
  274. // on one of these platforms. Instead, default stallSkip to 0 to force the
  275. // stall detector to pause and play instead.
  276. if (shaka.util.Platform.isWebOS() ||
  277. shaka.util.Platform.isTizen() ||
  278. shaka.util.Platform.isChromecast() ||
  279. shaka.util.Platform.isHisense()) {
  280. streaming.stallSkip = 0;
  281. }
  282. if (shaka.util.Platform.isTizen()) {
  283. streaming.gapPadding = 2;
  284. }
  285. const offline = {
  286. // We need to set this to a throw-away implementation for now as our
  287. // default implementation will need to reference other fields in the
  288. // config. We will set it to our intended implementation after we have
  289. // the top-level object created.
  290. // eslint-disable-next-line require-await
  291. trackSelectionCallback: async (tracks) => tracks,
  292. downloadSizeCallback: async (sizeEstimate) => {
  293. if (navigator.storage && navigator.storage.estimate) {
  294. const estimate = await navigator.storage.estimate();
  295. // Limit to 95% of quota.
  296. return estimate.usage + sizeEstimate < estimate.quota * 0.95;
  297. } else {
  298. return true;
  299. }
  300. },
  301. // Need some operation in the callback or else closure may remove calls
  302. // to the function as it would be a no-op. The operation can't just be a
  303. // log message, because those are stripped in the compiled build.
  304. progressCallback: (content, progress) => {
  305. return shaka.util.ConfigUtils.referenceParametersAndReturn(
  306. [content, progress],
  307. undefined);
  308. },
  309. // By default we use persistent licenses as forces errors to surface if
  310. // a platform does not support offline licenses rather than causing
  311. // unexpected behaviours when someone tries to plays downloaded content
  312. // without a persistent license.
  313. usePersistentLicense: true,
  314. numberOfParallelDownloads: 5,
  315. };
  316. const abr = {
  317. enabled: true,
  318. useNetworkInformation: true,
  319. defaultBandwidthEstimate: bandwidthEstimate,
  320. switchInterval: 8,
  321. bandwidthUpgradeTarget: 0.85,
  322. bandwidthDowngradeTarget: 0.95,
  323. restrictions: {
  324. minWidth: 0,
  325. maxWidth: Infinity,
  326. minHeight: 0,
  327. maxHeight: abrMaxHeight,
  328. minPixels: 0,
  329. maxPixels: Infinity,
  330. minFrameRate: 0,
  331. maxFrameRate: Infinity,
  332. minBandwidth: 0,
  333. maxBandwidth: Infinity,
  334. minChannelsCount: 0,
  335. maxChannelsCount: Infinity,
  336. },
  337. advanced: {
  338. minTotalBytes: 128e3,
  339. minBytes,
  340. fastHalfLife: 2,
  341. slowHalfLife: 5,
  342. },
  343. restrictToElementSize: false,
  344. restrictToScreenSize: false,
  345. ignoreDevicePixelRatio: false,
  346. clearBufferSwitch: false,
  347. safeMarginSwitch: 0,
  348. cacheLoadThreshold: 20,
  349. minTimeToSwitch: shaka.util.Platform.isApple() ? 0.5 : 0,
  350. preferNetworkInformationBandwidth: false,
  351. };
  352. const cmcd = {
  353. enabled: false,
  354. sessionId: '',
  355. contentId: '',
  356. rtpSafetyFactor: 5,
  357. useHeaders: false,
  358. includeKeys: [],
  359. version: 1,
  360. };
  361. const cmsd = {
  362. enabled: true,
  363. applyMaximumSuggestedBitrate: true,
  364. estimatedThroughputWeightRatio: 0.5,
  365. };
  366. const lcevc = {
  367. enabled: false,
  368. dynamicPerformanceScaling: true,
  369. logLevel: 0,
  370. drawLogo: false,
  371. };
  372. const mediaSource = {
  373. codecSwitchingStrategy: codecSwitchingStrategy,
  374. addExtraFeaturesToSourceBuffer: (mimeType) => {
  375. return shaka.util.ConfigUtils.referenceParametersAndReturn(
  376. [mimeType],
  377. '');
  378. },
  379. forceTransmux: false,
  380. insertFakeEncryptionInInit: true,
  381. modifyCueCallback: (cue, uri) => {
  382. return shaka.util.ConfigUtils.referenceParametersAndReturn(
  383. [cue, uri],
  384. undefined);
  385. },
  386. dispatchAllEmsgBoxes: false,
  387. };
  388. let customPlayheadTracker = false;
  389. let skipPlayDetection = false;
  390. let supportsMultipleMediaElements = true;
  391. if (shaka.util.Platform.isSmartTV()) {
  392. customPlayheadTracker = true;
  393. skipPlayDetection = true;
  394. supportsMultipleMediaElements = false;
  395. }
  396. const ads = {
  397. customPlayheadTracker,
  398. skipPlayDetection,
  399. supportsMultipleMediaElements,
  400. disableHLSInterstitial: false,
  401. disableDASHInterstitial: false,
  402. };
  403. const textDisplayer = {
  404. captionsUpdatePeriod: 0.25,
  405. };
  406. const AutoShowText = shaka.config.AutoShowText;
  407. /** @type {shaka.extern.PlayerConfiguration} */
  408. const config = {
  409. drm: drm,
  410. manifest: manifest,
  411. streaming: streaming,
  412. mediaSource: mediaSource,
  413. offline: offline,
  414. abrFactory: () => new shaka.abr.SimpleAbrManager(),
  415. adaptationSetCriteriaFactory:
  416. (...args) => new shaka.media.PreferenceBasedCriteria(...args),
  417. abr: abr,
  418. autoShowText: AutoShowText.IF_SUBTITLES_MAY_BE_NEEDED,
  419. preferredAudioLanguage: '',
  420. preferredAudioLabel: '',
  421. preferredTextLanguage: '',
  422. preferredVariantRole: '',
  423. preferredTextRole: '',
  424. preferredAudioChannelCount: 2,
  425. preferredVideoHdrLevel: 'AUTO',
  426. preferredVideoLayout: '',
  427. preferredVideoLabel: '',
  428. preferredVideoCodecs: [],
  429. preferredAudioCodecs: [],
  430. preferredTextFormats: [],
  431. preferForcedSubs: false,
  432. preferSpatialAudio: false,
  433. preferredDecodingAttributes: [],
  434. restrictions: {
  435. minWidth: 0,
  436. maxWidth: Infinity,
  437. minHeight: 0,
  438. maxHeight: Infinity,
  439. minPixels: 0,
  440. maxPixels: Infinity,
  441. minFrameRate: 0,
  442. maxFrameRate: Infinity,
  443. minBandwidth: 0,
  444. maxBandwidth: Infinity,
  445. minChannelsCount: 0,
  446. maxChannelsCount: Infinity,
  447. },
  448. playRangeStart: 0,
  449. playRangeEnd: Infinity,
  450. textDisplayer: textDisplayer,
  451. textDisplayFactory: () => null,
  452. cmcd: cmcd,
  453. cmsd: cmsd,
  454. lcevc: lcevc,
  455. ads: ads,
  456. ignoreHardwareResolution: false,
  457. };
  458. // Add this callback so that we can reference the preferred audio language
  459. // through the config object so that if it gets updated, we have the
  460. // updated value.
  461. // eslint-disable-next-line require-await
  462. offline.trackSelectionCallback = async (tracks) => {
  463. return shaka.util.PlayerConfiguration.defaultTrackSelect(
  464. tracks, config.preferredAudioLanguage,
  465. config.preferredVideoHdrLevel);
  466. };
  467. return config;
  468. }
  469. /**
  470. * @return {!Object}
  471. * @export
  472. */
  473. static createDefaultForLL() {
  474. return {
  475. streaming: {
  476. inaccurateManifestTolerance: 0,
  477. segmentPrefetchLimit: 2,
  478. updateIntervalSeconds: 0.1,
  479. maxDisabledTime: 1,
  480. retryParameters: {
  481. baseDelay: 100,
  482. },
  483. },
  484. manifest: {
  485. dash: {
  486. autoCorrectDrift: false,
  487. },
  488. retryParameters: {
  489. baseDelay: 100,
  490. },
  491. },
  492. drm: {
  493. retryParameters: {
  494. baseDelay: 100,
  495. },
  496. },
  497. };
  498. }
  499. /**
  500. * Merges the given configuration changes into the given destination. This
  501. * uses the default Player configurations as the template.
  502. *
  503. * @param {shaka.extern.PlayerConfiguration} destination
  504. * @param {!Object} updates
  505. * @param {shaka.extern.PlayerConfiguration=} template
  506. * @return {boolean}
  507. * @export
  508. */
  509. static mergeConfigObjects(destination, updates, template) {
  510. const overrides = {
  511. '.drm.keySystemsMapping': '',
  512. '.drm.servers': '',
  513. '.drm.clearKeys': '',
  514. '.drm.advanced': {
  515. distinctiveIdentifierRequired: false,
  516. persistentStateRequired: false,
  517. videoRobustness: [],
  518. audioRobustness: [],
  519. sessionType: '',
  520. serverCertificate: new Uint8Array(0),
  521. serverCertificateUri: '',
  522. individualizationServer: '',
  523. headers: {},
  524. },
  525. };
  526. return shaka.util.ConfigUtils.mergeConfigObjects(
  527. destination, updates,
  528. template || shaka.util.PlayerConfiguration.createDefault(), overrides,
  529. '');
  530. }
  531. /**
  532. * @param {!Array<shaka.extern.Track>} tracks
  533. * @param {string} preferredAudioLanguage
  534. * @param {string} preferredVideoHdrLevel
  535. * @return {!Array<shaka.extern.Track>}
  536. */
  537. static defaultTrackSelect(
  538. tracks, preferredAudioLanguage, preferredVideoHdrLevel) {
  539. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  540. const LanguageUtils = shaka.util.LanguageUtils;
  541. let hdrLevel = preferredVideoHdrLevel;
  542. if (hdrLevel == 'AUTO') {
  543. // Auto detect the ideal HDR level.
  544. if (window.matchMedia('(color-gamut: p3)').matches) {
  545. const someHLG = tracks.some((track) => {
  546. if (track.hdr && track.hdr == 'HLG') {
  547. return true;
  548. }
  549. return false;
  550. });
  551. hdrLevel = someHLG ? 'HLG' : 'PQ';
  552. } else {
  553. hdrLevel = 'SDR';
  554. }
  555. }
  556. /** @type {!Array<shaka.extern.Track>} */
  557. const allVariants = tracks.filter((track) => {
  558. if (track.type != 'variant') {
  559. return false;
  560. }
  561. if (track.hdr && track.hdr != hdrLevel) {
  562. return false;
  563. }
  564. return true;
  565. });
  566. /** @type {!Array<shaka.extern.Track>} */
  567. let selectedVariants = [];
  568. // Find the locale that best matches our preferred audio locale.
  569. const closestLocale = LanguageUtils.findClosestLocale(
  570. preferredAudioLanguage,
  571. allVariants.map((variant) => variant.language));
  572. // If we found a locale that was close to our preference, then only use
  573. // variants that use that locale.
  574. if (closestLocale) {
  575. selectedVariants = allVariants.filter((variant) => {
  576. const locale = LanguageUtils.normalize(variant.language);
  577. return locale == closestLocale;
  578. });
  579. }
  580. // If we failed to get a language match, go with primary.
  581. if (selectedVariants.length == 0) {
  582. selectedVariants = allVariants.filter((variant) => {
  583. return variant.primary;
  584. });
  585. }
  586. // Otherwise, there is no good way to choose the language, so we don't
  587. // choose a language at all.
  588. if (selectedVariants.length == 0) {
  589. // Issue a warning, but only if the content has multiple languages.
  590. // Otherwise, this warning would just be noise.
  591. const languages = new Set(allVariants.map((track) => {
  592. return track.language;
  593. }));
  594. if (languages.size > 1) {
  595. shaka.log.warning('Could not choose a good audio track based on ' +
  596. 'language preferences or primary tracks. An ' +
  597. 'arbitrary language will be stored!');
  598. }
  599. // Default back to all variants.
  600. selectedVariants = allVariants;
  601. }
  602. // From previously selected variants, choose the SD ones (height <= 480).
  603. const tracksByHeight = selectedVariants.filter((track) => {
  604. return track.height && track.height <= 480;
  605. });
  606. // If variants don't have video or no video with height <= 480 was
  607. // found, proceed with the previously selected tracks.
  608. if (tracksByHeight.length) {
  609. // Sort by resolution, then select all variants which match the height
  610. // of the highest SD res. There may be multiple audio bitrates for the
  611. // same video resolution.
  612. tracksByHeight.sort((a, b) => {
  613. // The items in this list have already been screened for height, but the
  614. // compiler doesn't know that.
  615. goog.asserts.assert(a.height != null, 'Null height');
  616. goog.asserts.assert(b.height != null, 'Null height');
  617. return b.height - a.height;
  618. });
  619. selectedVariants = tracksByHeight.filter((track) => {
  620. return track.height == tracksByHeight[0].height;
  621. });
  622. }
  623. /** @type {!Array<shaka.extern.Track>} */
  624. const selectedTracks = [];
  625. // If there are multiple matches at different audio bitrates, select the
  626. // middle bandwidth one.
  627. if (selectedVariants.length) {
  628. const middleIndex = Math.floor(selectedVariants.length / 2);
  629. selectedVariants.sort((a, b) => a.bandwidth - b.bandwidth);
  630. selectedTracks.push(selectedVariants[middleIndex]);
  631. }
  632. // Since this default callback is used primarily by our own demo app and by
  633. // app developers who haven't thought about which tracks they want, we
  634. // should select all image/text tracks, regardless of language. This makes
  635. // for a better demo for us, and does not rely on user preferences for the
  636. // unconfigured app.
  637. for (const track of tracks) {
  638. if (track.type == ContentType.TEXT || track.type == ContentType.IMAGE) {
  639. selectedTracks.push(track);
  640. }
  641. }
  642. return selectedTracks;
  643. }
  644. /**
  645. * @param {!Element} element
  646. * @return {!Element}
  647. */
  648. static defaultManifestPreprocessor(element) {
  649. return shaka.util.ConfigUtils.referenceParametersAndReturn(
  650. [element],
  651. element);
  652. }
  653. /**
  654. * @param {!shaka.extern.xml.Node} element
  655. * @return {!shaka.extern.xml.Node}
  656. */
  657. static defaultManifestPreprocessorTXml(element) {
  658. return shaka.util.ConfigUtils.referenceParametersAndReturn(
  659. [element],
  660. element);
  661. }
  662. };