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