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