senza-sdk 4.1.5 → 4.2.0
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 +2 -1
- package/dist/bundle.js.LICENSE.txt +53 -0
- package/package.json +6 -3
- package/src/api.js +3 -0
- package/src/devHelper.js +4 -13
- package/src/remotePlayer.js +3 -3
- package/src/senzaShakaPlayer.js +152 -0
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
EME Encryption Scheme Polyfill
|
|
16
|
+
Copyright 2019 Google LLC
|
|
17
|
+
SPDX-License-Identifier: Apache-2.0
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
@license
|
|
22
|
+
MSS Transmuxer
|
|
23
|
+
Copyright 2015 Dash Industry Forum
|
|
24
|
+
SPDX-License-Identifier: BSD-3-Clause
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
@license
|
|
29
|
+
Shaka Player
|
|
30
|
+
Copyright 2016 Google LLC
|
|
31
|
+
SPDX-License-Identifier: Apache-2.0
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
@license
|
|
36
|
+
Shaka Player
|
|
37
|
+
Copyright 2022 Google LLC
|
|
38
|
+
SPDX-License-Identifier: Apache-2.0
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
@license
|
|
43
|
+
Shaka Player
|
|
44
|
+
Copyright 2023 Google LLC
|
|
45
|
+
SPDX-License-Identifier: Apache-2.0
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
@license
|
|
50
|
+
tXml
|
|
51
|
+
Copyright 2015 Tobias Nickel
|
|
52
|
+
SPDX-License-Identifier: MIT
|
|
53
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senza-sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"main": "./src/api.js",
|
|
5
5
|
"description": "API for Senza application",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"eslint-plugin-jest": "^26.1.1",
|
|
27
27
|
"jest": "^27.5.1",
|
|
28
28
|
"jsdoc-to-markdown": "^7.1.1",
|
|
29
|
-
"webpack
|
|
30
|
-
"webpack": "^5.
|
|
29
|
+
"webpack": "^5.72.1",
|
|
30
|
+
"webpack-cli": "^5.1.4"
|
|
31
31
|
},
|
|
32
32
|
"jest": {
|
|
33
33
|
"verbose": false,
|
|
@@ -44,5 +44,8 @@
|
|
|
44
44
|
"statements": 92
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"shaka-player": "^4.12.5"
|
|
47
50
|
}
|
|
48
51
|
}
|
package/src/api.js
CHANGED
|
@@ -384,6 +384,9 @@ export { platformManager } from "./platformManager";
|
|
|
384
384
|
export { alarmManager } from "./alarmManager";
|
|
385
385
|
export { messageManager } from "./messageManager";
|
|
386
386
|
export { initSequence, showSequence } from "./devSequence";
|
|
387
|
+
export {SenzaShakaPlayer as ShakaPlayer} from "./senzaShakaPlayer";
|
|
388
|
+
|
|
389
|
+
|
|
387
390
|
import "./devHelper";
|
|
388
391
|
|
|
389
392
|
/**
|
package/src/devHelper.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
import { lifecycle,remotePlayer } from "./api";
|
|
1
|
+
import { lifecycle, remotePlayer } from "./api";
|
|
2
2
|
import { sdkLogger } from "./utils";
|
|
3
3
|
|
|
4
4
|
const timeout = 500;
|
|
5
5
|
let timerId;
|
|
6
|
-
let currentState;
|
|
7
|
-
|
|
8
|
-
lifecycle.getState()?.then((state) => {
|
|
9
|
-
currentState = state;
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
lifecycle.addEventListener("onstatechange", (e) => {
|
|
13
|
-
currentState = e.state;
|
|
14
|
-
});
|
|
15
6
|
|
|
16
7
|
const handleHintLogging = (event) => {
|
|
17
|
-
if (
|
|
8
|
+
if (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
18
9
|
if (timerId) {
|
|
19
10
|
clearTimeout(timerId);
|
|
20
11
|
}
|
|
21
12
|
timerId = setTimeout(() => {
|
|
22
|
-
if (
|
|
23
|
-
sdkLogger.log(`${event.type} event received while in '${
|
|
13
|
+
if (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
14
|
+
sdkLogger.log(`${event.type} event received while in '${lifecycle.state}' state and there was no call to lifecycle.moveToForeground() for ${timeout} ms. Make sure to call it if you want to move to foreground.`);
|
|
24
15
|
}
|
|
25
16
|
timerId = 0;
|
|
26
17
|
}, timeout);
|
package/src/remotePlayer.js
CHANGED
|
@@ -541,9 +541,9 @@ class RemotePlayer extends EventTarget {
|
|
|
541
541
|
throw new RemotePlayerError(6500, "Cannot call load() if remote player is not initialized");
|
|
542
542
|
}
|
|
543
543
|
if (url && window.cefQuery) {
|
|
544
|
-
const state =
|
|
545
|
-
if (state ===
|
|
546
|
-
throw new RemotePlayerError(6002,
|
|
544
|
+
const state = lifecycle.state;
|
|
545
|
+
if (state === lifecycle.UiState.BACKGROUND || state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
546
|
+
throw new RemotePlayerError(6002, `Cannot call load() while in state '${lifecycle.UiState.BACKGROUND}' or '${lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND}'`);
|
|
547
547
|
}
|
|
548
548
|
if (this._loadMode === this.LoadMode.LOADING) {
|
|
549
549
|
throw new RemotePlayerError(6501, "Cannot call load() while previous load is still in progress");
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { remotePlayer, lifecycle } from "./api";
|
|
2
|
+
import { sdkLogger } from "./utils";
|
|
3
|
+
import shaka from "shaka-player";
|
|
4
|
+
/**
|
|
5
|
+
* SenzaShakaPlayer subclass of Shaka that handles both local and remote playback.
|
|
6
|
+
*
|
|
7
|
+
* @class SenzaShakaPlayer
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { SenzaShakaPlayer } from "./senzaShakaPlayer.js";
|
|
11
|
+
*
|
|
12
|
+
* try {
|
|
13
|
+
* const videoElement = document.getElementById("video");
|
|
14
|
+
* const player = new SenzaShakaPlayer(videoElement);
|
|
15
|
+
* await player.load("http://playable.url/file.mpd");
|
|
16
|
+
* await videoElement.play(); // will start the playback
|
|
17
|
+
*
|
|
18
|
+
* } catch (err) {
|
|
19
|
+
* console.error("SenzaShakaPlayer failed with error", err);
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
export class SenzaShakaPlayer extends shaka.Player {
|
|
23
|
+
/**
|
|
24
|
+
* Creates an instance of SenzaShakaPlayer, which is a subclass of shaka.Player.
|
|
25
|
+
*
|
|
26
|
+
* @param {HTMLVideoElement} videoElement - The video element to be used for local playback.
|
|
27
|
+
*/
|
|
28
|
+
constructor(videoElement, videoContainer, dependencyInjector) {
|
|
29
|
+
super(videoElement, videoContainer, dependencyInjector);
|
|
30
|
+
this.videoElement = videoElement;
|
|
31
|
+
this.remotePlayer = remotePlayer;
|
|
32
|
+
|
|
33
|
+
this.remotePlayer.attach(this.videoElement);
|
|
34
|
+
|
|
35
|
+
this.addEventListeners();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
addEventListeners() {
|
|
39
|
+
this.remotePlayer.addEventListener("ended", () => {
|
|
40
|
+
sdkLogger.log("remotePlayer ended");
|
|
41
|
+
lifecycle.moveToForeground();
|
|
42
|
+
this.videoElement.dispatchEvent(new Event("ended"));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.remotePlayer.addEventListener("error", (event) => {
|
|
46
|
+
sdkLogger.log("remotePlayer error:", event.detail.errorCode, event.detail.message);
|
|
47
|
+
this.videoElement.dispatchEvent(new CustomEvent("error", event));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.remotePlayer.addEventListener("timeupdate", () => {
|
|
51
|
+
if (!this.isInRemotePlayback) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.videoElement.currentTime = this.remotePlayer.currentTime;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.addEventListener("error", (event) => {
|
|
58
|
+
sdkLogger.log("localPlayer error:", event.detail.errorCode, event.detail.message);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.videoElement.addEventListener("play", () => {
|
|
62
|
+
this.remotePlayer.play();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.videoElement.addEventListener("pause", () => {
|
|
66
|
+
this.remotePlayer.pause();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.videoElement.addEventListener("seeked", () => {
|
|
70
|
+
this.remotePlayer.currentTime = this.videoElement.currentTime;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
this.remotePlayer.addEventListener("license-request", async (event) => {
|
|
75
|
+
sdkLogger.log("remotePlayer", "license-request", "Got license-request event from remote player");
|
|
76
|
+
|
|
77
|
+
// Extract license body from event
|
|
78
|
+
const requestBuffer = event?.detail?.licenseRequest;
|
|
79
|
+
const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
|
|
80
|
+
const decodedLicenseRequest = window.atob(requestBufferStr); // Decode from base64
|
|
81
|
+
const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
|
|
82
|
+
|
|
83
|
+
const request = {
|
|
84
|
+
body: licenseRequestBytes.buffer,
|
|
85
|
+
uris: [this.getConfiguration().drm.servers["com.widevine.alpha"]], // TODO: safe gaurd against undefined and other server types
|
|
86
|
+
method: "POST"
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const response = await this.getNetworkingEngine().request(shaka.net.NetworkingEngine.RequestType.LICENSE, request).promise;
|
|
90
|
+
|
|
91
|
+
let responseBody = response.data;
|
|
92
|
+
if (response.status !== 200) {
|
|
93
|
+
responseBody = response.data ?? String.fromCharCode(new Uint8Array(response.data));
|
|
94
|
+
sdkLogger.error("remotePlayer", "license-request", "failed to to get response from widevine:", response.status, responseBody);
|
|
95
|
+
}
|
|
96
|
+
// Write response to remote player
|
|
97
|
+
sdkLogger.log("remotePlayer", "license-request", "Writing response to remote player", response.status);
|
|
98
|
+
event.writeLicenseResponse(response.status, responseBody);
|
|
99
|
+
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Helper function that makes it easier to check if lifecycle.state is
|
|
105
|
+
* either background or inTransitionToBackground.
|
|
106
|
+
*/
|
|
107
|
+
get isInRemotePlayback() {
|
|
108
|
+
return lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Moves playback to local player.
|
|
113
|
+
*
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async moveToLocalPlayback() {
|
|
117
|
+
await lifecycle.moveToForeground();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Moves playback to remote player.
|
|
122
|
+
* The timecode needs to be synced here if audiosync is not enabled.
|
|
123
|
+
*
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async moveToRemotePlayback() {
|
|
127
|
+
this.remotePlayer.currentTime = this.videoElement.currentTime;
|
|
128
|
+
await lifecycle.moveToBackground();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Loads a media URL into both local and remote players.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} url - The URL of the media to load.
|
|
135
|
+
* @returns {Promise<void>}
|
|
136
|
+
*/
|
|
137
|
+
async load(url, startTime, mimeType) {
|
|
138
|
+
if (!this.isInRemotePlayback || remotePlayer.getAssetUri() !== url) {
|
|
139
|
+
try {
|
|
140
|
+
await this.remotePlayer.load(url);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
sdkLogger.log("Couldn't load remote player. Error:", error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await super.load(url, startTime, mimeType);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
sdkLogger.log("Couldn't load local player. Error:", error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|