senza-sdk 4.4.1-4ca75a1.0 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.js +1 -1
- package/dist/bundle.js.LICENSE.txt +3 -10
- package/dist/implementation.bundle.js +2 -0
- package/dist/implementation.bundle.js.LICENSE.txt +82 -0
- package/package.json +6 -6
- package/src/api.js +3 -0
- package/src/implementation/lifecycle.js +3 -2
- package/src/implementation/remotePlayer.js +66 -25
- package/src/implementation/senzaShakaPlayer.js +121 -65
- package/src/implementation/utils.js +0 -206
- package/src/interface/remotePlayer.js +45 -2
- package/src/interface/senzaShakaPlayer.js +1 -2
- package/src/interface/version.js +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@license
|
|
3
|
+
Copyright 2006 The Closure Library Authors
|
|
4
|
+
SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
@license
|
|
9
|
+
Copyright 2008 The Closure Library Authors
|
|
10
|
+
SPDX-License-Identifier: Apache-2.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
@license
|
|
15
|
+
Copyright 2013 Ali Al Dallal
|
|
16
|
+
|
|
17
|
+
Licensed under the MIT license.
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
20
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
21
|
+
in the Software without restriction, including without limitation the rights
|
|
22
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
23
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
24
|
+
furnished to do so, subject to the following conditions:
|
|
25
|
+
|
|
26
|
+
The above copyright notice and this permission notice shall be included in
|
|
27
|
+
all copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
@license
|
|
40
|
+
MSS Transmuxer
|
|
41
|
+
Copyright 2015 Dash Industry Forum
|
|
42
|
+
SPDX-License-Identifier: BSD-3-Clause
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
@license
|
|
47
|
+
Shaka Player
|
|
48
|
+
Copyright 2016 Google LLC
|
|
49
|
+
SPDX-License-Identifier: Apache-2.0
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
@license
|
|
54
|
+
Shaka Player
|
|
55
|
+
Copyright 2022 Google LLC
|
|
56
|
+
SPDX-License-Identifier: Apache-2.0
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
@license
|
|
61
|
+
Shaka Player
|
|
62
|
+
Copyright 2023 Google LLC
|
|
63
|
+
SPDX-License-Identifier: Apache-2.0
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
@license
|
|
68
|
+
Shaka Player
|
|
69
|
+
Copyright 2025 Google LLC
|
|
70
|
+
SPDX-License-Identifier: Apache-2.0
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
@license
|
|
75
|
+
tXml
|
|
76
|
+
Copyright 2015 Tobias Nickel
|
|
77
|
+
SPDX-License-Identifier: MIT
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
//! moment.js
|
|
81
|
+
|
|
82
|
+
//! moment.js locale configuration
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senza-sdk",
|
|
3
|
-
"version": "4.4.1
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"main": "./src/api.js",
|
|
5
5
|
"description": "API for Senza application",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
"statements": 93.46
|
|
54
54
|
},
|
|
55
55
|
"src/interface": {
|
|
56
|
-
"branches":
|
|
57
|
-
"functions": 39.
|
|
58
|
-
"lines": 64.
|
|
59
|
-
"statements": 65
|
|
56
|
+
"branches": 63.9,
|
|
57
|
+
"functions": 39.28,
|
|
58
|
+
"lines": 64.61,
|
|
59
|
+
"statements": 65
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"shaka-player": "
|
|
64
|
+
"shaka-player": "4.15.8",
|
|
65
65
|
"moment": "^2.30.1"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/api.js
CHANGED
|
@@ -308,6 +308,9 @@ let ShakaPlayerImplementation;
|
|
|
308
308
|
let shakaImplementation;
|
|
309
309
|
|
|
310
310
|
if (!isRunningE2E()) {
|
|
311
|
+
// ------------------------------------------------------------------------------------------------
|
|
312
|
+
// -- Do NOT change this log, as ui-streamer is checking this string to detect abnormal behavior --
|
|
313
|
+
// ------------------------------------------------------------------------------------------------
|
|
311
314
|
sdkLogger.warn("Senza SDK is not running in E2E environment. Some features may not work as expected.");
|
|
312
315
|
|
|
313
316
|
lifecycle = new Lifecycle();
|
|
@@ -559,7 +559,8 @@ class Lifecycle extends LifecycleInterface {
|
|
|
559
559
|
action: "play",
|
|
560
560
|
fcid: FCID,
|
|
561
561
|
audioLanguage,
|
|
562
|
-
subtitlesLanguage
|
|
562
|
+
subtitlesLanguage,
|
|
563
|
+
autoTune: remotePlayer._autoTune
|
|
563
564
|
};
|
|
564
565
|
if (this._remotePlayerApiVersion >= 2) {
|
|
565
566
|
message.type = "remotePlayer.play";
|
|
@@ -579,7 +580,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
579
580
|
}
|
|
580
581
|
request = message;
|
|
581
582
|
}
|
|
582
|
-
logger.log(`lifecycle moveToBackground: sending play action audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${remotePlayer.textTrackVisibility}`);
|
|
583
|
+
logger.log(`lifecycle moveToBackground: sending play action audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${remotePlayer.textTrackVisibility} autoTune=${remotePlayer._autoTune}`);
|
|
583
584
|
let timerId = 0;
|
|
584
585
|
const timeBeforeSendingRequest = Date.now();
|
|
585
586
|
const queryId = window.cefQuery({
|
|
@@ -183,6 +183,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
this._loadMode = playerState?.isLoaded ? this.LoadMode.LOADED : this.LoadMode.NOT_LOADED;
|
|
186
|
+
this._autoTune = false;
|
|
186
187
|
|
|
187
188
|
this._isPlaying = window?.sessionStorage?.getItem("senzaSdk_isPlaying") === "true";
|
|
188
189
|
|
|
@@ -196,6 +197,8 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
196
197
|
this._remotePlayerConfirmationTimeout = uiStreamerSettings?.remotePlayerConfirmationTimeout ?? DEFAULT_REMOTE_PLAYER_CONFIRMATION_TIMEOUT;
|
|
197
198
|
this._remotePlayerApiVersion = uiStreamerSettings?.remotePlayerApiVersion || 1;
|
|
198
199
|
this._multiSeekDelay = uiStreamerSettings?.multiSeekDelay || MULTI_SEEK_DELAY_MSEC;
|
|
200
|
+
this._uiAvSyncIntervalMs = uiStreamerSettings?.uiAvSyncIntervalMs;
|
|
201
|
+
this._abrAvSyncIntervalMs = uiStreamerSettings?.abrAvSyncIntervalMs;
|
|
199
202
|
this._deviceAppVersion = sessionObj?.manifest?.["device-app"] || "";
|
|
200
203
|
|
|
201
204
|
sdkLogger.info(`remotePLayer isPlaying=${this._isPlaying}`);
|
|
@@ -208,6 +211,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
208
211
|
}
|
|
209
212
|
this._availabilityStartTime = playbackMetadata.availabilityStartTime;
|
|
210
213
|
this._updateTracks(playbackMetadata);
|
|
214
|
+
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
/**
|
|
@@ -785,6 +789,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
785
789
|
playbackPosition,
|
|
786
790
|
fcid: FCID
|
|
787
791
|
};
|
|
792
|
+
if (this._uiAvSyncIntervalMs !== undefined) {
|
|
793
|
+
message.uiAvSyncIntervalMs = this._uiAvSyncIntervalMs;
|
|
794
|
+
}
|
|
795
|
+
if (this._abrAvSyncIntervalMs !== undefined) {
|
|
796
|
+
message.abrAvSyncIntervalMs = this._abrAvSyncIntervalMs;
|
|
797
|
+
}
|
|
788
798
|
if (this._remotePlayerApiVersion >= 2) {
|
|
789
799
|
message.type = "remotePlayer.load";
|
|
790
800
|
message.class = "remotePlayer";
|
|
@@ -924,10 +934,11 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
924
934
|
* remotePlayer.play();
|
|
925
935
|
* lifecycle.moveToBackground();
|
|
926
936
|
*
|
|
937
|
+
* @param {boolean} [autoTune=false] Should be true when the play was triggered automatically by the app and not by a user action.
|
|
927
938
|
* @returns {Promise}
|
|
928
939
|
* @throws {RemotePlayerError} error object contains code & msg
|
|
929
940
|
*/
|
|
930
|
-
play() {
|
|
941
|
+
play(autoTune = false) {
|
|
931
942
|
if (!this._isInitialized) {
|
|
932
943
|
throw new RemotePlayerError(6500, "Cannot call play() if remote player is not initialized");
|
|
933
944
|
}
|
|
@@ -938,6 +949,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
938
949
|
}
|
|
939
950
|
|
|
940
951
|
this._changePlayMode(true);
|
|
952
|
+
this._autoTune = autoTune;
|
|
941
953
|
|
|
942
954
|
if (this._isSetAudioInProgress) {
|
|
943
955
|
sdkLogger.info("application requesting play during setAudioLanguage");
|
|
@@ -1091,24 +1103,30 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1091
1103
|
* @throws {RemotePlayerError} If the player is not initialized or not loaded.
|
|
1092
1104
|
**/
|
|
1093
1105
|
selectAudioTrack(audioTrackId) {
|
|
1106
|
+
return this._selectAudioTrack(audioTrackId, false);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
_selectAudioTrack(audioTrackId, force = false) {
|
|
1094
1110
|
if (!this._isInitialized) {
|
|
1095
1111
|
throw new RemotePlayerError(6500, "Cannot call selectAudioTrack() if remote player is not initialized");
|
|
1096
1112
|
}
|
|
1097
1113
|
if (this._loadMode !== this.LoadMode.LOADED) {
|
|
1098
1114
|
throw new RemotePlayerError(6001, "Cannot call selectAudioTrack() if remote player is not loaded");
|
|
1099
1115
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1116
|
+
if (!force) {
|
|
1117
|
+
let found = false;
|
|
1118
|
+
for (const track of this.getAudioTracks()) {
|
|
1119
|
+
if (track.id === audioTrackId) {
|
|
1120
|
+
found = true;
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
if (!found) {
|
|
1125
|
+
sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
|
|
1126
|
+
return Promise.resolve();
|
|
1106
1127
|
}
|
|
1107
1128
|
}
|
|
1108
|
-
|
|
1109
|
-
sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
|
|
1110
|
-
return Promise.resolve();
|
|
1111
|
-
}
|
|
1129
|
+
const prevSelectedAudioTrack = this._selectedAudioTrack;
|
|
1112
1130
|
if (this._selectedAudioTrack === audioTrackId) {
|
|
1113
1131
|
return Promise.resolve(); // Audio language already selected
|
|
1114
1132
|
}
|
|
@@ -1125,6 +1143,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1125
1143
|
}
|
|
1126
1144
|
}
|
|
1127
1145
|
|
|
1146
|
+
async selectAudioLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
|
|
1147
|
+
const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
|
|
1148
|
+
return this._selectAudioTrack(trackId, true);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
|
|
1128
1152
|
/**
|
|
1129
1153
|
* Handles the asynchronous selection of an audio track.
|
|
1130
1154
|
* If the player is playing, it pauses before changing the track and resumes playback if necessary.
|
|
@@ -1218,6 +1242,11 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1218
1242
|
return Promise.resolve(undefined);
|
|
1219
1243
|
}
|
|
1220
1244
|
|
|
1245
|
+
async selectTextLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
|
|
1246
|
+
const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
|
|
1247
|
+
return this._selectTextTrack(trackId, true);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1221
1250
|
/** Select a specific text (subtitle) track.
|
|
1222
1251
|
* Track id should come from a call to getTextTracks.
|
|
1223
1252
|
* If no tracks exist - this is a no-op.
|
|
@@ -1227,24 +1256,31 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1227
1256
|
* @throws {RemotePlayerError} If the player is not initialized or not loaded.
|
|
1228
1257
|
* */
|
|
1229
1258
|
selectTextTrack(textTrackId) {
|
|
1259
|
+
return this._selectTextTrack(textTrackId, false);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
_selectTextTrack(textTrackId, force = false) {
|
|
1230
1263
|
if (!this._isInitialized) {
|
|
1231
1264
|
throw new RemotePlayerError(6500, "Cannot call selectTextTrack() if remote player is not initialized");
|
|
1232
1265
|
}
|
|
1233
1266
|
if (this._loadMode !== this.LoadMode.LOADED) {
|
|
1234
1267
|
throw new RemotePlayerError(6001, "Cannot call selectTextTrack() if remote player is not loaded");
|
|
1235
1268
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1269
|
+
if (!force) {
|
|
1270
|
+
let found = false;
|
|
1271
|
+
for (const track of this.getTextTracks()) {
|
|
1272
|
+
if (track.id === textTrackId) {
|
|
1273
|
+
found = true;
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (!found) {
|
|
1278
|
+
sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
|
|
1279
|
+
return Promise.resolve();
|
|
1242
1280
|
}
|
|
1243
1281
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
return Promise.resolve();
|
|
1247
|
-
}
|
|
1282
|
+
|
|
1283
|
+
const prevSelectedTextTrack = this._selectedSubtitlesTrack;
|
|
1248
1284
|
if (this._selectedSubtitlesTrack === textTrackId) {
|
|
1249
1285
|
return Promise.resolve(); // Subtitle language already selected
|
|
1250
1286
|
}
|
|
@@ -1640,11 +1676,10 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1640
1676
|
// In case where we aborted (new load or unload called), we don't want to resume playback.
|
|
1641
1677
|
if (!this._abortSeeking) {
|
|
1642
1678
|
if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
|
|
1643
|
-
if
|
|
1644
|
-
|
|
1679
|
+
// When in foreground back from ABR, we are considered playing. But, if AIU is not enabled, we shouldn't resume it.
|
|
1680
|
+
if (this._isAudioSyncEnabled()) {
|
|
1681
|
+
this._play(StreamType.AUDIO);
|
|
1645
1682
|
}
|
|
1646
|
-
// resume audio play only if _isAudioSyncEnabled
|
|
1647
|
-
this._play(StreamType.AUDIO);
|
|
1648
1683
|
} else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
|
|
1649
1684
|
lifecycle._moveToBackground();
|
|
1650
1685
|
}
|
|
@@ -1788,6 +1823,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1788
1823
|
return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
|
|
1789
1824
|
}
|
|
1790
1825
|
|
|
1826
|
+
_generateSenzaTrackId(language, role, accessibilityPurpose) {
|
|
1827
|
+
const l = language || "";
|
|
1828
|
+
const r = role || "";
|
|
1829
|
+
const ap = accessibilityPurpose || "";
|
|
1830
|
+
return `${l}:${r}:${ap}`;
|
|
1831
|
+
}
|
|
1791
1832
|
_setScreenBlackout(blackoutTime) {
|
|
1792
1833
|
if (window.cefQuery) {
|
|
1793
1834
|
const FCID = getFCID();
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { SenzaShakaPlayer as SenzaShakaInterface, shaka } from "../interface/senzaShakaPlayer";
|
|
2
|
-
|
|
2
|
+
import * as shakaDebug from "shaka-player/dist/shaka-player.compiled.debug.js";
|
|
3
3
|
import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
|
|
4
|
-
import { sdkLogger
|
|
4
|
+
import { sdkLogger } from "./utils";
|
|
5
5
|
import moment from "moment";
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
// Define custom error category
|
|
9
8
|
shaka.util.Error.Category.SENZA_PLAYER_ERROR = 50;
|
|
10
9
|
shaka.util.Error.Code.SENZA_PLAYER_ERROR = 10500;
|
|
@@ -32,7 +31,6 @@ class SenzaError extends shaka.util.Error {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
36
34
|
export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
37
35
|
/** @private {SenzaShakaPlayer|null} Previous instance of the player */
|
|
38
36
|
static _prevInstance = null;
|
|
@@ -134,7 +132,7 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
134
132
|
this.videoElement.play();
|
|
135
133
|
}
|
|
136
134
|
},
|
|
137
|
-
"seeked"
|
|
135
|
+
"seeked": () => {
|
|
138
136
|
// In case of background seek - we need to pause the local player after "seeked" until remote player is "playing" to avoid difference between the players.
|
|
139
137
|
if (this.isInRemotePlayback) {
|
|
140
138
|
this._pauseForDelayedSeek = true;
|
|
@@ -164,57 +162,57 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
164
162
|
},
|
|
165
163
|
"license-request": async (event) => {
|
|
166
164
|
sdkLogger.log("remotePlayer", "license-request", "Got license-request event from remote player");
|
|
165
|
+
let responseBody = "Unknown Error";
|
|
166
|
+
let status = 500;
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
const requestBuffer = event?.detail?.licenseRequest;
|
|
170
|
-
const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
|
|
171
|
-
const decodedLicenseRequest = window.atob(requestBufferStr); // Decode from base64
|
|
172
|
-
const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
|
|
173
|
-
|
|
174
|
-
const request = {
|
|
175
|
-
body: licenseRequestBytes.buffer,
|
|
176
|
-
uris: [this.getConfiguration().drm.servers["com.widevine.alpha"]], // TODO: safe gaurd against undefined and other server types
|
|
177
|
-
method: "POST",
|
|
178
|
-
originatesFromRemotePlayer: true
|
|
179
|
-
};
|
|
168
|
+
try {
|
|
180
169
|
|
|
181
|
-
|
|
170
|
+
if (!this?.getConfiguration()?.drm?.servers?.["com.widevine.alpha"]) {
|
|
171
|
+
status = 400;
|
|
172
|
+
responseBody = "Missing Widevine license server";
|
|
173
|
+
sdkLogger.warn("remotePlayer", "license-request", "missing Widevine license server");
|
|
174
|
+
return; // the finally will send the response
|
|
175
|
+
}
|
|
176
|
+
// Extract license body from event
|
|
177
|
+
const requestBuffer = event?.detail?.licenseRequest;
|
|
178
|
+
if (!requestBuffer) {
|
|
179
|
+
status = 400;
|
|
180
|
+
responseBody = "Missing license request buffer";
|
|
181
|
+
sdkLogger.warn("remotePlayer", "license-request", "missing license request buffer");
|
|
182
|
+
return; // the finally will send the response
|
|
183
|
+
}
|
|
184
|
+
const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
|
|
185
|
+
const decodedLicenseRequest = window.atob(requestBufferStr); // Decode from base64
|
|
186
|
+
const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
|
|
182
187
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
sdkLogger.log("remotePlayer", "license-request", "Writing response to remote player", response.status);
|
|
190
|
-
event.writeLicenseResponse(response.status, responseBody);
|
|
188
|
+
const request = {
|
|
189
|
+
body: licenseRequestBytes.buffer,
|
|
190
|
+
uris: [this.getConfiguration().drm.servers["com.widevine.alpha"]],
|
|
191
|
+
method: "POST",
|
|
192
|
+
originatesFromRemotePlayer: true
|
|
193
|
+
};
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
"tracksupdate": () => {
|
|
194
|
-
this._audioTracksMap = {};
|
|
195
|
-
this._textTracksMap = {};
|
|
196
|
-
// create a map between language code and track id to be used for selection. If the language appears more than once the last one will be taken.
|
|
197
|
-
const audioTracks = remotePlayer.getAudioTracks();
|
|
198
|
-
for (const track of audioTracks) {
|
|
199
|
-
let lang = track.lang;
|
|
200
|
-
|
|
201
|
-
if (lang.length === 3) {
|
|
202
|
-
lang = iso6393to1[lang] || lang;
|
|
203
|
-
}
|
|
204
|
-
this._audioTracksMap[lang] = track.id;
|
|
205
|
-
}
|
|
195
|
+
const response = await this.getNetworkingEngine().request(shaka.net.NetworkingEngine.RequestType.LICENSE, request).promise;
|
|
206
196
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!track.autoTranslate) {
|
|
210
|
-
let lang = track.lang;
|
|
197
|
+
responseBody = response.data;
|
|
198
|
+
status = response.status;
|
|
211
199
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this._textTracksMap[lang] = track.id;
|
|
200
|
+
if (status < 200 || status >= 300) {
|
|
201
|
+
responseBody = response.data ?? String.fromCharCode(new Uint8Array(response.data));
|
|
202
|
+
sdkLogger.error("remotePlayer", "license-request", "failed to to get response from widevine:", status, responseBody);
|
|
216
203
|
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
sdkLogger.error("remotePlayer", "license-request", "License request failed:", error);
|
|
206
|
+
// For network errors, the error.data array contains [uri, status, responseText, ...]
|
|
207
|
+
status = error?.data?.[1] ?? 500;
|
|
208
|
+
responseBody = error?.data?.[2] ?? `Error response: ${error.message || error.toString()}`;
|
|
209
|
+
sdkLogger.error("remotePlayer", "license-request", "failed to get response from widevine:", status, responseBody);
|
|
210
|
+
} finally {
|
|
211
|
+
// Write response to remote player
|
|
212
|
+
sdkLogger.log("remotePlayer", "license-request", "Writing response to remote player", status);
|
|
213
|
+
event.writeLicenseResponse(status, responseBody);
|
|
217
214
|
}
|
|
215
|
+
|
|
218
216
|
},
|
|
219
217
|
"playing": async () => {
|
|
220
218
|
sdkLogger.info("remotePlayer playing event received");
|
|
@@ -453,8 +451,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
453
451
|
}
|
|
454
452
|
|
|
455
453
|
await super.detach(keepAdManager);
|
|
456
|
-
this._audioTracksMap = {};
|
|
457
|
-
this._textTracksMap = {};
|
|
458
454
|
this._removeVideoElementEventListeners();
|
|
459
455
|
try {
|
|
460
456
|
if (remotePlayer.getAssetUri() !== "") {
|
|
@@ -484,30 +480,65 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
484
480
|
}
|
|
485
481
|
|
|
486
482
|
getTextLanguages() {
|
|
487
|
-
|
|
483
|
+
const tracks = this.remotePlayer.getTextTracks();
|
|
484
|
+
return [...new Set(tracks.map(item => item.lang))];
|
|
488
485
|
}
|
|
489
486
|
|
|
490
487
|
getAudioLanguages() {
|
|
491
|
-
|
|
488
|
+
const tracks = this.remotePlayer.getAudioTracks();
|
|
489
|
+
return [...new Set(tracks.map(item => item.lang))];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
selectVariantTrack(track, clearBuffer = false, safeMargin = 0) {
|
|
494
|
+
const audioLang = track.language;
|
|
495
|
+
const audioRole = Array.isArray(track.audioRoles) && track.audioRoles.length > 0
|
|
496
|
+
? track.audioRoles[0]
|
|
497
|
+
: null;
|
|
498
|
+
const audioId = track.audioId;
|
|
499
|
+
sdkLogger.log(`selectVariantTrack() called: audio id ${audioId} language ${audioLang}, role ${audioRole}, accessibilityPurpose: ${track.accessibilityPurpose}`);
|
|
500
|
+
|
|
501
|
+
const apCode = this._getAccessibilityCodeFromPurpose(track.accessibilityPurpose);
|
|
502
|
+
remotePlayer.selectAudioLanguage(audioLang, audioRole, apCode);
|
|
503
|
+
|
|
504
|
+
super.selectVariantTrack(track, clearBuffer, safeMargin);
|
|
505
|
+
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
selectAudioTrack(audioTrack, safeMargin = 0) {
|
|
509
|
+
const audioId = audioTrack.id;
|
|
510
|
+
const audioLang = audioTrack.language;
|
|
511
|
+
const role = Array.isArray(audioTrack.roles) && audioTrack.roles.length > 0
|
|
512
|
+
? audioTrack.roles[0]
|
|
513
|
+
: null;
|
|
514
|
+
sdkLogger.log(`selectAudioTrack() called: audio id ${audioId} language ${audioLang}, role ${role}, accessibilityPurpose: ${audioTrack.accessibilityPurpose}`);
|
|
515
|
+
const apCode = this._getAccessibilityCodeFromPurpose(audioTrack.accessibilityPurpose);
|
|
516
|
+
remotePlayer.selectAudioLanguage(audioLang, role, apCode);
|
|
517
|
+
|
|
518
|
+
super.selectAudioTrack(audioTrack, safeMargin);
|
|
492
519
|
}
|
|
493
520
|
|
|
494
521
|
selectAudioLanguage(language, role) {
|
|
495
|
-
sdkLogger.log("Selecting audio language:", language, "with role: ", role);
|
|
496
|
-
|
|
497
|
-
remotePlayer.selectAudioTrack(this._audioTracksMap[language]);
|
|
498
|
-
} else {
|
|
499
|
-
sdkLogger.warn(`Language ${language} not found in audio tracks map`);
|
|
500
|
-
}
|
|
522
|
+
sdkLogger.log("selectAudioLanguage() Selecting audio language:", language, "with role: ", role);
|
|
523
|
+
remotePlayer.selectAudioLanguage(language, role);
|
|
501
524
|
super.selectAudioLanguage(language, role);
|
|
502
525
|
}
|
|
503
526
|
|
|
527
|
+
selectTextTrack(textTrack) {
|
|
528
|
+
const textId = textTrack.id;
|
|
529
|
+
const textLang = textTrack.language;
|
|
530
|
+
const role = Array.isArray(textTrack.roles) && textTrack.roles.length > 0
|
|
531
|
+
? textTrack.roles[0]
|
|
532
|
+
: null;
|
|
533
|
+
sdkLogger.log(`selectTextTrack() called: text id ${textId} language ${textLang}, role ${role}, accessibilityPurpose: ${textTrack.accessibilityPurpose}`);
|
|
534
|
+
const apCode = this._getAccessibilityCodeFromPurpose(textTrack.accessibilityPurpose);
|
|
535
|
+
remotePlayer.selectTextLanguage(textLang, role, apCode);
|
|
536
|
+
|
|
537
|
+
super.selectTextTrack(textTrack);
|
|
538
|
+
}
|
|
504
539
|
selectTextLanguage(language, role) {
|
|
505
|
-
sdkLogger.log("Selecting text language:", language, "with role:", role);
|
|
506
|
-
|
|
507
|
-
remotePlayer.selectTextTrack(this._textTracksMap[language]);
|
|
508
|
-
} else {
|
|
509
|
-
sdkLogger.warn(`Language ${language} not found in text tracks map`);
|
|
510
|
-
}
|
|
540
|
+
sdkLogger.log("selectTextLanguage() Selecting text language:", language, "with role: ", role);
|
|
541
|
+
remotePlayer.selectTextLanguage(language, role);
|
|
511
542
|
super.selectTextLanguage(language, role);
|
|
512
543
|
}
|
|
513
544
|
|
|
@@ -734,7 +765,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
734
765
|
};
|
|
735
766
|
|
|
736
767
|
if (!this.isInRemotePlayback || remotePlayer.getAssetUri() !== url) {
|
|
737
|
-
this._audioTracksMap = {};
|
|
738
768
|
this._videoTracksMap = {};
|
|
739
769
|
try {
|
|
740
770
|
if (this.remotePlayer.getLoadMode() === this.remotePlayer.LoadMode.UNLOADING) {
|
|
@@ -789,6 +819,32 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
789
819
|
}
|
|
790
820
|
}
|
|
791
821
|
|
|
822
|
+
/**
|
|
823
|
+
* Returns accessibility code of Shaka's accessibility purpose string
|
|
824
|
+
* @param {string} purpose
|
|
825
|
+
* @returns {null|string}
|
|
826
|
+
* @private
|
|
827
|
+
*/
|
|
828
|
+
_getAccessibilityCodeFromPurpose(purpose) {
|
|
829
|
+
switch (purpose) {
|
|
830
|
+
case shakaDebug.media.ManifestParser.AccessibilityPurpose.VISUALLY_IMPAIRED:
|
|
831
|
+
return remotePlayer.AccessibilityPurposeCode.VISUALLY_IMPAIRED;
|
|
832
|
+
case shakaDebug.media.ManifestParser.AccessibilityPurpose.HARD_OF_HEARING:
|
|
833
|
+
return remotePlayer.AccessibilityPurposeCode.HARD_OF_HEARING;
|
|
834
|
+
case shakaDebug.media.ManifestParser.AccessibilityPurpose.SPOKEN_SUBTITLES:
|
|
835
|
+
return remotePlayer.AccessibilityPurposeCode.SPOKEN_SUBTITLES;
|
|
836
|
+
case undefined:
|
|
837
|
+
case null:
|
|
838
|
+
case "":
|
|
839
|
+
return remotePlayer.AccessibilityPurposeCode.NONE;
|
|
840
|
+
default:
|
|
841
|
+
sdkLogger.warn(`_getAccessibilityCodeFromPurpose(): unknown accessibilityPurpose ${purpose}`);
|
|
842
|
+
return remotePlayer.AccessibilityPurposeCode.NONE;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
|
|
792
848
|
}
|
|
793
849
|
|
|
794
850
|
shaka.Player = SenzaShakaPlayer;
|