From e69335c667478f146b2ea862ca7d8ad04c97c984 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Fri, 1 Dec 2017 14:00:43 +0100 Subject: [PATCH 01/38] v1.14.0-dev --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8dbb7cc24..a5f635f81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hasplayer.js", - "version": "1.13.1", + "version": "1.14.0-dev", "scripts": { "build": "cd build && gulp", "doc": "cd build && gulp doc", From d642f270830e8b3118c49e34ec81487a4b5a8590 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Mon, 11 Dec 2017 14:55:03 +0100 Subject: [PATCH 02/38] update test of timestampOffset --- app/js/mss/MssParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/mss/MssParser.js b/app/js/mss/MssParser.js index b6ba20077..8535a4030 100644 --- a/app/js/mss/MssParser.js +++ b/app/js/mss/MssParser.js @@ -633,7 +633,7 @@ Mss.dependencies.MssParser = function() { if (adaptations[i].contentType === 'audio' || adaptations[i].contentType === 'video') { segments = adaptations[i].SegmentTemplate.SegmentTimeline.S_asArray; startTime = segments[0].t; - if (!timestampOffset) { + if (timestampOffset === undefined) { timestampOffset = startTime; } timestampOffset = Math.min(timestampOffset, startTime); From e26cc0a9512bdd52a8e6ff4084ad5a639b6c1b45 Mon Sep 17 00:00:00 2001 From: Anatoliy Klymenko Date: Sun, 3 Dec 2017 17:38:01 -0800 Subject: [PATCH 03/38] Do not remove past buffers for generic WebKit port --- app/js/streaming/BufferController.js | 5 +++-- app/lib/fingerprint/fp_browser.js | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/js/streaming/BufferController.js b/app/js/streaming/BufferController.js index 7e883d967..675d6b08b 100644 --- a/app/js/streaming/BufferController.js +++ b/app/js/streaming/BufferController.js @@ -85,6 +85,7 @@ MediaPlayer.dependencies.BufferController = function() { // Patch for Safari: do not remove past buffer in live use case since it generates MEDIA_ERROR_DECODE while appending new segment (see hasEnoughSpaceToAppend()) isSafari = (fingerprint_browser().name === "Safari"), + isWebKit = (fingerprint_browser().name === "WebKit"), // Patch for Firefox: set buffer timestampOffset since on Firefox timestamping is based on CTS (see OnMediaLoaded()) isFirefox = (fingerprint_browser().name === "Firefox"), @@ -454,8 +455,8 @@ MediaPlayer.dependencies.BufferController = function() { isQuotaExceeded = false; - // Patch for Safari: do not remove past buffer since it generates MEDIA_ERROR_DECODE while appending new segment - if (bufferLevel > 1 && !isSafari) { + // Patch for Safari & WebKit: do not remove past buffer since it generates MEDIA_ERROR_DECODE while appending new segment + if (bufferLevel > 1 && !isSafari && !isWebKit) { // Remove outdated buffer parts and requests // (checking bufferLevel ensure buffer is not empty or back to current time) removeBuffer.call(self, -1, getWorkingTime.call(self) - bufferToKeep).then( diff --git a/app/lib/fingerprint/fp_browser.js b/app/lib/fingerprint/fp_browser.js index c338729fb..e480c28c2 100644 --- a/app/lib/fingerprint/fp_browser.js +++ b/app/lib/fingerprint/fp_browser.js @@ -112,6 +112,9 @@ function fingerprint_browser() { } else if (/adventurer/.test(userAgent)) { //test for Orange Adventurer; version = Number(RegExp.$1); // capture x.x portion and store as a number name = "Adventurer"; + } else if (/webkit[\/\s](\d+\.\d+)/.test(userAgent)) { //test for generic webkit port; + version = Number(RegExp.$1); // capture x.x portion and store as a number + name = "WebKit"; } else { version = "unknown"; name = "unknown"; @@ -125,4 +128,4 @@ function fingerprint_browser() { name: name.replace(/\s+/g, ''), version: version }; -} \ No newline at end of file +} From 8b5f259da6da516c578e04bffac2a04594c4889f Mon Sep 17 00:00:00 2001 From: Anatoliy Klymenko Date: Sun, 3 Dec 2017 18:01:14 -0800 Subject: [PATCH 04/38] Fix ArrayBuffer.isView method implementation --- app/js/streaming/protection/ProtectionController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/streaming/protection/ProtectionController.js b/app/js/streaming/protection/ProtectionController.js index 21e6fb0f0..1615d9341 100644 --- a/app/js/streaming/protection/ProtectionController.js +++ b/app/js/streaming/protection/ProtectionController.js @@ -46,7 +46,7 @@ // Define ArrayBuffer.isView method in case it is not defined (like in IE11 for example) if (!ArrayBuffer.isView) { ArrayBuffer.isView = function(data) { - return data instanceof ArrayBuffer; + return data && data.buffer instanceof ArrayBuffer; }; } From a5e21363dad5f1988c298b6780e14e5d583a8214 Mon Sep 17 00:00:00 2001 From: Anatoliy Klymenko Date: Sun, 3 Dec 2017 20:12:36 -0800 Subject: [PATCH 05/38] Fix initData type passing to CDM --- app/js/streaming/protection/ProtectionModel_01b.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/streaming/protection/ProtectionModel_01b.js b/app/js/streaming/protection/ProtectionModel_01b.js index 0a6c77dc3..b7581fee2 100644 --- a/app/js/streaming/protection/ProtectionModel_01b.js +++ b/app/js/streaming/protection/ProtectionModel_01b.js @@ -397,7 +397,7 @@ MediaPlayer.models.ProtectionModel_01b = function () { if (!this.protectionExt.isClearKey(this.keySystem)) { // Send our request to the CDM videoElement[api.addKey](this.keySystem.systemString, - new Uint8Array(message), sessionToken.initData, sessionID); + new Uint8Array(message), new Uint8Array(sessionToken.initData), sessionID); } else { // For clearkey, message is a MediaPlayer.vo.protection.ClearKeyKeySet for (var i = 0; i < message.keyPairs.length; i++) { From 327dd254ef62bd9bc4c74a0f83c03edb5cd6c639 Mon Sep 17 00:00:00 2001 From: Anatoliy Klymenko Date: Thu, 14 Dec 2017 11:31:42 -0800 Subject: [PATCH 06/38] Perform null check on playBackQuality --- app/js/streaming/StreamController.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/js/streaming/StreamController.js b/app/js/streaming/StreamController.js index a9f6c9222..48a76b421 100644 --- a/app/js/streaming/StreamController.js +++ b/app/js/streaming/StreamController.js @@ -162,7 +162,9 @@ MediaPlayer.dependencies.StreamController = function() { currentTime = videoElement.currentTime, playBackQuality = self.videoExt.getPlaybackQuality(videoElement); - self.metricsModel.addPlaybackQuality("video", time, playBackQuality, currentTime); + // playBackQuality may be null + if (playBackQuality) + self.metricsModel.addPlaybackQuality("video", time, playBackQuality, currentTime); self.metricsModel.addVideoResolution("video", time, videoElement.videoWidth, videoElement.videoHeight, currentTime); if (!getNextStream()) { @@ -725,4 +727,4 @@ MediaPlayer.dependencies.StreamController.prototype = { MediaPlayer.dependencies.StreamController.eventList = { ENAME_STREAMS_COMPOSED: "streamsComposed", ENAME_TEARDOWN_COMPLETE: "streamTeardownComplete" -}; \ No newline at end of file +}; From 5fb485d162aee3d36c8a02c20a12c46f9f5d5ec6 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Fri, 22 Dec 2017 11:01:00 +0100 Subject: [PATCH 07/38] Update ProtectionController.js Changed comment since in IE11 supports ArrayBuffer.isView --- app/js/streaming/protection/ProtectionController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/streaming/protection/ProtectionController.js b/app/js/streaming/protection/ProtectionController.js index 1615d9341..5ba1d7e03 100644 --- a/app/js/streaming/protection/ProtectionController.js +++ b/app/js/streaming/protection/ProtectionController.js @@ -43,7 +43,7 @@ * each step manually (key system selection, session creation, session maintenance). */ -// Define ArrayBuffer.isView method in case it is not defined (like in IE11 for example) +// Define ArrayBuffer.isView method in case it is not defined if (!ArrayBuffer.isView) { ArrayBuffer.isView = function(data) { return data && data.buffer instanceof ArrayBuffer; From 6cb79228844673d413d2c2fa19b730370e015148 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Tue, 30 Jan 2018 17:04:15 +0100 Subject: [PATCH 08/38] HlsStream: bug correction in case no FP certificate is provided --- app/js/hls/HlsStream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/hls/HlsStream.js b/app/js/hls/HlsStream.js index 63fb368fb..dbef3ef36 100644 --- a/app/js/hls/HlsStream.js +++ b/app/js/hls/HlsStream.js @@ -236,7 +236,7 @@ Hls.dependencies.HlsStream = function() { getCertificate = function () { var protData = getKsProtectionData('com.apple.fps.1_0'); if (!protData || !protData.serverCertificate) { - return []; + return new Uint8Array(0); } return BASE64.decodeArray(protData.serverCertificate); }, From ea920ebd8c546fcd26878e1493fcd4853bcb09f7 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Thu, 1 Feb 2018 09:33:58 +0100 Subject: [PATCH 09/38] MssParser: generate a default/minimal pssh Widevine with KID extracted from PlayReady ProtectionHeader --- app/js/mss/MssParser.js | 48 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/app/js/mss/MssParser.js b/app/js/mss/MssParser.js index 8535a4030..a399bdc58 100644 --- a/app/js/mss/MssParser.js +++ b/app/js/mss/MssParser.js @@ -471,7 +471,7 @@ Mss.dependencies.MssParser = function() { return contentProtection; }, - createWidevineContentProtection = function(/*protectionHeader*/) { + createWidevineContentProtection = function(KID) { var contentProtection = {}, keySystem = this.system.getObject("ksWidevine"); @@ -479,9 +479,51 @@ Mss.dependencies.MssParser = function() { contentProtection.schemeIdUri = keySystem.schemeIdURI; contentProtection.value = keySystem.systemString; + // Create Widevine CENC header (Protocol Buffer) with KID value + var wvCencHeader = new Uint8Array(2 + KID.length); + wvCencHeader[0] = 0x12; + wvCencHeader[1] = 0x10; + wvCencHeader.set(KID, 2); + + // Create a pssh box + var length = 12 /* box length, type, version and flags */ + 16 /* SystemID */ + 4 /* data length */ + wvCencHeader.length, + pssh = new Uint8Array(length), + i = 0; + + // Set box length value + pssh[i++] = (length & 0xFF000000) >> 24; + pssh[i++] = (length & 0x00FF0000) >> 16; + pssh[i++] = (length & 0x0000FF00) >> 8; + pssh[i++] = (length & 0x000000FF); + + // Set type ('pssh'), version (0) and flags (0) + pssh.set([0x70, 0x73, 0x73, 0x68, 0x00, 0x00, 0x00, 0x00], i); + i += 8; + + // Set SystemID ('edef8ba9-79d6-4ace-a3c8-27dcd51d21ed') + pssh.set([0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed], i); + i += 16; + + // Set data length value + pssh[i++] = (wvCencHeader.length & 0xFF000000) >> 24; + pssh[i++] = (wvCencHeader.length & 0x00FF0000) >> 16; + pssh[i++] = (wvCencHeader.length & 0x0000FF00) >> 8; + pssh[i++] = (wvCencHeader.length & 0x000000FF); + + // Copy Widevine CENC header + pssh.set(wvCencHeader, i); + + // Convert to BASE64 string + pssh = String.fromCharCode.apply(null, pssh); + pssh = BASE64.encodeASCII(pssh); + + // Add pssh value to ContentProtection + contentProtection.pssh = { + __text: pssh + }; + return contentProtection; }, - /* @endif */ addDVRInfo = function(adaptationSet) { var segmentTemplate = adaptationSet.SegmentTemplate, @@ -574,7 +616,7 @@ Mss.dependencies.MssParser = function() { contentProtections.push(contentProtection); // Create ContentProtection for Widevine (as a CENC protection) - contentProtection = createWidevineContentProtection.call(this, protectionHeader); + contentProtection = createWidevineContentProtection.call(this, KID); contentProtection["cenc:default_KID"] = KID; contentProtections.push(contentProtection); From f308d649c403d1aabe56340e3fab7169904534cf Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Thu, 1 Feb 2018 09:34:23 +0100 Subject: [PATCH 10/38] KeySystemWidevine: remove reading of pssh from protData (MSS use-case only, pssh now generated in MssParser) --- .../protection/drm/KeySystem_Widevine.js | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/app/js/streaming/protection/drm/KeySystem_Widevine.js b/app/js/streaming/protection/drm/KeySystem_Widevine.js index 485da980e..4a08e376b 100644 --- a/app/js/streaming/protection/drm/KeySystem_Widevine.js +++ b/app/js/streaming/protection/drm/KeySystem_Widevine.js @@ -42,50 +42,8 @@ MediaPlayer.dependencies.protection.KeySystem_Widevine = function() { keySystemUUID = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", protData = null, - replaceKID = function (pssh, KID) { - var pssh_array, - replace = true, - kidLen = 16, - pos, - i, j; - - pssh_array = new Uint8Array(pssh); - - for (i = 0; i <= pssh_array.length - (kidLen + 2); i++) { - if (pssh_array[i] === 0x12 && pssh_array[i+1] === 0x10) { - pos = i + 2; - for (j = pos; j < (pos + kidLen); j++) { - if (pssh_array[j] !== 0xFF) { - replace = false; - break; - } - } - break; - } - } - - if (replace) { - pssh_array.set(KID, pos); - } - - return pssh_array.buffer; - }, - doGetInitData = function(cpData) { - var pssh = null; - // Get pssh from protectionData or from manifest - if (protData && protData.pssh) { - pssh = BASE64.decodeArray(protData.pssh).buffer; - } else { - pssh = MediaPlayer.dependencies.protection.CommonEncryption.parseInitDataFromContentProtection(cpData); - } - - // Check if KID within pssh is empty, in that case set KID value according to 'cenc:default_KID' value - if (pssh) { - pssh = replaceKID(pssh, cpData['cenc:default_KID']); - } - - return pssh; + return MediaPlayer.dependencies.protection.CommonEncryption.parseInitDataFromContentProtection(cpData); }, doGetKeySystemConfigurations = function(videoCodec, audioCodec, sessionType) { From fe3f2adffbedd3949a1216e2baf62c52c2ce5e76 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Thu, 1 Feb 2018 09:34:56 +0100 Subject: [PATCH 11/38] Update doc (README and MediaPlayer API) to remove pssh parameter from protectionData --- README.md | 1 - app/js/streaming/MediaPlayer.js | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index d17334c08..cd0d3c3e4 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,6 @@ In the case of protected content, here is an example illustrating setting of the laURL: "", withCredentials: "", cdmData: "", // Supported by PlayReady key system (using MS-prefixed EME API) only - pssh: "" // Considered for Widevine key system only serverCertificate: "" audioRobustness: "" // Considered for Widevine key system only videoRobustness: "" // Considered for Widevine key system only diff --git a/app/js/streaming/MediaPlayer.js b/app/js/streaming/MediaPlayer.js index 046ea5645..bc021939c 100644 --- a/app/js/streaming/MediaPlayer.js +++ b/app/js/streaming/MediaPlayer.js @@ -851,7 +851,6 @@ MediaPlayer = function () { "[key_system_name]": { laURL: "[licenser url (optional)]", withCredentials: "[license_request_withCredentials_value (true or false, optional)]", - pssh: "[base64 pssh box (as Base64 string, optional)]", // Considered for Widevine key system only cdmData: "[CDM data (optional)]", // Supported by PlayReady key system (using MS-prefixed EME API) only serverCertificate: "[license_server_certificate (as Base64 string, optional)]", audioRobustness: "[audio_robustness_level (optional)]", // Considered for Widevine key system only From 294221779e4e79469586deee691d4e7aad09d4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Cunat?= Date: Fri, 2 Feb 2018 15:52:33 +0100 Subject: [PATCH 12/38] Update .travis.yml --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e33ab068e..60b5d637e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ branches: - /(release).*/ stages: - build + - deploy - name: test # Run test stage only if cron job if: type = cron @@ -25,7 +26,6 @@ env: - BROWSER=edge # - BROWSER=firefox before_install: - - openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d - npm install -g gulp - chmod 600 travis_deploy - eval `ssh-agent -s` @@ -40,7 +40,10 @@ jobs: - npm run build # Build/generate the jsdoc - npm run doc + - stage: deploy + script: # Package and deploy version on project's pages + - openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d - npm run deploy deploy: api_key: @@ -48,4 +51,4 @@ deploy: on: branch: master tags: true - skip_cleanup: true \ No newline at end of file + skip_cleanup: true From c1016a26069ef45551c8782eae74a636acef3329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Cunat?= Date: Fri, 2 Feb 2018 15:59:24 +0100 Subject: [PATCH 13/38] Update .travis.yml --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 60b5d637e..efa3d8ec0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,9 +27,6 @@ env: # - BROWSER=firefox before_install: - npm install -g gulp - - chmod 600 travis_deploy - - eval `ssh-agent -s` - - ssh-add travis_deploy install: - npm install jobs: @@ -44,6 +41,9 @@ jobs: script: # Package and deploy version on project's pages - openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d + - chmod 600 travis_deploy + - eval `ssh-agent -s` + - ssh-add travis_deploy - npm run deploy deploy: api_key: From 3114fcd9355c7e7a9ef06cb01fdbf8bc7c313bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Cunat?= Date: Fri, 2 Feb 2018 16:11:43 +0100 Subject: [PATCH 14/38] Update .travis.yml --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index efa3d8ec0..d98bee7a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,14 +37,14 @@ jobs: - npm run build # Build/generate the jsdoc - npm run doc - - stage: deploy - script: + - if: [branch = master] OR [branch = development]; then # Package and deploy version on project's pages - - openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d - - chmod 600 travis_deploy - - eval `ssh-agent -s` - - ssh-add travis_deploy - - npm run deploy + openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d + chmod 600 travis_deploy + eval `ssh-agent -s` + ssh-add travis_deploy + npm run deploy + fi deploy: api_key: secure: fLr/s6xT5T2mh2UaPUtt3UlkdmvRkqzOlolJss3c6OICeFegFmx/ieuf8iACnB5u3fZ1pgY9/wIXq9f6uKUpTsDQd/B0uALpe/Nzv/OFGkC1S8qk66Z9VC4/LNkDSEOryAKDDPwMgcZgxyPe5u20NrVUKahMB0B4vhYKwGle5LQ= From 8379f0cbf4ce90a13ce248f609cfeaef6b6bd2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Cunat?= Date: Fri, 2 Feb 2018 16:42:12 +0100 Subject: [PATCH 15/38] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d98bee7a7..99adf661e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ jobs: - npm run build # Build/generate the jsdoc - npm run doc - - if: [branch = master] OR [branch = development]; then + - if [ "$TRAVIS_BRANCH" == "master" || "$TRAVIS_BRANCH" == "development" ]; then # Package and deploy version on project's pages openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d chmod 600 travis_deploy From 4eeeb50c5310636eacb0334fd0f46086468b60d5 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Fri, 2 Feb 2018 16:55:57 +0100 Subject: [PATCH 16/38] Update .travis.yml --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 99adf661e..776aa1a76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,14 +37,15 @@ jobs: - npm run build # Build/generate the jsdoc - npm run doc - - if [ "$TRAVIS_BRANCH" == "master" || "$TRAVIS_BRANCH" == "development" ]; then - # Package and deploy version on project's pages - openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d - chmod 600 travis_deploy - eval `ssh-agent -s` - ssh-add travis_deploy - npm run deploy - fi + - | + if [ "$TRAVIS_BRANCH" == "master" || "$TRAVIS_BRANCH" == "development" ]; then + # Package and deploy version on project's pages + openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d + chmod 600 travis_deploy + eval `ssh-agent -s` + ssh-add travis_deploy + npm run deploy + fi deploy: api_key: secure: fLr/s6xT5T2mh2UaPUtt3UlkdmvRkqzOlolJss3c6OICeFegFmx/ieuf8iACnB5u3fZ1pgY9/wIXq9f6uKUpTsDQd/B0uALpe/Nzv/OFGkC1S8qk66Z9VC4/LNkDSEOryAKDDPwMgcZgxyPe5u20NrVUKahMB0B4vhYKwGle5LQ= From 0178fb79fe14639e0e9976c7517bbe456326621b Mon Sep 17 00:00:00 2001 From: Anatoliy Klymenko Date: Sun, 3 Dec 2017 19:55:50 -0800 Subject: [PATCH 17/38] Detect PlayReady messages encoding --- .../protection/drm/KeySystem_PlayReady.js | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/app/js/streaming/protection/drm/KeySystem_PlayReady.js b/app/js/streaming/protection/drm/KeySystem_PlayReady.js index da000caa2..f332e6ba8 100644 --- a/app/js/streaming/protection/drm/KeySystem_PlayReady.js +++ b/app/js/streaming/protection/drm/KeySystem_PlayReady.js @@ -40,16 +40,20 @@ MediaPlayer.dependencies.protection.KeySystem_PlayReady = function() { var keySystemStr = "com.microsoft.playready", keySystemUUID = "9a04f079-9840-4286-ab92-e65be0885f95", - messageFormat = "utf16", PRCDMData = '%CUSTOMDATA%', protData, + // expecting data instanceof ArrayBuffer + detectEncoding = function(data) { + return new DataView(data).getUint8(1) ? "utf8" : "utf16"; + }, + getRequestHeaders = function(message) { var msg, xmlDoc, headers = {}, data = (message instanceof ArrayBuffer) ? message : message.buffer, - dataview = (messageFormat === "utf16") ? new Uint16Array(data) : new Uint8Array(data), + dataview = (detectEncoding(data) === "utf16") ? new Uint16Array(data) : new Uint8Array(data), headerNameList, headerValueList, i = 0; @@ -85,10 +89,11 @@ MediaPlayer.dependencies.protection.KeySystem_PlayReady = function() { xmlDoc, licenseRequest = null, data = (message instanceof ArrayBuffer) ? message : message.buffer, - dataview = (messageFormat === "utf16") ? new Uint16Array(data) : new Uint8Array(data), + dataview = (detectEncoding(data) === "utf16") ? new Uint16Array(data) : new Uint8Array(data), Challenge; msg = String.fromCharCode.apply(null, dataview); + xmlDoc = this.domParser.createXmlTree(msg); if (xmlDoc.getElementsByTagName("Challenge")[0]) { @@ -96,7 +101,8 @@ MediaPlayer.dependencies.protection.KeySystem_PlayReady = function() { if (Challenge) { licenseRequest = BASE64.decode(Challenge); } - } else { + } + if (!licenseRequest) { // Some versions of the PlayReady CDM do not return the Microsoft-specified XML structure // but just return the raw license request. If we can't extract the license request, let's // assume it is the latter and just return the whole message. @@ -287,20 +293,6 @@ MediaPlayer.dependencies.protection.KeySystem_PlayReady = function() { getServerCertificate: function () { return null; }, - /** - * It seems that some PlayReady implementations return their XML-based CDM - * messages using UTF16, while others return them as UTF8. Use this function - * to modify the message format to expect when parsing CDM messages. - * - * @param {string} format the expected message format. Either "utf8" or "utf16". - * @throws {Error} Specified message format is not one of "utf8" or "utf16" - */ - setPlayReadyMessageFormat: function(format) { - if (format !== "utf8" && format !== "utf16") { - throw new Error("Illegal PlayReady message format! -- " + format); - } - messageFormat = format; - } }; }; From a4d872923559816b020a888bb84e80fb2c2ff770 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Wed, 31 Jan 2018 11:44:51 +0100 Subject: [PATCH 18/38] add Encoding.js utility file --- app/js/utils/Encoding.js | 73 ++++++++++++++++++++++++++++++++++++++++ build/sources.json | 1 + 2 files changed, 74 insertions(+) create mode 100644 app/js/utils/Encoding.js diff --git a/app/js/utils/Encoding.js b/app/js/utils/Encoding.js new file mode 100644 index 000000000..df5e2e901 --- /dev/null +++ b/app/js/utils/Encoding.js @@ -0,0 +1,73 @@ +/* + * The copyright in this software module is being made available under the BSD License, included below. This software module may be subject to other third party and/or contributor rights, including patent rights, and no such rights are granted under this license. + * The whole software resulting from the execution of this software module together with its external dependent software modules from dash.js project may be subject to Orange and/or other third party rights, including patent rights, and no such rights are granted under this license. + * + * Copyright (c) 2014, Orange + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * • Neither the name of the Orange nor the names of its contributors may be used to endorse or promote products derived from this software module without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** +* UTF-16 (LE or BE) +* +* RFC2781: UTF-16, an encoding of ISO 10646 +* +* @link http://www.ietf.org/rfc/rfc2781.txt +* @private +* @ignore +*/ +MediaPlayer.utils.isUTF16 = function (data) { + var i = 0; + var len = data && data.length; + var pos = null; + var b1, b2, next, prev; + + if (len < 2) { + if (data[0] > 0xFF) { + return false; + } + } else { + b1 = data[0]; + b2 = data[1]; + if (b1 === 0xFF && // BOM (little-endian) + b2 === 0xFE) { + return true; + } + if (b1 === 0xFE && // BOM (big-endian) + b2 === 0xFF) { + return true; + } + + for (; i < len; i++) { + if (data[i] === 0x00) { + pos = i; + break; + } else if (data[i] > 0xFF) { + return false; + } + } + + if (pos === null) { + return false; // Non ASCII + } + + next = data[pos + 1]; // BE + if (next !== void 0 && next > 0x00 && next < 0x80) { + return true; + } + + prev = data[pos - 1]; // LE + if (prev !== void 0 && prev > 0x00 && prev < 0x80) { + return true; + } + } + + return false; +}; diff --git a/build/sources.json b/build/sources.json index b2954bcbe..b24295667 100644 --- a/build/sources.json +++ b/build/sources.json @@ -61,6 +61,7 @@ "../app/js/mss/Mss.js", "../app/js/utils/CopyMethods.js", "../app/js/utils/DOMParser.js", + "../app/js/utils/Encoding.js", "../app/js/utils/ObjectIron.js" ], "protection": [ From 71cb96d21749e70422147b68133465349b7d60ad Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Wed, 31 Jan 2018 11:45:04 +0100 Subject: [PATCH 19/38] KeySystemPlayReady: use MediaPlayer.utils.isUTF16() function to detect PlayReady CDM message format --- app/js/streaming/protection/drm/KeySystem_PlayReady.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/js/streaming/protection/drm/KeySystem_PlayReady.js b/app/js/streaming/protection/drm/KeySystem_PlayReady.js index f332e6ba8..66d710403 100644 --- a/app/js/streaming/protection/drm/KeySystem_PlayReady.js +++ b/app/js/streaming/protection/drm/KeySystem_PlayReady.js @@ -43,17 +43,12 @@ MediaPlayer.dependencies.protection.KeySystem_PlayReady = function() { PRCDMData = '%CUSTOMDATA%', protData, - // expecting data instanceof ArrayBuffer - detectEncoding = function(data) { - return new DataView(data).getUint8(1) ? "utf8" : "utf16"; - }, - getRequestHeaders = function(message) { var msg, xmlDoc, headers = {}, data = (message instanceof ArrayBuffer) ? message : message.buffer, - dataview = (detectEncoding(data) === "utf16") ? new Uint16Array(data) : new Uint8Array(data), + dataview = MediaPlayer.utils.isUTF16(new Uint8Array(data)) ? new Uint16Array(data) : new Uint8Array(data), headerNameList, headerValueList, i = 0; @@ -89,7 +84,7 @@ MediaPlayer.dependencies.protection.KeySystem_PlayReady = function() { xmlDoc, licenseRequest = null, data = (message instanceof ArrayBuffer) ? message : message.buffer, - dataview = (detectEncoding(data) === "utf16") ? new Uint16Array(data) : new Uint8Array(data), + dataview = MediaPlayer.utils.isUTF16(new Uint8Array(data)) ? new Uint16Array(data) : new Uint8Array(data), Challenge; msg = String.fromCharCode.apply(null, dataview); From f01d45740f8b1b391eac4c1333b7c784c485a0c5 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Wed, 31 Jan 2018 11:45:15 +0100 Subject: [PATCH 20/38] TextTTMLXMLMP4SourceBuffer:use MediaPlayer.utils.isUTF16() function to detect TTML data (from mdat) format --- .../captioning/TextTTMLXMLMP4SourceBuffer.js | 62 +------------------ 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js b/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js index 551106f91..78caf21bd 100644 --- a/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js +++ b/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js @@ -152,7 +152,7 @@ MediaPlayer.dependencies.TextTTMLXMLMP4SourceBuffer = function() { this.track = this.textTrackExtensions.addTextTrack(video, [], currentId, currentLang, true); //detect utf-16 encoding - if (self.isUTF16(bytes)) { + if (MediaPlayer.utils.isUTF16(bytes)) { encoding = 'utf-16'; } @@ -229,7 +229,7 @@ MediaPlayer.dependencies.TextTTMLXMLMP4SourceBuffer = function() { } //detect utf-16 encoding - if (self.isUTF16(ttmlData)) { + if (MediaPlayer.utils.isUTF16(ttmlData)) { encoding = 'utf-16'; } // parse data and add to cues @@ -276,64 +276,6 @@ MediaPlayer.dependencies.TextTTMLXMLMP4SourceBuffer = function() { return deferred.promise; }, - /** - * UTF-16 (LE or BE) - * - * RFC2781: UTF-16, an encoding of ISO 10646 - * - * @link http://www.ietf.org/rfc/rfc2781.txt - * @private - * @ignore - */ - isUTF16: function(data) { - var i = 0; - var len = data && data.length; - var pos = null; - var b1, b2, next, prev; - - if (len < 2) { - if (data[0] > 0xFF) { - return false; - } - } else { - b1 = data[0]; - b2 = data[1]; - if (b1 === 0xFF && // BOM (little-endian) - b2 === 0xFE) { - return true; - } - if (b1 === 0xFE && // BOM (big-endian) - b2 === 0xFF) { - return true; - } - - for (; i < len; i++) { - if (data[i] === 0x00) { - pos = i; - break; - } else if (data[i] > 0xFF) { - return false; - } - } - - if (pos === null) { - return false; // Non ASCII - } - - next = data[pos + 1]; // BE - if (next !== void 0 && next > 0x00 && next < 0x80) { - return true; - } - - prev = data[pos - 1]; // LE - if (prev !== void 0 && prev > 0x00 && prev < 0x80) { - return true; - } - } - - return false; - }, - UpdateLang: function(id, lang){ currentId = id; currentLang = lang; From 19bb77a84052c1c55e1a2c6ba29e22deb746f5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Cunat?= Date: Fri, 2 Feb 2018 17:22:09 +0100 Subject: [PATCH 21/38] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 776aa1a76..f02560293 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ jobs: # Build/generate the jsdoc - npm run doc - | - if [ "$TRAVIS_BRANCH" == "master" || "$TRAVIS_BRANCH" == "development" ]; then + if [ "$TRAVIS_BRANCH" == "master" ] || [ "$TRAVIS_BRANCH" == "development" ]; then # Package and deploy version on project's pages openssl aes-256-cbc -K $encrypted_f186f3e7458a_key -iv $encrypted_f186f3e7458a_iv -in travis_deploy.enc -out travis_deploy -d chmod 600 travis_deploy From d5ee260063a7508f6f62a0ab3f555bfaa03228c4 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 30 Jan 2018 15:58:25 +0100 Subject: [PATCH 22/38] start MssFragmentInfoController with a short delay (segment duration) in order to avoid precondition failed response. --- app/js/streaming/Stream.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/js/streaming/Stream.js b/app/js/streaming/Stream.js index 150dc31c4..7f8a05134 100644 --- a/app/js/streaming/Stream.js +++ b/app/js/streaming/Stream.js @@ -433,15 +433,15 @@ MediaPlayer.dependencies.Stream = function() { if (fragmentInfoVideoController && dvrStarted === false) { dvrStarted = true; - fragmentInfoVideoController.start(); + fragmentInfoVideoController.start(videoController.getSegmentDuration()); } if (fragmentInfoAudioController) { - fragmentInfoAudioController.start(); + fragmentInfoAudioController.start(audioController.getSegmentDuration()); } if (fragmentInfoTextController && subtitlesEnabled) { - fragmentInfoTextController.start(); + fragmentInfoTextController.start(textController.getSegmentDuration()); } }, @@ -897,6 +897,10 @@ MediaPlayer.dependencies.Stream = function() { startTime = Math.max(seekTime, videoRange.start); + if (videoRange.end < startTime) { + return; + } + if (audioController) { // Check if audio buffer is not empty audioRange = this.sourceBufferExt.getBufferRange(audioController.getBuffer(), seekTime, audioController.getSegmentDuration()); From 505cb8aab552a4134d211d0513fc0fc5589b605c Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 30 Jan 2018 15:59:18 +0100 Subject: [PATCH 23/38] add only the first entry of tfrf box --- app/js/mss/MssFragmentController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/mss/MssFragmentController.js b/app/js/mss/MssFragmentController.js index 0c358949e..3de63eaed 100644 --- a/app/js/mss/MssFragmentController.js +++ b/app/js/mss/MssFragmentController.js @@ -62,7 +62,7 @@ Mss.dependencies.MssFragmentController = function() { // Get last segment time segmentTime = segments[segments.length - 1].tManifest ? parseFloat(segments[segments.length - 1].tManifest) : segments[segments.length - 1].t; // Check if we have to append new segment to timeline - if (entries[i].fragment_absolute_time > segmentTime) { + if (entries[i].fragment_absolute_time > segmentTime && i === 0) { this.debug.log("[MssFragmentController][" + type + "] Add new segment - t = " + (entries[i].fragment_absolute_time / timescale)); segment = {}; segment.t = entries[i].fragment_absolute_time; From fa429a04bb1432741f7910cc982360c5c5c4d52b Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 30 Jan 2018 16:00:50 +0100 Subject: [PATCH 24/38] MssFragmentInfoController request the last segment, not the penultimate one. --- app/js/mss/MssFragmentInfoController.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/js/mss/MssFragmentInfoController.js b/app/js/mss/MssFragmentInfoController.js index 01b808132..4a0145205 100644 --- a/app/js/mss/MssFragmentInfoController.js +++ b/app/js/mss/MssFragmentInfoController.js @@ -52,16 +52,16 @@ Mss.dependencies.MssFragmentInfoController = function() { return Q.when(null); }, - start = function() { + start = function(delay) { if (!_ready || _started) { return; } this.debug.info("[MssFragmentInfoController][" + _type + "] START"); _started = true; - _startTime = new Date().getTime(); + _startTime = new Date().getTime() + delay * 1000; - loadNextFragmentInfo.call(this); + delayLoadNextFragmentInfo.call(this, delay); }, stop = function() { @@ -86,8 +86,7 @@ Mss.dependencies.MssFragmentInfoController = function() { var adaptation = _bufferController.getData(), segments = adaptation.SegmentTemplate.SegmentTimeline.S_asArray, - // tak before last segment to avoid precondition failed (412) errors - segment = segments[segments.length - 2], + segment = segments[segments.length - 1], representation = adaptation.Representation_asArray[0], request; From 76b00113fccd02545dc68b9b9d8ca90ebec4be1f Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Mon, 5 Feb 2018 15:48:49 +0100 Subject: [PATCH 25/38] MssFragmentController: remove unused code --- app/js/mss/MssFragmentController.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/app/js/mss/MssFragmentController.js b/app/js/mss/MssFragmentController.js index 3de63eaed..8231c10bc 100644 --- a/app/js/mss/MssFragmentController.js +++ b/app/js/mss/MssFragmentController.js @@ -96,30 +96,6 @@ Mss.dependencies.MssFragmentController = function() { return; } - // Update segment timeline in case the timestamps from tfrf differ from timestamps in Manifest. - // In that case we consider tfrf timing - // var j = 0, - // segmentId = -1, - // for (j = segments.length - 1; j >= 0; j -= 1) { - // if (segments[j].t === tfdt.baseMediaDecodeTime) { - // segmentId = j; - // break; - // } - // } - // if (segmentId >= 0) { - // for (i = 0; i < entries.length; i += 1) { - // if (segmentId + i < segments.length) { - // t = segments[segmentId + i].t; - // if ((t + segments[segmentId + i].d) !== entries[i].fragment_absolute_time) { - // segments[segmentId + i].t = entries[i].fragment_absolute_time; - // segments[segmentId + i].d = entries[i].fragment_duration; - // this.debug.log("[MssFragmentController] Correct tfrf time = " + entries[i].fragment_absolute_time + " and duration = " + entries[i].fragment_duration); - // segmentsUpdated = true; - // } - // } - // } - // } - // Update segment timeline according to DVR window if (manifest.timeShiftBufferDepth && manifest.timeShiftBufferDepth > 0) { if (segmentsUpdated) { From 04153f2f259797205241fd0e7350b0203ff6362b Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Mon, 5 Feb 2018 15:55:22 +0100 Subject: [PATCH 26/38] MssFragmentController: simplify processTfrf (only first entry is processed) --- app/js/mss/MssFragmentController.js | 129 ++++++++++++++-------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/app/js/mss/MssFragmentController.js b/app/js/mss/MssFragmentController.js index 8231c10bc..f7395decb 100644 --- a/app/js/mss/MssFragmentController.js +++ b/app/js/mss/MssFragmentController.js @@ -18,71 +18,75 @@ Mss.dependencies.MssFragmentController = function() { var processTfrf = function(request, tfrf, tfdt, adaptation) { var manifest = this.manifestModel.getValue(), - segmentsUpdated = false, segments = adaptation.SegmentTemplate.SegmentTimeline.S_asArray, timescale = adaptation.SegmentTemplate.timescale, entries = tfrf.entry, + entry, segment = null, segmentTime, t = 0, - i = 0, availabilityStartTime = null, type = adaptation.type, range; - // Process tfrf only for live streams + // Process tfrf only for live or start-over streams if (!this.manifestExt.getIsDynamic(manifest) && !this.manifestExt.getIsStartOver(manifest)) { return; } - // Go through tfrf entries + if (entries.length === 0) { + return; + } + + // Consider only first tfrf entry (to avoid pre-condition failure on fragment info requests) + entry = entries[0]; + // !! For tfrf fragment_absolute_time and fragment_duration are returned as goog.math.Long values (see mp4lib) - while (i < entries.length) { - // Check if time is not greater than Number.MAX_SAFE_INTEGER (2^53-1), see MssParser - // => fragment_absolute_timeManifest = original timestamp value as a string (for constructing the fragment request url, see DashHandler) - // => fragment_absolute_time = number value of timestamp (maybe rounded value, but only for 0.1 microsecond) - if (entries[i].fragment_absolute_time.greaterThan(goog.math.Long.fromNumber(Number.MAX_SAFE_INTEGER))) { - entries[i].fragment_absolute_timeManifest = entries[i].fragment_absolute_time.toString(); - } - // Convert goog.math.Long to Number values - entries[i].fragment_absolute_time = entries[i].fragment_absolute_time.toNumber(); - entries[i].fragment_duration = entries[i].fragment_duration.toNumber(); - - // In case of start-over streams, check if we have reached end of original manifest duration (set in timeShiftBufferDepth) - // => then do not update anymore timeline - if (this.manifestExt.getIsStartOver(manifest)) { - // Get first segment time - segmentTime = segments[0].tManifest ? parseFloat(segments[0].tManifest) : segments[0].t; - if (entries[i].fragment_absolute_time > (segmentTime + (manifest.timeShiftBufferDepth * timescale))) { - break; - } - } + // Check if time is not greater than Number.MAX_SAFE_INTEGER (2^53-1), see MssParser + // => fragment_absolute_timeManifest = original timestamp value as a string (for constructing the fragment request url, see DashHandler) + // => fragment_absolute_time = number value of timestamp (maybe rounded value, but only for 0.1 microsecond) + if (entry.fragment_absolute_time.greaterThan(goog.math.Long.fromNumber(Number.MAX_SAFE_INTEGER))) { + entry.fragment_absolute_timeManifest = entry.fragment_absolute_time.toString(); + } - // Get last segment time - segmentTime = segments[segments.length - 1].tManifest ? parseFloat(segments[segments.length - 1].tManifest) : segments[segments.length - 1].t; - // Check if we have to append new segment to timeline - if (entries[i].fragment_absolute_time > segmentTime && i === 0) { - this.debug.log("[MssFragmentController][" + type + "] Add new segment - t = " + (entries[i].fragment_absolute_time / timescale)); - segment = {}; - segment.t = entries[i].fragment_absolute_time; - segment.d = entries[i].fragment_duration; - // If timestamps starts at 0 relative to 1st segment (dynamic to static) then update segment time - if (segments[0].tManifest) { - segment.t -= parseFloat(segments[0].tManifest) - segments[0].t; - } - // Set tManifest either in case of timestamps greater then 2^53 or in case of dynamic to static streams - if (entries[i].fragment_absolute_timeManifest) { - segment.tManifest = entries[i].fragment_absolute_timeManifest; - } else if (segments[0].tManifest) { - segment.tManifest = entries[i].fragment_absolute_time; - } - segments.push(segment); - segmentsUpdated = true; - } + // Convert goog.math.Long to Number values + entry.fragment_absolute_time = entry.fragment_absolute_time.toNumber(); + entry.fragment_duration = entry.fragment_duration.toNumber(); + + // In case of start-over streams, check if we have reached end of original manifest duration (set in timeShiftBufferDepth) + // => then do not update anymore timeline + if (this.manifestExt.getIsStartOver(manifest)) { + // Get first segment time + segmentTime = segments[0].tManifest ? parseFloat(segments[0].tManifest) : segments[0].t; + if (entry.fragment_absolute_time > (segmentTime + (manifest.timeShiftBufferDepth * timescale))) { + return; + } + } + + // Get last segment time + segmentTime = segments[segments.length - 1].tManifest ? parseFloat(segments[segments.length - 1].tManifest) : segments[segments.length - 1].t; + + // Check if we have to append new segment to timeline + if (entry.fragment_absolute_time <= segmentTime) { + return; + } - i += 1; + this.debug.log("[MssFragmentController][" + type + "] Add new segment - t = " + (entry.fragment_absolute_time / timescale)); + segment = {}; + segment.t = entry.fragment_absolute_time; + segment.d = entry.fragment_duration; + // If timestamps starts at 0 relative to 1st segment (dynamic to static) then update segment time + if (segments[0].tManifest) { + segment.t -= parseFloat(segments[0].tManifest) - segments[0].t; + } + // Set tManifest either in case of timestamps greater then 2^53 or in case of dynamic to static streams + if (entry.fragment_absolute_timeManifest) { + segment.tManifest = entry.fragment_absolute_timeManifest; + } else if (segments[0].tManifest) { + segment.tManifest = entry.fragment_absolute_time; } + segments.push(segment); // In case of static start-over streams, update content duration if (this.manifestExt.getIsStartOver(manifest)) { @@ -95,24 +99,21 @@ Mss.dependencies.MssFragmentController = function() { } return; } - - // Update segment timeline according to DVR window - if (manifest.timeShiftBufferDepth && manifest.timeShiftBufferDepth > 0) { - if (segmentsUpdated) { - // Get timestamp of the last segment - segment = segments[segments.length - 1]; - t = segment.t; - - // Determine the segments' availability start time - availabilityStartTime = t - (manifest.timeShiftBufferDepth * timescale); - - // Remove segments prior to availability start time + // In case of live streams, update segment timeline according to DVR window + else if (manifest.timeShiftBufferDepth && manifest.timeShiftBufferDepth > 0) { + // Get timestamp of the last segment + segment = segments[segments.length - 1]; + t = segment.t; + + // Determine the segments' availability start time + availabilityStartTime = t - (manifest.timeShiftBufferDepth * timescale); + + // Remove segments prior to availability start time + segment = segments[0]; + while (segment.t < availabilityStartTime) { + this.debug.log("[MssFragmentController][" + type + "] Remove segment - t = " + (segment.t / timescale)); + segments.splice(0, 1); segment = segments[0]; - while (segment.t < availabilityStartTime) { - this.debug.log("[MssFragmentController][" + type + "] Remove segment - t = " + (segment.t / timescale)); - segments.splice(0, 1); - segment = segments[0]; - } } // Update DVR window range => set range's end to end time of current segment @@ -330,8 +331,8 @@ Mss.dependencies.MssFragmentController = function() { traf.boxes.splice(pos + 1, 0, tfdt); } - // Process tfrf box if (manifest.type === 'dynamic') { + // Process tfrf box tfrf = traf.getBoxesByType("tfrf"); if (tfrf === null || tfrf.length === 0) { throw { From f38afa35e696d337b94182dadfef0d13a07d74ae Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Mon, 5 Feb 2018 16:02:14 +0100 Subject: [PATCH 27/38] MssFragmentInfoController: process only first tfrf boxes --- app/js/mss/MssFragmentController.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/js/mss/MssFragmentController.js b/app/js/mss/MssFragmentController.js index f7395decb..4ecc9ba74 100644 --- a/app/js/mss/MssFragmentController.js +++ b/app/js/mss/MssFragmentController.js @@ -165,9 +165,7 @@ Mss.dependencies.MssFragmentController = function() { } }; } else { - for (i = 0; i < tfrf.length; i += 1) { - processTfrf.call(this, request, tfrf[i], tfdt, adaptation); - } + processTfrf.call(this, request, tfrf[0], tfdt, adaptation); } }, @@ -343,10 +341,8 @@ Mss.dependencies.MssFragmentController = function() { } }; } else { - for (i = 0; i < tfrf.length; i += 1) { - processTfrf.call(this, request, tfrf[i], tfdt, adaptation); - traf.removeBoxByType("tfrf"); - } + processTfrf.call(this, request, tfrf[0], tfdt, adaptation); + traf.removeBoxByType("tfrf"); } } From e3e9c974aec13fde0bf0ec0f83129bd2652d1d40 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Mon, 5 Feb 2018 16:43:23 +0100 Subject: [PATCH 28/38] TextTTMLXMLMP4SourceBuffer: send warning in case of TTML parsing error --- .../streaming/captioning/TextTTMLXMLMP4SourceBuffer.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js b/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js index 78caf21bd..be8c4d18a 100644 --- a/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js +++ b/app/js/streaming/captioning/TextTTMLXMLMP4SourceBuffer.js @@ -98,6 +98,7 @@ MediaPlayer.dependencies.TextTTMLXMLMP4SourceBuffer = function() { ttmlParser: undefined, debug: undefined, manifestModel: undefined, + errHandler: undefined, initialize: function(type, bufferController, subtitleData) { mimeType = type; @@ -167,8 +168,8 @@ MediaPlayer.dependencies.TextTTMLXMLMP4SourceBuffer = function() { type: "updateend" }); } - }, function( /*error*/ ) { - //self.debug.error("[TextTTMLXMLMP4SourceBuffer] error parsing TTML "+error); + }, function(error) { + self.errHandler.sendWarning(MediaPlayer.dependencies.ErrorHandler.prototype.INTERNAL_ERROR, "Internal error while parsing TTML data", error); }); }); return; @@ -253,8 +254,8 @@ MediaPlayer.dependencies.TextTTMLXMLMP4SourceBuffer = function() { type: "updateend" }); } - }, function( /*error*/ ) { - //self.debug.error("[TextTTMLXMLMP4SourceBuffer] error parsing TTML "+error); + }, function(error) { + self.errHandler.sendWarning(MediaPlayer.dependencies.ErrorHandler.prototype.INTERNAL_ERROR, "Internal error while parsing TTML data", error); }); }); } From 0fdf697b465a3cc04dd9401705d99a27576c062c Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Tue, 6 Feb 2018 14:48:04 +0100 Subject: [PATCH 29/38] Avoid seeking at end of stream (= duration), seek 2sec backward --- app/js/streaming/Stream.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/js/streaming/Stream.js b/app/js/streaming/Stream.js index 7f8a05134..4c4e4c7a8 100644 --- a/app/js/streaming/Stream.js +++ b/app/js/streaming/Stream.js @@ -568,7 +568,8 @@ MediaPlayer.dependencies.Stream = function() { }, onSeeking = function() { - var time = this.videoModel.getCurrentTime(); + var time = this.videoModel.getCurrentTime(), + duration = this.videoModel.getDuration(); this.debug.info("[Stream]