diff --git a/demo/common/assets.js b/demo/common/assets.js index fb01c1a10b..3c563a5901 100644 --- a/demo/common/assets.js +++ b/demo/common/assets.js @@ -1406,7 +1406,8 @@ shakaAssets.testAssets = [ /* source= */ shakaAssets.Source.MICROSOFT) .addFeature(shakaAssets.Feature.MSS) .addFeature(shakaAssets.Feature.HIGH_DEFINITION) - .addFeature(shakaAssets.Feature.MP4), + .addFeature(shakaAssets.Feature.MP4) + .addFeature(shakaAssets.Feature.OFFLINE), new ShakaDemoAssetInfo( /* name= */ 'Super Speedway Trailer (MSS - PlayReady)', /* iconUri= */ 'https://reference.dashif.org/dash.js/latest/samples/lib/img/mss-1.jpg', diff --git a/externs/shaka/offline.js b/externs/shaka/offline.js index 0d2696cc79..9971b25521 100644 --- a/externs/shaka/offline.js +++ b/externs/shaka/offline.js @@ -141,6 +141,7 @@ shaka.extern.ManifestDB; * spatialAudio: boolean, * closedCaptions: Map., * tilesLayout: (string|undefined), + * mssPrivateData: (shaka.extern.MssPrivateData|undefined), * external: boolean, * fastSwitching: boolean, * isAudioMuxedInVideo: boolean @@ -214,6 +215,9 @@ shaka.extern.ManifestDB; * The value is a grid-item-dimension consisting of two positive decimal * integers in the format: column-x-row ('4x3'). It describes the arrangement * of Images in a Grid. The minimum valid LAYOUT is '1x1'. + * @property {(shaka.extern.MssPrivateData|undefined)} mssPrivateData + * Microsoft Smooth Streaming only.
+ * Private MSS data that is necessary to be able to do transmuxing. * @property {boolean} external * Indicate if the stream was added externally. * Eg: external text tracks. diff --git a/lib/offline/download_info.js b/lib/offline/download_info.js index 45073787b7..62831f5329 100644 --- a/lib/offline/download_info.js +++ b/lib/offline/download_info.js @@ -7,6 +7,7 @@ goog.provide('shaka.offline.DownloadInfo'); goog.require('shaka.util.Networking'); +goog.require('shaka.util.Uint8ArrayUtils'); goog.requireType('shaka.media.InitSegmentReference'); goog.requireType('shaka.media.SegmentReference'); @@ -43,6 +44,10 @@ shaka.offline.DownloadInfo = class { * @return {string} */ static idForSegmentRef(ref) { + const segmentData = ref.getSegmentData(); + if (segmentData) { + return shaka.util.Uint8ArrayUtils.toBase64(segmentData); + } // Escape the URIs using encodeURI, to make sure that a weirdly formed URI // cannot cause two unrelated refs to be considered equivalent. const removeSprites = (uri) => { diff --git a/lib/offline/download_manager.js b/lib/offline/download_manager.js index b5e3390c1b..d6a6ffc7db 100644 --- a/lib/offline/download_manager.js +++ b/lib/offline/download_manager.js @@ -168,6 +168,60 @@ shaka.offline.DownloadManager = class { return newPromise; } + /** + * Add already-downloaded data to a group. + * + * @param {number} groupId + * The group to add this segment to. If the group does not exist, a new + * group will be created. + * @param {!BufferSource} queueData + * @param {number} estimateId + * @param {boolean} isInitSegment + * @param {function(BufferSource):!Promise} onDownloaded + * The callback for when this request has been downloaded. Downloading for + * |group| will pause until the promise returned by |onDownloaded| resolves. + * @return {!Promise} Resolved when this request is complete. + */ + queueData(groupId, queueData, estimateId, isInitSegment, onDownloaded) { + this.destroyer_.ensureNotDestroyed(); + + const group = this.groups_.get(groupId) || Promise.resolve(); + + // Add another download to the group. + const newPromise = group.then(() => { + // Make sure we stop downloading if we have been destroyed. + if (this.destroyer_.destroyed()) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.STORAGE, + shaka.util.Error.Code.OPERATION_ABORTED); + } + + // Update initData + if (isInitSegment) { + const segmentBytes = shaka.util.BufferUtils.toUint8(queueData); + const pssh = new shaka.util.Pssh(segmentBytes); + for (const key in pssh.data) { + const index = Number(key); + const data = pssh.data[index]; + const systemId = pssh.systemIds[index]; + this.onInitData_(data, systemId); + } + } + + // Update all our internal stats. + this.estimator_.close(estimateId, queueData.byteLength); + this.onProgress_( + this.estimator_.getEstimatedProgress(), + this.estimator_.getTotalDownloaded()); + + return onDownloaded(queueData); + }); + + this.groups_.set(groupId, newPromise); + return newPromise; + } + /** * Add additional async work to the group work queue. * diff --git a/lib/offline/manifest_converter.js b/lib/offline/manifest_converter.js index b07c6e9cc7..c88bfd4d1e 100644 --- a/lib/offline/manifest_converter.js +++ b/lib/offline/manifest_converter.js @@ -215,6 +215,7 @@ shaka.offline.ManifestConverter = class { spatialAudio: streamDB.spatialAudio, closedCaptions: streamDB.closedCaptions, tilesLayout: streamDB.tilesLayout, + mssPrivateData: streamDB.mssPrivateData, accessibilityPurpose: null, external: streamDB.external, fastSwitching: streamDB.fastSwitching, diff --git a/lib/offline/storage.js b/lib/offline/storage.js index 32ee8b2da6..2a208b46a9 100644 --- a/lib/offline/storage.js +++ b/lib/offline/storage.js @@ -511,8 +511,16 @@ shaka.offline.Storage = class { pendingDataSize += data.byteLength; }; - downloader.queue(download.groupId, - request, estimateId, isInitSegment, onDownloaded); + const ref = /** @type {!shaka.media.SegmentReference} */ ( + download.ref); + const segmentData = ref.getSegmentData(); + if (segmentData) { + downloader.queueData(download.groupId, + segmentData, estimateId, isInitSegment, onDownloaded); + } else { + downloader.queue(download.groupId, + request, estimateId, isInitSegment, onDownloaded); + } } await downloader.waitToFinish(); @@ -1606,6 +1614,7 @@ shaka.offline.Storage = class { spatialAudio: stream.spatialAudio, closedCaptions: stream.closedCaptions, tilesLayout: stream.tilesLayout, + mssPrivateData: stream.mssPrivateData, external: stream.external, fastSwitching: stream.fastSwitching, isAudioMuxedInVideo: stream.isAudioMuxedInVideo, diff --git a/test/offline/manifest_convert_unit.js b/test/offline/manifest_convert_unit.js index a85be1974a..75ee311bcd 100644 --- a/test/offline/manifest_convert_unit.js +++ b/test/offline/manifest_convert_unit.js @@ -532,6 +532,7 @@ describe('ManifestConverter', () => { spatialAudio: false, closedCaptions: null, tilesLayout: undefined, + mssPrivateData: undefined, accessibilityPurpose: null, external: false, fastSwitching: false, @@ -587,6 +588,7 @@ describe('ManifestConverter', () => { spatialAudio: streamDb.spatialAudio, closedCaptions: streamDb.closedCaptions, tilesLayout: streamDb.tilesLayout, + mssPrivateData: streamDb.mssPrivateData, accessibilityPurpose: null, external: streamDb.external, fastSwitching: streamDb.fastSwitching,