Source: lib/offline/manifest_converter.js

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