Source: lib/util/cmcd_manager.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.CmcdManager');
  7. goog.require('goog.Uri');
  8. goog.require('shaka.log');
  9. goog.require('shaka.net.NetworkingEngine');
  10. goog.require('shaka.util.ArrayUtils');
  11. goog.requireType('shaka.media.SegmentReference');
  12. /**
  13. * @summary
  14. * A CmcdManager maintains CMCD state as well as a collection of utility
  15. * functions.
  16. */
  17. shaka.util.CmcdManager = class {
  18. /**
  19. * @param {shaka.util.CmcdManager.PlayerInterface} playerInterface
  20. * @param {shaka.extern.CmcdConfiguration} config
  21. */
  22. constructor(playerInterface, config) {
  23. /** @private {shaka.util.CmcdManager.PlayerInterface} */
  24. this.playerInterface_ = playerInterface;
  25. /** @private {?shaka.extern.CmcdConfiguration} */
  26. this.config_ = config;
  27. /**
  28. * Streaming format
  29. *
  30. * @private {(shaka.util.CmcdManager.StreamingFormat|undefined)}
  31. */
  32. this.sf_ = undefined;
  33. /**
  34. * @private {boolean}
  35. */
  36. this.playbackStarted_ = false;
  37. /**
  38. * @private {boolean}
  39. */
  40. this.buffering_ = true;
  41. /**
  42. * @private {boolean}
  43. */
  44. this.starved_ = false;
  45. }
  46. /**
  47. * Called by the Player to provide an updated configuration any time it
  48. * changes.
  49. *
  50. * @param {shaka.extern.CmcdConfiguration} config
  51. */
  52. configure(config) {
  53. this.config_ = config;
  54. }
  55. /**
  56. * Set the buffering state
  57. *
  58. * @param {boolean} buffering
  59. */
  60. setBuffering(buffering) {
  61. if (!buffering && !this.playbackStarted_) {
  62. this.playbackStarted_ = true;
  63. }
  64. if (this.playbackStarted_ && buffering) {
  65. this.starved_ = true;
  66. }
  67. this.buffering_ = buffering;
  68. }
  69. /**
  70. * Apply CMCD data to a request.
  71. *
  72. * @param {!shaka.net.NetworkingEngine.RequestType} type
  73. * The request type
  74. * @param {!shaka.extern.Request} request
  75. * The request to apply CMCD data to
  76. * @param {shaka.extern.RequestContext=} context
  77. * The request context
  78. */
  79. applyData(type, request, context = {}) {
  80. if (!this.config_.enabled) {
  81. return;
  82. }
  83. if (request.method === 'HEAD') {
  84. this.apply_(request);
  85. return;
  86. }
  87. const RequestType = shaka.net.NetworkingEngine.RequestType;
  88. const ObjectType = shaka.util.CmcdManager.ObjectType;
  89. switch (type) {
  90. case RequestType.MANIFEST:
  91. this.applyManifestData(request, context);
  92. break;
  93. case RequestType.SEGMENT:
  94. this.applySegmentData(request, context);
  95. break;
  96. case RequestType.LICENSE:
  97. case RequestType.SERVER_CERTIFICATE:
  98. case RequestType.KEY:
  99. this.apply_(request, {ot: ObjectType.KEY});
  100. break;
  101. case RequestType.TIMING:
  102. this.apply_(request, {ot: ObjectType.OTHER});
  103. break;
  104. }
  105. }
  106. /**
  107. * Apply CMCD data to a manifest request.
  108. *
  109. * @param {!shaka.extern.Request} request
  110. * The request to apply CMCD data to
  111. * @param {shaka.extern.RequestContext} context
  112. * The request context
  113. */
  114. applyManifestData(request, context) {
  115. try {
  116. if (!this.config_.enabled) {
  117. return;
  118. }
  119. if (context.type) {
  120. this.sf_ = this.getStreamFormat_(context.type);
  121. }
  122. this.apply_(request, {
  123. ot: shaka.util.CmcdManager.ObjectType.MANIFEST,
  124. su: !this.playbackStarted_,
  125. });
  126. } catch (error) {
  127. shaka.log.warnOnce('CMCD_MANIFEST_ERROR',
  128. 'Could not generate manifest CMCD data.', error);
  129. }
  130. }
  131. /**
  132. * Apply CMCD data to a segment request
  133. *
  134. * @param {!shaka.extern.Request} request
  135. * @param {shaka.extern.RequestContext} context
  136. * The request context
  137. */
  138. applySegmentData(request, context) {
  139. try {
  140. if (!this.config_.enabled) {
  141. return;
  142. }
  143. const segment = context.segment;
  144. let duration = 0;
  145. if (segment) {
  146. duration = segment.endTime - segment.startTime;
  147. }
  148. const data = {
  149. d: duration * 1000,
  150. st: this.getStreamType_(),
  151. };
  152. data.ot = this.getObjectType_(context);
  153. const ObjectType = shaka.util.CmcdManager.ObjectType;
  154. const isMedia = data.ot === ObjectType.VIDEO ||
  155. data.ot === ObjectType.AUDIO ||
  156. data.ot === ObjectType.MUXED ||
  157. data.ot === ObjectType.TIMED_TEXT;
  158. const stream = context.stream;
  159. if (stream) {
  160. const playbackRate = this.playerInterface_.getPlaybackRate();
  161. if (isMedia) {
  162. data.bl = this.getBufferLength_(stream.type);
  163. if (data.ot !== ObjectType.TIMED_TEXT) {
  164. const remainingBufferLength =
  165. this.getRemainingBufferLength_(stream.type);
  166. if (playbackRate) {
  167. data.dl = remainingBufferLength / Math.abs(playbackRate);
  168. } else {
  169. data.dl = remainingBufferLength;
  170. }
  171. }
  172. }
  173. if (stream.bandwidth) {
  174. data.br = stream.bandwidth / 1000;
  175. }
  176. if (stream.segmentIndex && segment) {
  177. const reverse = playbackRate < 0;
  178. const iterator = stream.segmentIndex.getIteratorForTime(
  179. segment.endTime, /* allowNonIndepedent= */ true, reverse);
  180. if (iterator) {
  181. const nextSegment = iterator.next().value;
  182. if (nextSegment && nextSegment != segment) {
  183. if (!shaka.util.ArrayUtils.equal(
  184. segment.getUris(), nextSegment.getUris())) {
  185. data.nor = this.urlToRelativePath_(
  186. nextSegment.getUris()[0], request.uris[0]);
  187. }
  188. if ((nextSegment.startByte || nextSegment.endByte) &&
  189. (segment.startByte != nextSegment.startByte ||
  190. segment.endByte != nextSegment.endByte)) {
  191. let range = nextSegment.startByte + '-';
  192. if (nextSegment.endByte) {
  193. range += nextSegment.endByte;
  194. }
  195. data.nrr = range;
  196. }
  197. }
  198. }
  199. const rtp = this.calculateRtp_(stream, segment);
  200. if (!isNaN(rtp)) {
  201. data.rtp = rtp;
  202. }
  203. }
  204. }
  205. if (isMedia && data.ot !== ObjectType.TIMED_TEXT) {
  206. data.tb = this.getTopBandwidth_(data.ot) / 1000;
  207. }
  208. this.apply_(request, data);
  209. } catch (error) {
  210. shaka.log.warnOnce('CMCD_SEGMENT_ERROR',
  211. 'Could not generate segment CMCD data.', error);
  212. }
  213. }
  214. /**
  215. * Apply CMCD data to a text request
  216. *
  217. * @param {!shaka.extern.Request} request
  218. */
  219. applyTextData(request) {
  220. try {
  221. if (!this.config_.enabled) {
  222. return;
  223. }
  224. this.apply_(request, {
  225. ot: shaka.util.CmcdManager.ObjectType.CAPTION,
  226. su: true,
  227. });
  228. } catch (error) {
  229. shaka.log.warnOnce('CMCD_TEXT_ERROR',
  230. 'Could not generate text CMCD data.', error);
  231. }
  232. }
  233. /**
  234. * Apply CMCD data to streams loaded via src=.
  235. *
  236. * @param {string} uri
  237. * @param {string} mimeType
  238. * @return {string}
  239. */
  240. appendSrcData(uri, mimeType) {
  241. try {
  242. if (!this.config_.enabled) {
  243. return uri;
  244. }
  245. const data = this.createData_();
  246. data.ot = this.getObjectTypeFromMimeType_(mimeType);
  247. data.su = true;
  248. const query = shaka.util.CmcdManager.toQuery(data);
  249. return shaka.util.CmcdManager.appendQueryToUri(uri, query);
  250. } catch (error) {
  251. shaka.log.warnOnce('CMCD_SRC_ERROR',
  252. 'Could not generate src CMCD data.', error);
  253. return uri;
  254. }
  255. }
  256. /**
  257. * Apply CMCD data to side car text track uri.
  258. *
  259. * @param {string} uri
  260. * @return {string}
  261. */
  262. appendTextTrackData(uri) {
  263. try {
  264. if (!this.config_.enabled) {
  265. return uri;
  266. }
  267. const data = this.createData_();
  268. data.ot = shaka.util.CmcdManager.ObjectType.CAPTION;
  269. data.su = true;
  270. const query = shaka.util.CmcdManager.toQuery(data);
  271. return shaka.util.CmcdManager.appendQueryToUri(uri, query);
  272. } catch (error) {
  273. shaka.log.warnOnce('CMCD_TEXT_TRACK_ERROR',
  274. 'Could not generate text track CMCD data.', error);
  275. return uri;
  276. }
  277. }
  278. /**
  279. * Create baseline CMCD data
  280. *
  281. * @return {CmcdData}
  282. * @private
  283. */
  284. createData_() {
  285. if (!this.config_.sessionId) {
  286. this.config_.sessionId = window.crypto.randomUUID();
  287. }
  288. return {
  289. v: shaka.util.CmcdManager.Version,
  290. sf: this.sf_,
  291. sid: this.config_.sessionId,
  292. cid: this.config_.contentId,
  293. mtp: this.playerInterface_.getBandwidthEstimate() / 1000,
  294. };
  295. }
  296. /**
  297. * Apply CMCD data to a request.
  298. *
  299. * @param {!shaka.extern.Request} request The request to apply CMCD data to
  300. * @param {!CmcdData} data The data object
  301. * @param {boolean} useHeaders Send data via request headers
  302. * @private
  303. */
  304. apply_(request, data = {}, useHeaders = this.config_.useHeaders) {
  305. if (!this.config_.enabled) {
  306. return;
  307. }
  308. // apply baseline data
  309. Object.assign(data, this.createData_());
  310. data.pr = this.playerInterface_.getPlaybackRate();
  311. const isVideo = data.ot === shaka.util.CmcdManager.ObjectType.VIDEO ||
  312. data.ot === shaka.util.CmcdManager.ObjectType.MUXED;
  313. if (this.starved_ && isVideo) {
  314. data.bs = true;
  315. data.su = true;
  316. this.starved_ = false;
  317. }
  318. if (data.su == null) {
  319. data.su = this.buffering_;
  320. }
  321. const output = this.filterKeys_(data);
  322. if (useHeaders) {
  323. const headers = shaka.util.CmcdManager.toHeaders(output);
  324. if (!Object.keys(headers).length) {
  325. return;
  326. }
  327. Object.assign(request.headers, headers);
  328. } else {
  329. const query = shaka.util.CmcdManager.toQuery(output);
  330. if (!query) {
  331. return;
  332. }
  333. request.uris = request.uris.map((uri) => {
  334. return shaka.util.CmcdManager.appendQueryToUri(uri, query);
  335. });
  336. }
  337. }
  338. /**
  339. * Filter the CMCD data object to include only the keys specified in the
  340. * configuration.
  341. *
  342. * @param {CmcdData} data
  343. * @return {CmcdData}
  344. * @private
  345. */
  346. filterKeys_(data) {
  347. const includeKeys = this.config_.includeKeys;
  348. if (!includeKeys.length) {
  349. return data;
  350. }
  351. return Object.keys(data).reduce((acc, key) => {
  352. if (includeKeys.includes(key)) {
  353. acc[key] = data[key];
  354. }
  355. return acc;
  356. }, {});
  357. }
  358. /**
  359. * The CMCD object type.
  360. *
  361. * @param {shaka.extern.RequestContext} context
  362. * The request context
  363. * @private
  364. */
  365. getObjectType_(context) {
  366. if (context.type ===
  367. shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT) {
  368. return shaka.util.CmcdManager.ObjectType.INIT;
  369. }
  370. const stream = context.stream;
  371. if (!stream) {
  372. return undefined;
  373. }
  374. const type = stream.type;
  375. if (type == 'video') {
  376. if (stream.codecs && stream.codecs.includes(',')) {
  377. return shaka.util.CmcdManager.ObjectType.MUXED;
  378. }
  379. return shaka.util.CmcdManager.ObjectType.VIDEO;
  380. }
  381. if (type == 'audio') {
  382. return shaka.util.CmcdManager.ObjectType.AUDIO;
  383. }
  384. if (type == 'text') {
  385. if (stream.mimeType === 'application/mp4') {
  386. return shaka.util.CmcdManager.ObjectType.TIMED_TEXT;
  387. }
  388. return shaka.util.CmcdManager.ObjectType.CAPTION;
  389. }
  390. return undefined;
  391. }
  392. /**
  393. * The CMCD object type from mimeType.
  394. *
  395. * @param {!string} mimeType
  396. * @return {(shaka.util.CmcdManager.ObjectType|undefined)}
  397. * @private
  398. */
  399. getObjectTypeFromMimeType_(mimeType) {
  400. switch (mimeType.toLowerCase()) {
  401. case 'audio/mp4':
  402. case 'audio/webm':
  403. case 'audio/ogg':
  404. case 'audio/mpeg':
  405. case 'audio/aac':
  406. case 'audio/flac':
  407. case 'audio/wav':
  408. return shaka.util.CmcdManager.ObjectType.AUDIO;
  409. case 'video/webm':
  410. case 'video/mp4':
  411. case 'video/mpeg':
  412. case 'video/mp2t':
  413. return shaka.util.CmcdManager.ObjectType.MUXED;
  414. case 'application/x-mpegurl':
  415. case 'application/vnd.apple.mpegurl':
  416. case 'application/dash+xml':
  417. case 'video/vnd.mpeg.dash.mpd':
  418. case 'application/vnd.ms-sstr+xml':
  419. return shaka.util.CmcdManager.ObjectType.MANIFEST;
  420. default:
  421. return undefined;
  422. }
  423. }
  424. /**
  425. * Get the buffer length for a media type in milliseconds
  426. *
  427. * @param {string} type
  428. * @return {number}
  429. * @private
  430. */
  431. getBufferLength_(type) {
  432. const ranges = this.playerInterface_.getBufferedInfo()[type];
  433. if (!ranges.length) {
  434. return NaN;
  435. }
  436. const start = this.playerInterface_.getCurrentTime();
  437. const range = ranges.find((r) => r.start <= start && r.end >= start);
  438. if (!range) {
  439. return NaN;
  440. }
  441. return (range.end - start) * 1000;
  442. }
  443. /**
  444. * Get the remaining buffer length for a media type in milliseconds
  445. *
  446. * @param {string} type
  447. * @return {number}
  448. * @private
  449. */
  450. getRemainingBufferLength_(type) {
  451. const ranges = this.playerInterface_.getBufferedInfo()[type];
  452. if (!ranges.length) {
  453. return 0;
  454. }
  455. const start = this.playerInterface_.getCurrentTime();
  456. const range = ranges.find((r) => r.start <= start && r.end >= start);
  457. if (!range) {
  458. return 0;
  459. }
  460. return (range.end - start) * 1000;
  461. }
  462. /**
  463. * Constructs a relative path from a URL
  464. *
  465. * @param {string} url
  466. * @param {string} base
  467. * @return {string}
  468. * @private
  469. */
  470. urlToRelativePath_(url, base) {
  471. const to = new URL(url);
  472. const from = new URL(base);
  473. if (to.origin !== from.origin) {
  474. return url;
  475. }
  476. const toPath = to.pathname.split('/').slice(1);
  477. const fromPath = from.pathname.split('/').slice(1, -1);
  478. // remove common parents
  479. while (toPath[0] === fromPath[0]) {
  480. toPath.shift();
  481. fromPath.shift();
  482. }
  483. // add back paths
  484. while (fromPath.length) {
  485. fromPath.shift();
  486. toPath.unshift('..');
  487. }
  488. return toPath.join('/');
  489. }
  490. /**
  491. * Calculate requested maximun throughput
  492. *
  493. * @param {shaka.extern.Stream} stream
  494. * @param {shaka.media.SegmentReference} segment
  495. * @return {number}
  496. * @private
  497. */
  498. calculateRtp_(stream, segment) {
  499. const playbackRate = this.playerInterface_.getPlaybackRate() || 1;
  500. const currentBufferLevel =
  501. this.getRemainingBufferLength_(stream.type) || 500;
  502. const bandwidth = stream.bandwidth;
  503. if (!bandwidth) {
  504. return NaN;
  505. }
  506. const segmentDuration = segment.endTime - segment.startTime;
  507. // Calculate file size in kilobits
  508. const segmentSize = bandwidth * segmentDuration / 1000;
  509. // Calculate time available to load file in seconds
  510. const timeToLoad = (currentBufferLevel / playbackRate) / 1000;
  511. // Calculate the exact bandwidth required
  512. const minBandwidth = segmentSize / timeToLoad;
  513. // Include a safety buffer
  514. return minBandwidth * this.config_.rtpSafetyFactor;
  515. }
  516. /**
  517. * Get the stream format
  518. *
  519. * @param {shaka.net.NetworkingEngine.AdvancedRequestType} type
  520. * The request's advanced type
  521. * @return {(shaka.util.CmcdManager.StreamingFormat|undefined)}
  522. * @private
  523. */
  524. getStreamFormat_(type) {
  525. const AdvancedRequestType = shaka.net.NetworkingEngine.AdvancedRequestType;
  526. switch (type) {
  527. case AdvancedRequestType.MPD:
  528. return shaka.util.CmcdManager.StreamingFormat.DASH;
  529. case AdvancedRequestType.MASTER_PLAYLIST:
  530. case AdvancedRequestType.MEDIA_PLAYLIST:
  531. return shaka.util.CmcdManager.StreamingFormat.HLS;
  532. case AdvancedRequestType.MSS:
  533. return shaka.util.CmcdManager.StreamingFormat.SMOOTH;
  534. }
  535. return undefined;
  536. }
  537. /**
  538. * Get the stream type
  539. *
  540. * @return {shaka.util.CmcdManager.StreamType}
  541. * @private
  542. */
  543. getStreamType_() {
  544. const isLive = this.playerInterface_.isLive();
  545. if (isLive) {
  546. return shaka.util.CmcdManager.StreamType.LIVE;
  547. } else {
  548. return shaka.util.CmcdManager.StreamType.VOD;
  549. }
  550. }
  551. /**
  552. * Get the highest bandwidth for a given type.
  553. *
  554. * @param {string} type
  555. * @return {number}
  556. * @private
  557. */
  558. getTopBandwidth_(type) {
  559. const variants = this.playerInterface_.getVariantTracks();
  560. if (!variants.length) {
  561. return NaN;
  562. }
  563. let top = variants[0];
  564. for (const variant of variants) {
  565. if (variant.type === 'variant' && variant.bandwidth > top.bandwidth) {
  566. top = variant;
  567. }
  568. }
  569. const ObjectType = shaka.util.CmcdManager.ObjectType;
  570. switch (type) {
  571. case ObjectType.VIDEO:
  572. return top.videoBandwidth || NaN;
  573. case ObjectType.AUDIO:
  574. return top.audioBandwidth || NaN;
  575. default:
  576. return top.bandwidth;
  577. }
  578. }
  579. /**
  580. * Serialize a CMCD data object according to the rules defined in the
  581. * section 3.2 of
  582. * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
  583. *
  584. * @param {CmcdData} data The CMCD data object
  585. * @return {string}
  586. */
  587. static serialize(data) {
  588. const results = [];
  589. const isValid = (value) =>
  590. !Number.isNaN(value) && value != null && value !== '' && value !== false;
  591. const toRounded = (value) => Math.round(value);
  592. const toHundred = (value) => toRounded(value / 100) * 100;
  593. const toUrlSafe = (value) => encodeURIComponent(value);
  594. const formatters = {
  595. br: toRounded,
  596. d: toRounded,
  597. bl: toHundred,
  598. dl: toHundred,
  599. mtp: toHundred,
  600. nor: toUrlSafe,
  601. rtp: toHundred,
  602. tb: toRounded,
  603. };
  604. const keys = Object.keys(data || {}).sort();
  605. for (const key of keys) {
  606. let value = data[key];
  607. // ignore invalid values
  608. if (!isValid(value)) {
  609. continue;
  610. }
  611. // Version should only be reported if not equal to 1.
  612. if (key === 'v' && value === 1) {
  613. continue;
  614. }
  615. // Playback rate should only be sent if not equal to 1.
  616. if (key == 'pr' && value === 1) {
  617. continue;
  618. }
  619. // Certain values require special formatting
  620. const formatter = formatters[key];
  621. if (formatter) {
  622. value = formatter(value);
  623. }
  624. // Serialize the key/value pair
  625. const type = typeof value;
  626. let result;
  627. if (type === 'string' && key !== 'ot' && key !== 'sf' && key !== 'st') {
  628. result = `${key}=${JSON.stringify(value)}`;
  629. } else if (type === 'boolean') {
  630. result = key;
  631. } else if (type === 'symbol') {
  632. result = `${key}=${value.description}`;
  633. } else {
  634. result = `${key}=${value}`;
  635. }
  636. results.push(result);
  637. }
  638. return results.join(',');
  639. }
  640. /**
  641. * Convert a CMCD data object to request headers according to the rules
  642. * defined in the section 2.1 and 3.2 of
  643. * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
  644. *
  645. * @param {CmcdData} data The CMCD data object
  646. * @return {!Object}
  647. */
  648. static toHeaders(data) {
  649. const keys = Object.keys(data);
  650. const headers = {};
  651. const headerNames = ['Object', 'Request', 'Session', 'Status'];
  652. const headerGroups = [{}, {}, {}, {}];
  653. const headerMap = {
  654. br: 0, d: 0, ot: 0, tb: 0,
  655. bl: 1, dl: 1, mtp: 1, nor: 1, nrr: 1, su: 1,
  656. cid: 2, pr: 2, sf: 2, sid: 2, st: 2, v: 2,
  657. bs: 3, rtp: 3,
  658. };
  659. for (const key of keys) {
  660. // Unmapped fields are mapped to the Request header
  661. const index = (headerMap[key] != null) ? headerMap[key] : 1;
  662. headerGroups[index][key] = data[key];
  663. }
  664. for (let i = 0; i < headerGroups.length; i++) {
  665. const value = shaka.util.CmcdManager.serialize(headerGroups[i]);
  666. if (value) {
  667. headers[`CMCD-${headerNames[i]}`] = value;
  668. }
  669. }
  670. return headers;
  671. }
  672. /**
  673. * Convert a CMCD data object to query args according to the rules
  674. * defined in the section 2.2 and 3.2 of
  675. * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf).
  676. *
  677. * @param {CmcdData} data The CMCD data object
  678. * @return {string}
  679. */
  680. static toQuery(data) {
  681. return shaka.util.CmcdManager.serialize(data);
  682. }
  683. /**
  684. * Append query args to a uri.
  685. *
  686. * @param {string} uri
  687. * @param {string} query
  688. * @return {string}
  689. */
  690. static appendQueryToUri(uri, query) {
  691. if (!query) {
  692. return uri;
  693. }
  694. if (uri.includes('offline:')) {
  695. return uri;
  696. }
  697. const url = new goog.Uri(uri);
  698. url.getQueryData().set('CMCD', query);
  699. return url.toString();
  700. }
  701. };
  702. /**
  703. * @typedef {{
  704. * getBandwidthEstimate: function():number,
  705. * getBufferedInfo: function():shaka.extern.BufferedInfo,
  706. * getCurrentTime: function():number,
  707. * getPlaybackRate: function():number,
  708. * getVariantTracks: function():Array.<shaka.extern.Track>,
  709. * isLive: function():boolean
  710. * }}
  711. *
  712. * @property {function():number} getBandwidthEstimate
  713. * Get the estimated bandwidth in bits per second.
  714. * @property {function():shaka.extern.BufferedInfo} getBufferedInfo
  715. * Get information about what the player has buffered.
  716. * @property {function():number} getCurrentTime
  717. * Get the current time
  718. * @property {function():number} getPlaybackRate
  719. * Get the playback rate
  720. * @property {function():Array.<shaka.extern.Track>} getVariantTracks
  721. * Get the variant tracks
  722. * @property {function():boolean} isLive
  723. * Get if the player is playing live content.
  724. */
  725. shaka.util.CmcdManager.PlayerInterface;
  726. /**
  727. * @enum {string}
  728. */
  729. shaka.util.CmcdManager.ObjectType = {
  730. MANIFEST: 'm',
  731. AUDIO: 'a',
  732. VIDEO: 'v',
  733. MUXED: 'av',
  734. INIT: 'i',
  735. CAPTION: 'c',
  736. TIMED_TEXT: 'tt',
  737. KEY: 'k',
  738. OTHER: 'o',
  739. };
  740. /**
  741. * @enum {string}
  742. */
  743. shaka.util.CmcdManager.StreamType = {
  744. VOD: 'v',
  745. LIVE: 'l',
  746. };
  747. /**
  748. * @enum {string}
  749. * @export
  750. */
  751. shaka.util.CmcdManager.StreamingFormat = {
  752. DASH: 'd',
  753. HLS: 'h',
  754. SMOOTH: 's',
  755. OTHER: 'o',
  756. };
  757. /**
  758. * The CMCD spec version
  759. * @const {number}
  760. */
  761. shaka.util.CmcdManager.Version = 1;