Source: lib/offline/manifest_converter.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.offline.ManifestConverter');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.media.InitSegmentReference');
  9. goog.require('shaka.media.PresentationTimeline');
  10. goog.require('shaka.media.SegmentIndex');
  11. goog.require('shaka.media.SegmentReference');
  12. goog.require('shaka.offline.OfflineUri');
  13. goog.require('shaka.util.ManifestParserUtils');
  14. /**
  15. * Utility class for converting database manifest objects back to normal
  16. * player-ready objects. Used by the offline system to convert on-disk
  17. * objects back to the in-memory objects.
  18. */
  19. shaka.offline.ManifestConverter = class {
  20. /**
  21. * Create a new manifest converter. Need to know the mechanism and cell that
  22. * the manifest is from so that all segments paths can be created.
  23. *
  24. * @param {string} mechanism
  25. * @param {string} cell
  26. */
  27. constructor(mechanism, cell) {
  28. /** @private {string} */
  29. this.mechanism_ = mechanism;
  30. /** @private {string} */
  31. this.cell_ = cell;
  32. }
  33. /**
  34. * Convert a |shaka.extern.ManifestDB| object to a |shaka.extern.Manifest|
  35. * object.
  36. *
  37. * @param {shaka.extern.ManifestDB} manifestDB
  38. * @return {shaka.extern.Manifest}
  39. */
  40. fromManifestDB(manifestDB) {
  41. const timeline = new shaka.media.PresentationTimeline(null, 0);
  42. timeline.setDuration(manifestDB.duration);
  43. /** @type {!Array.<shaka.extern.StreamDB>} */
  44. const audioStreams =
  45. manifestDB.streams.filter((streamDB) => this.isAudio_(streamDB));
  46. /** @type {!Array.<shaka.extern.StreamDB>} */
  47. const videoStreams =
  48. manifestDB.streams.filter((streamDB) => this.isVideo_(streamDB));
  49. /** @type {!Map.<number, shaka.extern.Variant>} */
  50. const variants = this.createVariants(audioStreams, videoStreams, timeline);
  51. /** @type {!Array.<shaka.extern.Stream>} */
  52. const textStreams =
  53. manifestDB.streams.filter((streamDB) => this.isText_(streamDB))
  54. .map((streamDB) => this.fromStreamDB_(streamDB, timeline));
  55. /** @type {!Array.<shaka.extern.Stream>} */
  56. const imageStreams =
  57. manifestDB.streams.filter((streamDB) => this.isImage_(streamDB))
  58. .map((streamDB) => this.fromStreamDB_(streamDB, timeline));
  59. const drmInfos = manifestDB.drmInfo ? [manifestDB.drmInfo] : [];
  60. if (manifestDB.drmInfo) {
  61. for (const variant of variants.values()) {
  62. if (variant.audio && variant.audio.encrypted) {
  63. variant.audio.drmInfos = drmInfos;
  64. }
  65. if (variant.video && variant.video.encrypted) {
  66. variant.video.drmInfos = drmInfos;
  67. }
  68. }
  69. }
  70. return {
  71. presentationTimeline: timeline,
  72. minBufferTime: 2,
  73. offlineSessionIds: manifestDB.sessionIds,
  74. variants: Array.from(variants.values()),
  75. textStreams: textStreams,
  76. imageStreams: imageStreams,
  77. };
  78. }
  79. /**
  80. * Recreates Variants from audio and video StreamDB collections.
  81. *
  82. * @param {!Array.<!shaka.extern.StreamDB>} audios
  83. * @param {!Array.<!shaka.extern.StreamDB>} videos
  84. * @param {shaka.media.PresentationTimeline} timeline
  85. * @return {!Map.<number, !shaka.extern.Variant>}
  86. */
  87. createVariants(audios, videos, timeline) {
  88. // Get all the variant ids from all audio and video streams.
  89. /** @type {!Set.<number>} */
  90. const variantIds = new Set();
  91. for (const streamDB of audios) {
  92. for (const id of streamDB.variantIds) {
  93. variantIds.add(id);
  94. }
  95. }
  96. for (const streamDB of videos) {
  97. for (const id of streamDB.variantIds) {
  98. variantIds.add(id);
  99. }
  100. }
  101. /** @type {!Map.<number, shaka.extern.Variant>} */
  102. const variantMap = new Map();
  103. for (const id of variantIds) {
  104. variantMap.set(id, this.createEmptyVariant_(id));
  105. }
  106. // Assign each audio stream to its variants.
  107. for (const audio of audios) {
  108. /** @type {shaka.extern.Stream} */
  109. const stream = this.fromStreamDB_(audio, timeline);
  110. for (const variantId of audio.variantIds) {
  111. const variant = variantMap.get(variantId);
  112. goog.asserts.assert(
  113. !variant.audio, 'A variant should only have one audio stream');
  114. variant.language = stream.language;
  115. variant.primary = variant.primary || stream.primary;
  116. variant.audio = stream;
  117. }
  118. }
  119. // Assign each video stream to its variants.
  120. for (const video of videos) {
  121. /** @type {shaka.extern.Stream} */
  122. const stream = this.fromStreamDB_(video, timeline);
  123. for (const variantId of video.variantIds) {
  124. const variant = variantMap.get(variantId);
  125. goog.asserts.assert(
  126. !variant.video, 'A variant should only have one video stream');
  127. variant.primary = variant.primary || stream.primary;
  128. variant.video = stream;
  129. }
  130. }
  131. return variantMap;
  132. }
  133. /**
  134. * @param {shaka.extern.StreamDB} streamDB
  135. * @param {shaka.media.PresentationTimeline} timeline
  136. * @return {shaka.extern.Stream}
  137. * @private
  138. */
  139. fromStreamDB_(streamDB, timeline) {
  140. /** @type {!Array.<!shaka.media.SegmentReference>} */
  141. const segments = streamDB.segments.map(
  142. (segment, index) => this.fromSegmentDB_(index, segment));
  143. timeline.notifySegments(segments);
  144. /** @type {!shaka.media.SegmentIndex} */
  145. const segmentIndex = new shaka.media.SegmentIndex(segments);
  146. /** @type {shaka.extern.Stream} */
  147. const stream = {
  148. id: streamDB.id,
  149. originalId: streamDB.originalId,
  150. createSegmentIndex: () => Promise.resolve(),
  151. segmentIndex,
  152. mimeType: streamDB.mimeType,
  153. codecs: streamDB.codecs,
  154. width: streamDB.width || undefined,
  155. height: streamDB.height || undefined,
  156. frameRate: streamDB.frameRate,
  157. pixelAspectRatio: streamDB.pixelAspectRatio,
  158. hdr: streamDB.hdr,
  159. kind: streamDB.kind,
  160. encrypted: streamDB.encrypted,
  161. drmInfos: [],
  162. keyIds: streamDB.keyIds,
  163. language: streamDB.language,
  164. label: streamDB.label,
  165. type: streamDB.type,
  166. primary: streamDB.primary,
  167. trickModeVideo: null,
  168. emsgSchemeIdUris: null,
  169. roles: streamDB.roles,
  170. forced: streamDB.forced,
  171. channelsCount: streamDB.channelsCount,
  172. audioSamplingRate: streamDB.audioSamplingRate,
  173. spatialAudio: streamDB.spatialAudio,
  174. closedCaptions: streamDB.closedCaptions,
  175. tilesLayout: streamDB.tilesLayout,
  176. };
  177. return stream;
  178. }
  179. /**
  180. * @param {number} index
  181. * @param {shaka.extern.SegmentDB} segmentDB
  182. * @return {!shaka.media.SegmentReference}
  183. * @private
  184. */
  185. fromSegmentDB_(index, segmentDB) {
  186. /** @type {!shaka.offline.OfflineUri} */
  187. const uri = shaka.offline.OfflineUri.segment(
  188. this.mechanism_, this.cell_, segmentDB.dataKey);
  189. const initSegmentReference = segmentDB.initSegmentKey != null ?
  190. this.fromInitSegmentDB_(segmentDB.initSegmentKey) : null;
  191. return new shaka.media.SegmentReference(
  192. segmentDB.startTime,
  193. segmentDB.endTime,
  194. () => [uri.toString()],
  195. /* startByte= */ 0,
  196. /* endByte= */ null,
  197. initSegmentReference,
  198. segmentDB.timestampOffset,
  199. segmentDB.appendWindowStart,
  200. segmentDB.appendWindowEnd);
  201. }
  202. /**
  203. * @param {number} key
  204. * @return {!shaka.media.InitSegmentReference}
  205. * @private
  206. */
  207. fromInitSegmentDB_(key) {
  208. /** @type {!shaka.offline.OfflineUri} */
  209. const uri = shaka.offline.OfflineUri.segment(
  210. this.mechanism_, this.cell_, key);
  211. return new shaka.media.InitSegmentReference(
  212. () => [uri.toString()],
  213. /* startBytes= */ 0,
  214. /* endBytes= */ null );
  215. }
  216. /**
  217. * @param {shaka.extern.StreamDB} streamDB
  218. * @return {boolean}
  219. * @private
  220. */
  221. isAudio_(streamDB) {
  222. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  223. return streamDB.type == ContentType.AUDIO;
  224. }
  225. /**
  226. * @param {shaka.extern.StreamDB} streamDB
  227. * @return {boolean}
  228. * @private
  229. */
  230. isVideo_(streamDB) {
  231. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  232. return streamDB.type == ContentType.VIDEO;
  233. }
  234. /**
  235. * @param {shaka.extern.StreamDB} streamDB
  236. * @return {boolean}
  237. * @private
  238. */
  239. isText_(streamDB) {
  240. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  241. return streamDB.type == ContentType.TEXT;
  242. }
  243. /**
  244. * @param {shaka.extern.StreamDB} streamDB
  245. * @return {boolean}
  246. * @private
  247. */
  248. isImage_(streamDB) {
  249. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  250. return streamDB.type == ContentType.IMAGE;
  251. }
  252. /**
  253. * Creates an empty Variant.
  254. *
  255. * @param {number} id
  256. * @return {!shaka.extern.Variant}
  257. * @private
  258. */
  259. createEmptyVariant_(id) {
  260. return {
  261. id: id,
  262. language: '',
  263. primary: false,
  264. audio: null,
  265. video: null,
  266. bandwidth: 0,
  267. allowedByApplication: true,
  268. allowedByKeySystem: true,
  269. decodingInfos: [],
  270. };
  271. }
  272. };