diff --git a/demo/common/asset.js b/demo/common/asset.js index 199e0506444..e17a4cfb02b 100644 --- a/demo/common/asset.js +++ b/demo/common/asset.js @@ -167,6 +167,11 @@ const ShakaDemoAssetInfo = class { return this.drm.length == 1 && this.drm[0] == shakaAssets.KeySystem.CLEAR; } + /** @return {boolean} */ + isAes128() { + return this.drm.length == 1 && this.drm[0] == shakaAssets.KeySystem.AES128; + } + /** * @param {string} mediaPlaylistFullMimeType * @return {!ShakaDemoAssetInfo} diff --git a/demo/common/assets.js b/demo/common/assets.js index 5105c5a7e6e..068c81705fa 100644 --- a/demo/common/assets.js +++ b/demo/common/assets.js @@ -50,6 +50,7 @@ shakaAssets.KeySystem = { FAIRPLAY: shakaDemo.MessageIds.FAIRPLAY, PLAYREADY: shakaDemo.MessageIds.PLAYREADY, WIDEVINE: shakaDemo.MessageIds.WIDEVINE, + AES128: shakaDemo.MessageIds.AES128, CLEAR: shakaDemo.MessageIds.CLEAR, }; @@ -88,6 +89,7 @@ shakaAssets.identifierForKeySystem = (keySystem) => { case KeySystem.FAIRPLAY: return 'com.apple.fps'; case KeySystem.PLAYREADY: return 'com.microsoft.playready'; case KeySystem.WIDEVINE: return 'com.widevine.alpha'; + case KeySystem.AES128: return 'aes128'; default: return 'no drm protection'; } }; @@ -360,6 +362,7 @@ shakaAssets.testAssets = [ /* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/sintel.png', /* manifestUri= */ 'https://storage.googleapis.com/shaka-demo-assets/sintel-ts-aes-key-rotation/master.m3u8', /* source= */ shakaAssets.Source.SHAKA) + .addKeySystem(shakaAssets.KeySystem.AES128) .addFeature(shakaAssets.Feature.HLS) .addFeature(shakaAssets.Feature.MP2TS) .addFeature(shakaAssets.Feature.OFFLINE), @@ -368,6 +371,7 @@ shakaAssets.testAssets = [ /* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/sintel.png', /* manifestUri= */ 'https://storage.googleapis.com/shaka-demo-assets/sintel-fmp4-aes/master.m3u8', /* source= */ shakaAssets.Source.SHAKA) + .addKeySystem(shakaAssets.KeySystem.AES128) .addFeature(shakaAssets.Feature.HLS) .addFeature(shakaAssets.Feature.MP4) .addFeature(shakaAssets.Feature.OFFLINE), @@ -948,6 +952,7 @@ shakaAssets.testAssets = [ /* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/art_of_motion.png', /* manifestUri= */ 'https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/m3u8s/11331.m3u8', /* source= */ shakaAssets.Source.BITCODIN) + .addKeySystem(shakaAssets.KeySystem.AES128) .addFeature(shakaAssets.Feature.HIGH_DEFINITION) .addFeature(shakaAssets.Feature.HLS) .addFeature(shakaAssets.Feature.MP2TS) @@ -1087,6 +1092,16 @@ shakaAssets.testAssets = [ kind: 'subtitle', mime: 'text/vtt', }), + new ShakaDemoAssetInfo( + /* name= */ 'Sintel (DASH, AES-128)', + /* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/sintel.png', + /* manifestUri= */ 'https://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=mpd-time-csf)', + /* source= */ shakaAssets.Source.AZURE_MEDIA_SERVICES) + .addKeySystem(shakaAssets.KeySystem.AES128) + .addFeature(shakaAssets.Feature.DASH) + .addFeature(shakaAssets.Feature.MP4) + .addFeature(shakaAssets.Feature.HIGH_DEFINITION) + .addFeature(shakaAssets.Feature.OFFLINE), // End Azure Media Services assets }}} // GPAC assets {{{ diff --git a/demo/common/message_ids.js b/demo/common/message_ids.js index 55d440a443c..6dc69a1ee24 100644 --- a/demo/common/message_ids.js +++ b/demo/common/message_ids.js @@ -34,6 +34,7 @@ shakaDemo.MessageIds = { WEBM: 'DEMO_WEBM', XLINK: 'DEMO_XLINK', /* Key systems. */ + AES128: 'DEMO_AES128', CLEAR: 'DEMO_CLEAR', CLEAR_KEY: 'DEMO_CLEAR_KEY', FAIRPLAY: 'DEMO_FAIRPLAY', diff --git a/demo/locales/en.json b/demo/locales/en.json index 58eba4827ff..98c31ec2aa2 100644 --- a/demo/locales/en.json +++ b/demo/locales/en.json @@ -6,6 +6,7 @@ "DEMO_AD_TAG_URL": "Ad Tag URL", "DEMO_ADS_TAB": "Ads", "DEMO_ADS_SECTION_HEADER": "Ads", + "DEMO_AES128": "AES-128 protection", "DEMO_ALL_CONTENT": "ALL CONTENT", "DEMO_ALWAYS_STREAM_TEXT": "Always Stream Text", "DEMO_ALWAYS_STREAM_TEXT_WARNING": "Text must always be streamed while native controls are enabled, for captions to work.", diff --git a/demo/locales/source.json b/demo/locales/source.json index da7e9f68993..c454286673d 100644 --- a/demo/locales/source.json +++ b/demo/locales/source.json @@ -27,6 +27,10 @@ "description": "The label on a field that allows users to provide a Ad Tag URL for a custom asset.", "message": "Ad Tag URL" }, + "DEMO_AES128": { + "description": "Text that describes an asset that is not protected with any Digital Rights Management system but it is protected with AES-128.", + "message": "[JARGON:AES-128] protection" + }, "DEMO_ALL_CONTENT": { "description": "A link in the header, that switches to a page for browsing or searching through the entire content library.", "message": "ALL CONTENT" diff --git a/demo/main.js b/demo/main.js index 3738aed3694..b92a45adf03 100644 --- a/demo/main.js +++ b/demo/main.js @@ -743,7 +743,7 @@ shakaDemo.Main = class { return shakaDemo.MessageIds.UNSUPPORTED_NO_DOWNLOAD; } - if (!asset.isClear()) { + if (!asset.isClear() && !asset.isAes128()) { const hasSupportedDRM = asset.drm.some((drm) => { return this.support_.drm[shakaAssets.identifierForKeySystem(drm)]; }); diff --git a/lib/media/segment_reference.js b/lib/media/segment_reference.js index 1f29887fdde..062d9ce8b3a 100644 --- a/lib/media/segment_reference.js +++ b/lib/media/segment_reference.js @@ -32,9 +32,11 @@ shaka.media.InitSegmentReference = class { * the quality of the media associated with this init segment. * @param {(null|number)=} timescale * @param {(null|BufferSource)=} segmentData + * @param {?shaka.extern.HlsAes128Key=} aes128Key + * The segment's AES-128-CBC full segment encryption key and iv. */ constructor(uris, startByte, endByte, mediaQuality = null, timescale = null, - segmentData = null) { + segmentData = null, aes128Key = null) { /** @type {function():!Array.} */ this.getUris = uris; @@ -52,6 +54,9 @@ shaka.media.InitSegmentReference = class { /** @type {BufferSource|null} */ this.segmentData = segmentData; + + /** @type {?shaka.extern.HlsAes128Key} */ + this.hlsAes128Key = aes128Key; } /** diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 0206fa007fb..83b39ea1912 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -1409,15 +1409,11 @@ shaka.media.StreamingEngine = class { 'ReadableStream is not supported by the browser.'); } const fetchSegment = this.fetch_(mediaState, reference); - let result = await fetchSegment; + const result = await fetchSegment; this.destroyer_.ensureNotDestroyed(); if (this.fatalError_) { return; } - if (reference.hlsAes128Key) { - goog.asserts.assert(iter, 'mediaState.segmentIterator should exist'); - result = await this.aes128Decrypt_(result, reference, iter); - } this.destroyer_.ensureNotDestroyed(); // If the text stream gets switched between fetch_() and append_(), the @@ -1502,13 +1498,13 @@ shaka.media.StreamingEngine = class { /** * @param {!BufferSource} rawResult - * @param {!shaka.media.SegmentReference} reference + * @param {shaka.extern.HlsAes128Key} aes128Key * @param {!shaka.media.SegmentIterator} iter * @return {!Promise.} finalResult * @private */ - async aes128Decrypt_(rawResult, reference, iter) { - const key = reference.hlsAes128Key; + async aes128Decrypt_(rawResult, aes128Key, iter) { + const key = aes128Key; if (!key.cryptoKey) { goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' + 'preloaded, fetchKey function should be provided'); @@ -2188,7 +2184,13 @@ shaka.media.StreamingEngine = class { mediaState.operation = op; const response = await op.promise; mediaState.operation = null; - return response.data; + let result = response.data; + if (reference.hlsAes128Key) { + const iter = mediaState.segmentIterator; + goog.asserts.assert(iter, 'mediaState.segmentIterator should exist'); + result = await this.aes128Decrypt_(result, reference.hlsAes128Key, iter); + } + return result; } /**