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. offlineSessionIds: manifestDB.sessionIds,
  75. variants: Array.from(variants.values()),
  76. textStreams: textStreams,
  77. imageStreams: imageStreams,
  78. sequenceMode: manifestDB.sequenceMode || false,
  79. ignoreManifestTimestampsInSegmentsMode: false,
  80. type: manifestDB.type || shaka.media.ManifestParser.UNKNOWN,
  81. serviceDescription: null,
  82. nextUrl: null,
  83. periodCount: 1,
  84. gapCount: 0,
  85. isLowLatency: false,
  86. startTime: null,
  87. };
  88. }
  89. /**
  90. * Recreates Variants from audio and video StreamDB collections.
  91. *
  92. * @param {!Array<!shaka.extern.StreamDB>} audios
  93. * @param {!Array<!shaka.extern.StreamDB>} videos
  94. * @param {shaka.media.PresentationTimeline} timeline
  95. * @return {!Map<number, !shaka.extern.Variant>}
  96. */
  97. createVariants(audios, videos, timeline) {
  98. // Get all the variant ids from all audio and video streams.
  99. /** @type {!Set<number>} */
  100. const variantIds = new Set();
  101. for (const streamDB of audios) {
  102. for (const id of streamDB.variantIds) {
  103. variantIds.add(id);
  104. }
  105. }
  106. for (const streamDB of videos) {
  107. for (const id of streamDB.variantIds) {
  108. variantIds.add(id);
  109. }
  110. }
  111. /** @type {!Map<number, shaka.extern.Variant>} */
  112. const variantMap = new Map();
  113. for (const id of variantIds) {
  114. variantMap.set(id, this.createEmptyVariant_(id));
  115. }
  116. // Assign each audio stream to its variants.
  117. for (const audio of audios) {
  118. /** @type {shaka.extern.Stream} */
  119. const stream = this.fromStreamDB_(audio, timeline);
  120. for (const variantId of audio.variantIds) {
  121. const variant = variantMap.get(variantId);
  122. goog.asserts.assert(
  123. !variant.audio, 'A variant should only have one audio stream');
  124. variant.language = stream.language;
  125. variant.primary = variant.primary || stream.primary;
  126. variant.audio = stream;
  127. }
  128. }
  129. // Assign each video stream to its variants.
  130. for (const video of videos) {
  131. /** @type {shaka.extern.Stream} */
  132. const stream = this.fromStreamDB_(video, timeline);
  133. for (const variantId of video.variantIds) {
  134. const variant = variantMap.get(variantId);
  135. goog.asserts.assert(
  136. !variant.video, 'A variant should only have one video stream');
  137. variant.primary = variant.primary || stream.primary;
  138. variant.video = stream;
  139. }
  140. }
  141. return variantMap;
  142. }
  143. /**
  144. * @param {shaka.extern.StreamDB} streamDB
  145. * @param {shaka.media.PresentationTimeline} timeline
  146. * @return {shaka.extern.Stream}
  147. * @private
  148. */
  149. fromStreamDB_(streamDB, timeline) {
  150. /** @type {!Array<!shaka.media.SegmentReference>} */
  151. const segments = streamDB.segments.map(
  152. (segment, index) => this.fromSegmentDB_(index, segment, streamDB));
  153. timeline.notifySegments(segments);
  154. /** @type {!shaka.media.SegmentIndex} */
  155. const segmentIndex = new shaka.media.SegmentIndex(segments);
  156. /** @type {shaka.extern.Stream} */
  157. const stream = {
  158. id: streamDB.id,
  159. originalId: streamDB.originalId,
  160. groupId: streamDB.groupId,
  161. createSegmentIndex: () => Promise.resolve(),
  162. segmentIndex,
  163. mimeType: streamDB.mimeType,
  164. codecs: streamDB.codecs,
  165. width: streamDB.width || undefined,
  166. height: streamDB.height || undefined,
  167. frameRate: streamDB.frameRate,
  168. pixelAspectRatio: streamDB.pixelAspectRatio,
  169. hdr: streamDB.hdr,
  170. colorGamut: streamDB.colorGamut,
  171. videoLayout: streamDB.videoLayout,
  172. kind: streamDB.kind,
  173. encrypted: streamDB.encrypted,
  174. drmInfos: [],
  175. keyIds: streamDB.keyIds,
  176. language: streamDB.language,
  177. originalLanguage: streamDB.originalLanguage || null,
  178. label: streamDB.label,
  179. type: streamDB.type,
  180. primary: streamDB.primary,
  181. trickModeVideo: null,
  182. dependencyStream: null,
  183. emsgSchemeIdUris: null,
  184. roles: streamDB.roles,
  185. forced: streamDB.forced,
  186. channelsCount: streamDB.channelsCount,
  187. audioSamplingRate: streamDB.audioSamplingRate,
  188. spatialAudio: streamDB.spatialAudio,
  189. closedCaptions: streamDB.closedCaptions,
  190. tilesLayout: streamDB.tilesLayout,
  191. mssPrivateData: streamDB.mssPrivateData,
  192. accessibilityPurpose: null,
  193. external: streamDB.external,
  194. fastSwitching: streamDB.fastSwitching,
  195. fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
  196. streamDB.mimeType, streamDB.codecs)]),
  197. isAudioMuxedInVideo: false,
  198. baseOriginalId: null,
  199. };
  200. return stream;
  201. }
  202. /**
  203. * @param {number} index
  204. * @param {shaka.extern.SegmentDB} segmentDB
  205. * @param {shaka.extern.StreamDB} streamDB
  206. * @return {!shaka.media.SegmentReference}
  207. * @private
  208. */
  209. fromSegmentDB_(index, segmentDB, streamDB) {
  210. /** @type {!shaka.offline.OfflineUri} */
  211. const uri = shaka.offline.OfflineUri.segment(
  212. this.mechanism_, this.cell_, segmentDB.dataKey);
  213. const initSegmentReference = segmentDB.initSegmentKey != null ?
  214. this.fromInitSegmentDB_(segmentDB.initSegmentKey) : null;
  215. const ref = new shaka.media.SegmentReference(
  216. segmentDB.startTime,
  217. segmentDB.endTime,
  218. () => [uri.toString()],
  219. /* startByte= */ 0,
  220. /* endByte= */ null,
  221. initSegmentReference,
  222. segmentDB.timestampOffset,
  223. segmentDB.appendWindowStart,
  224. segmentDB.appendWindowEnd,
  225. /* partialReferences= */ [],
  226. segmentDB.tilesLayout || '');
  227. ref.mimeType = segmentDB.mimeType || streamDB.mimeType || '';
  228. ref.codecs = segmentDB.codecs || streamDB.codecs || '';
  229. if (segmentDB.thumbnailSprite) {
  230. ref.setThumbnailSprite(segmentDB.thumbnailSprite);
  231. }
  232. return ref;
  233. }
  234. /**
  235. * @param {number} key
  236. * @return {!shaka.media.InitSegmentReference}
  237. * @private
  238. */
  239. fromInitSegmentDB_(key) {
  240. /** @type {!shaka.offline.OfflineUri} */
  241. const uri = shaka.offline.OfflineUri.segment(
  242. this.mechanism_, this.cell_, key);
  243. return new shaka.media.InitSegmentReference(
  244. () => [uri.toString()],
  245. /* startBytes= */ 0,
  246. /* endBytes= */ null );
  247. }
  248. /**
  249. * @param {shaka.extern.StreamDB} streamDB
  250. * @return {boolean}
  251. * @private
  252. */
  253. isAudio_(streamDB) {
  254. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  255. return streamDB.type == ContentType.AUDIO;
  256. }
  257. /**
  258. * @param {shaka.extern.StreamDB} streamDB
  259. * @return {boolean}
  260. * @private
  261. */
  262. isVideo_(streamDB) {
  263. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  264. return streamDB.type == ContentType.VIDEO;
  265. }
  266. /**
  267. * @param {shaka.extern.StreamDB} streamDB
  268. * @return {boolean}
  269. * @private
  270. */
  271. isText_(streamDB) {
  272. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  273. return streamDB.type == ContentType.TEXT;
  274. }
  275. /**
  276. * @param {shaka.extern.StreamDB} streamDB
  277. * @return {boolean}
  278. * @private
  279. */
  280. isImage_(streamDB) {
  281. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  282. return streamDB.type == ContentType.IMAGE;
  283. }
  284. /**
  285. * Creates an empty Variant.
  286. *
  287. * @param {number} id
  288. * @return {!shaka.extern.Variant}
  289. * @private
  290. */
  291. createEmptyVariant_(id) {
  292. return {
  293. id: id,
  294. language: '',
  295. disabledUntilTime: 0,
  296. primary: false,
  297. audio: null,
  298. video: null,
  299. bandwidth: 0,
  300. allowedByApplication: true,
  301. allowedByKeySystem: true,
  302. decodingInfos: [],
  303. };
  304. }
  305. };