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