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. };
  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. emsgSchemeIdUris: null,
  183. roles: streamDB.roles,
  184. forced: streamDB.forced,
  185. channelsCount: streamDB.channelsCount,
  186. audioSamplingRate: streamDB.audioSamplingRate,
  187. spatialAudio: streamDB.spatialAudio,
  188. closedCaptions: streamDB.closedCaptions,
  189. tilesLayout: streamDB.tilesLayout,
  190. accessibilityPurpose: null,
  191. external: streamDB.external,
  192. fastSwitching: streamDB.fastSwitching,
  193. fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
  194. streamDB.mimeType, streamDB.codecs)]),
  195. };
  196. return stream;
  197. }
  198. /**
  199. * @param {number} index
  200. * @param {shaka.extern.SegmentDB} segmentDB
  201. * @param {shaka.extern.StreamDB} streamDB
  202. * @return {!shaka.media.SegmentReference}
  203. * @private
  204. */
  205. fromSegmentDB_(index, segmentDB, streamDB) {
  206. /** @type {!shaka.offline.OfflineUri} */
  207. const uri = shaka.offline.OfflineUri.segment(
  208. this.mechanism_, this.cell_, segmentDB.dataKey);
  209. const initSegmentReference = segmentDB.initSegmentKey != null ?
  210. this.fromInitSegmentDB_(segmentDB.initSegmentKey) : null;
  211. const ref = new shaka.media.SegmentReference(
  212. segmentDB.startTime,
  213. segmentDB.endTime,
  214. () => [uri.toString()],
  215. /* startByte= */ 0,
  216. /* endByte= */ null,
  217. initSegmentReference,
  218. segmentDB.timestampOffset,
  219. segmentDB.appendWindowStart,
  220. segmentDB.appendWindowEnd,
  221. /* partialReferences= */ [],
  222. segmentDB.tilesLayout || '');
  223. ref.mimeType = segmentDB.mimeType || streamDB.mimeType || '';
  224. ref.codecs = segmentDB.codecs || streamDB.codecs || '';
  225. return ref;
  226. }
  227. /**
  228. * @param {number} key
  229. * @return {!shaka.media.InitSegmentReference}
  230. * @private
  231. */
  232. fromInitSegmentDB_(key) {
  233. /** @type {!shaka.offline.OfflineUri} */
  234. const uri = shaka.offline.OfflineUri.segment(
  235. this.mechanism_, this.cell_, key);
  236. return new shaka.media.InitSegmentReference(
  237. () => [uri.toString()],
  238. /* startBytes= */ 0,
  239. /* endBytes= */ null );
  240. }
  241. /**
  242. * @param {shaka.extern.StreamDB} streamDB
  243. * @return {boolean}
  244. * @private
  245. */
  246. isAudio_(streamDB) {
  247. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  248. return streamDB.type == ContentType.AUDIO;
  249. }
  250. /**
  251. * @param {shaka.extern.StreamDB} streamDB
  252. * @return {boolean}
  253. * @private
  254. */
  255. isVideo_(streamDB) {
  256. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  257. return streamDB.type == ContentType.VIDEO;
  258. }
  259. /**
  260. * @param {shaka.extern.StreamDB} streamDB
  261. * @return {boolean}
  262. * @private
  263. */
  264. isText_(streamDB) {
  265. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  266. return streamDB.type == ContentType.TEXT;
  267. }
  268. /**
  269. * @param {shaka.extern.StreamDB} streamDB
  270. * @return {boolean}
  271. * @private
  272. */
  273. isImage_(streamDB) {
  274. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  275. return streamDB.type == ContentType.IMAGE;
  276. }
  277. /**
  278. * Creates an empty Variant.
  279. *
  280. * @param {number} id
  281. * @return {!shaka.extern.Variant}
  282. * @private
  283. */
  284. createEmptyVariant_(id) {
  285. return {
  286. id: id,
  287. language: '',
  288. disabledUntilTime: 0,
  289. primary: false,
  290. audio: null,
  291. video: null,
  292. bandwidth: 0,
  293. allowedByApplication: true,
  294. allowedByKeySystem: true,
  295. decodingInfos: [],
  296. };
  297. }
  298. };