senza-sdk 4.3.1-ca3d96f.0 → 4.3.1-e113d43.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 +1 -1
- package/package.json +8 -9
- package/src/api.js +2 -14
- package/src/implementation/api.js +4 -0
- package/src/implementation/lifecycle.js +10 -9
- package/src/implementation/messageManager.js +33 -0
- package/src/implementation/platformManager.js +19 -0
- package/src/implementation/remotePlayer.js +215 -17
- package/src/implementation/senzaShakaPlayer.js +127 -104
- package/src/interface/alarmManager.js +21 -28
- package/src/interface/devSequence.js +0 -35
- package/src/interface/deviceManager.js +21 -30
- package/src/interface/lifecycle.js +39 -44
- package/src/interface/messageManager.js +27 -34
- package/src/interface/platformManager.js +5 -11
- package/src/interface/remotePlayer.js +180 -209
- package/src/interface/senzaShakaPlayer.js +5 -2
|
@@ -1,42 +1,13 @@
|
|
|
1
|
+
/* eslint-disable no-empty-function */
|
|
2
|
+
/* eslint-disable no-unused-vars */
|
|
1
3
|
import {
|
|
2
4
|
sdkLogger,
|
|
3
5
|
noop
|
|
4
6
|
} from "./utils.js";
|
|
5
|
-
/**
|
|
6
|
-
* @event Lifecycle#onstatechange
|
|
7
|
-
* @description Fired after transition from one state to another.<br>
|
|
8
|
-
* The flow is: foreground --> inTransitionToBackground --> background --> inTransitionToForeground --> foreground
|
|
9
|
-
* @property {UiState} state - Indicates the new state.
|
|
10
|
-
* @example
|
|
11
|
-
* lifecycle.addEventListener("onstatechange", (e) => {
|
|
12
|
-
* console.log("new state is", e.state);
|
|
13
|
-
* });
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @event Lifecycle#userinactivity
|
|
18
|
-
* @description Fired after the ui has been inactive (i.e. no key presses) for a configurable number of seconds.<br>
|
|
19
|
-
* @property {number} timeout - the number of seconds after which the application will be unloaded.
|
|
20
|
-
* @example
|
|
21
|
-
* lifecycle.addEventListener("userinactivity", (e) => {
|
|
22
|
-
* console.log(`Application will be unloaded in ${e.timeout} seconds`);
|
|
23
|
-
* });
|
|
24
|
-
* @alpha API has not yet been released
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @event Lifecycle#userdisconnected
|
|
29
|
-
* @description Fired when the user session ends .
|
|
30
|
-
* This event is useful for cleaning up application state or saving data before the application closes. Event callback should return promise to ensure that the event is handled before the application is terminated
|
|
31
|
-
* @example
|
|
32
|
-
* lifecycle.addEventListener("userdisconnected", () => {
|
|
33
|
-
* console.log("User session ended, cleaning up application state");
|
|
34
|
-
* // Perform any necessary cleanup here
|
|
35
|
-
* });
|
|
36
|
-
*/
|
|
37
7
|
|
|
38
8
|
/**
|
|
39
9
|
* Lifecycle is a singleton class that manages the application lifecycle states.<br>
|
|
10
|
+
* @class Lifecycle
|
|
40
11
|
* @fires onstatechange
|
|
41
12
|
* @fires userinactivity
|
|
42
13
|
* @fires userdisconnected
|
|
@@ -79,6 +50,42 @@ export class Lifecycle extends EventTarget {
|
|
|
79
50
|
IN_TRANSITION_TO_BACKGROUND: "inTransitionToBackground"
|
|
80
51
|
});
|
|
81
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @event Lifecycle#onstatechange
|
|
55
|
+
* @description Fired after transition from one state to another.<br>
|
|
56
|
+
* The flow is: foreground --> inTransitionToBackground --> background --> inTransitionToForeground --> foreground
|
|
57
|
+
* @property {UiState} state - Indicates the new state.
|
|
58
|
+
* @example
|
|
59
|
+
* lifecycle.addEventListener("onstatechange", (e) => {
|
|
60
|
+
* console.log("new state is", e.state);
|
|
61
|
+
* });
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @event Lifecycle#userinactivity
|
|
66
|
+
* @description Fired after the ui has been inactive (i.e. no key presses) for a configurable number of seconds.<br>
|
|
67
|
+
* @property {number} timeout - the number of seconds after which the application will be unloaded.
|
|
68
|
+
* @example
|
|
69
|
+
* lifecycle.addEventListener("userinactivity", (e) => {
|
|
70
|
+
* console.log(`Application will be unloaded in ${e.timeout} seconds`);
|
|
71
|
+
* });
|
|
72
|
+
* @alpha API has not yet been released
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @event Lifecycle#userdisconnected
|
|
77
|
+
* @description Fired when the user session ends .
|
|
78
|
+
* This event is useful for cleaning up application state or saving data before the application closes. Event callback should return promise to ensure that the event is handled before the application is terminated
|
|
79
|
+
* @example
|
|
80
|
+
* lifecycle.addEventListener("userdisconnected", () => {
|
|
81
|
+
* console.log("User session ended, cleaning up application state");
|
|
82
|
+
* // Perform any necessary cleanup here
|
|
83
|
+
* });
|
|
84
|
+
*/
|
|
85
|
+
constructor() {
|
|
86
|
+
super();
|
|
87
|
+
}
|
|
88
|
+
|
|
82
89
|
/**
|
|
83
90
|
* Configure lifecycle settings
|
|
84
91
|
* @param {Object} config - Configuration object
|
|
@@ -89,7 +96,6 @@ export class Lifecycle extends EventTarget {
|
|
|
89
96
|
* @param {number|false} [config.autoBackground.timeout.idle=false] - Timeout in seconds when in UI mode, false to disable
|
|
90
97
|
*/
|
|
91
98
|
configure(config) {
|
|
92
|
-
noop("lifecycle.configure", config);
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
/**
|
|
@@ -270,14 +276,3 @@ export class Lifecycle extends EventTarget {
|
|
|
270
276
|
super.removeEventListener(type, listener, options);
|
|
271
277
|
}
|
|
272
278
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* @module
|
|
277
|
-
* @type {Lifecycle}
|
|
278
|
-
* @example
|
|
279
|
-
* import { lifecycle } from "senza-sdk";
|
|
280
|
-
*
|
|
281
|
-
* @return {Lifecycle} pointer to the Lifecycle singleton
|
|
282
|
-
*/
|
|
283
|
-
"needed for the module doc comment to be recognized";
|
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
|
|
2
2
|
import { noop } from "./utils";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {object} MessageDetails - object which contains the content of the message
|
|
6
|
-
* @property {string} eventName - The name of the event message, a property of MessageDetails
|
|
7
|
-
* @property {object} payload - The payload for this event, a property of MessageDetails
|
|
8
|
-
* @property {string} fcid - The flow context for this message, a property of MessageDetails
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* message event
|
|
13
|
-
*
|
|
14
|
-
* @event MessageManager#message
|
|
15
|
-
* @type {CustomEvent}
|
|
16
|
-
* @property {MessageDetails} detail - object containing data related to the event:
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* @description Fired when an external event arrives. This is a generic handler for all messages received for any registered group <br>
|
|
20
|
-
* @example
|
|
21
|
-
* messageManager.addEventListener("message", (e) => {
|
|
22
|
-
* console.log("message arrived with data", e.detail);
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
3
|
/**
|
|
28
4
|
* MessageManager is a singleton class that manages the external messages received by the application. It fires custom events as "message" with the payload as the content
|
|
29
5
|
* @class MessageManager
|
|
30
6
|
* @fires MessageManager#message
|
|
31
7
|
*/
|
|
8
|
+
|
|
32
9
|
export class MessageManager extends EventTarget {
|
|
33
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} MessageDetails - object which contains the content of the message
|
|
13
|
+
* @property {string} eventName - The name of the event message, a property of MessageDetails
|
|
14
|
+
* @property {object} payload - The payload for this event, a property of MessageDetails
|
|
15
|
+
* @property {string} fcid - The flow context for this message, a property of MessageDetails
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* message event
|
|
20
|
+
*
|
|
21
|
+
* @event MessageManager#message
|
|
22
|
+
* @type {CustomEvent}
|
|
23
|
+
* @property {MessageDetails} detail - object containing data related to the event:
|
|
24
|
+
|
|
25
|
+
*
|
|
26
|
+
* @description Fired when an external event arrives. This is a generic handler for all messages received for any registered group <br>
|
|
27
|
+
* @example
|
|
28
|
+
* messageManager.addEventListener("message", (e) => {
|
|
29
|
+
* console.log("message arrived with data", e.detail);
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
34
37
|
/** Register to specific group(s). This function replaces the previously registered groups
|
|
35
38
|
* @param {Array<String>} groups group events to receive messages.
|
|
36
39
|
* @return {Promise} Promise which is resolved when the registerGroups command has been successfully processed.
|
|
@@ -41,13 +44,3 @@ export class MessageManager extends EventTarget {
|
|
|
41
44
|
return noop("MessageManager.registerGroups", groups);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @module
|
|
47
|
-
* @type {MessageManager}
|
|
48
|
-
* @example
|
|
49
|
-
* import { MessageManager } from "senza-sdk";
|
|
50
|
-
*
|
|
51
|
-
* @return {MessageManager} pointer to the MessageManager singleton
|
|
52
|
-
*/
|
|
53
|
-
"needed for the module doc comment to be recognized";
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { noop } from "./utils";
|
|
2
2
|
/**
|
|
3
|
+
* @class PlatformManager
|
|
3
4
|
* PlatformManager is a singleton class that manages the platform
|
|
4
5
|
*/
|
|
5
6
|
export class PlatformManager extends EventTarget {
|
|
6
7
|
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* @returns {Object} appConfig object
|
|
9
14
|
* @property {String[]} territories - a list of territories configured for the tenant.
|
|
@@ -28,14 +33,3 @@ export class PlatformManager extends EventTarget {
|
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
/**
|
|
32
|
-
* @module
|
|
33
|
-
* @type {PlatformManager}
|
|
34
|
-
* @example
|
|
35
|
-
* import { platformManager } from "senza-sdk";
|
|
36
|
-
* console.info(platformManager.appConfig);
|
|
37
|
-
* platformManager.setTimezone("Europe/Brussels");
|
|
38
|
-
*
|
|
39
|
-
* @return {PlatformManager} pointer to the PlatformManager singleton
|
|
40
|
-
*/
|
|
41
|
-
"needed for the module doc comment to be recognized";
|
|
@@ -1,193 +1,22 @@
|
|
|
1
1
|
import { noop } from "./utils";
|
|
2
|
-
|
|
3
|
-
// TODO: check that the link below to the error list is working on dashreadme
|
|
4
|
-
/** Error object to be thrown on remotePlayer api failures.
|
|
5
|
-
* [See error list]{@link RemotePlayer#error}
|
|
6
|
-
*/
|
|
7
2
|
export class RemotePlayerError extends Error {
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.msg = message; // For backwards compatibility
|
|
7
|
+
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef {Object} Config
|
|
12
12
|
* @property {string} preferredAudioLanguage
|
|
13
13
|
* @property {string} preferredSubtitlesLanguage
|
|
14
|
-
* @property {number} minSuggestedPresentationDelay - minimal delay allowed for live playback in seconds
|
|
15
14
|
* @property {boolean} autoPlay - (Not implemented yet) upon loading start playing automatically
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
* @event RemotePlayer#canplay
|
|
20
|
-
* @description canplay event will be dispatched when the remote player can start play the event
|
|
21
|
-
* @example
|
|
22
|
-
* remotePlayer.addEventListener("canplay", () => {
|
|
23
|
-
* console.info("remotePlayer canplay");
|
|
24
|
-
* remotePlayer.play();
|
|
25
|
-
* });
|
|
26
|
-
*/
|
|
27
|
-
/**
|
|
28
|
-
* @event RemotePlayer#loadedmetadata
|
|
29
|
-
* @description loadedmetadata event will be dispatched when the remote player metadata is loaded
|
|
30
|
-
* and the audio/video tracks are available
|
|
31
|
-
* @example
|
|
32
|
-
* remotePlayer.addEventListener("loadedmetadata", () => {
|
|
33
|
-
* console.info("remotePlayer loadedmetadata", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
|
|
34
|
-
* });
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @event RemotePlayer#timeupdate
|
|
39
|
-
* @example
|
|
40
|
-
* remotePlayer.addEventListener("timeupdate", () => {
|
|
41
|
-
* console.info("remotePlayer timeupdate", remotePlayer.currentTime);
|
|
42
|
-
* localPlayer.getMediaElement().currentTime = remotePlayer.currentTime || 0;
|
|
43
|
-
* });
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @event RemotePlayer#tracksupdate
|
|
48
|
-
* @example
|
|
49
|
-
* remotePlayer.addEventListener("tracksupdate", () => {
|
|
50
|
-
* console.info("remotePlayer tracksupdate", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
|
|
51
|
-
* });
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @event RemotePlayer#ended
|
|
56
|
-
* @example
|
|
57
|
-
* remotePlayer.addEventListener("ended", () => {
|
|
58
|
-
* console.info("remotePlayer ended");
|
|
59
|
-
* });
|
|
60
|
-
*/
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @event RemotePlayer#onloadmodechange
|
|
64
|
-
* @example
|
|
65
|
-
* remotePlayer.addEventListener("onloadmodechange", () => {
|
|
66
|
-
* console.info("remotePlayer load mode changed to", remotePlayer.getLoadMode());
|
|
67
|
-
* });
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* @event RemotePlayer#error
|
|
72
|
-
* @type {object}
|
|
73
|
-
* @property {int} detail.errorCode
|
|
74
|
-
* @property {string} detail.message
|
|
75
|
-
*
|
|
76
|
-
* @see Possible error codes:
|
|
77
|
-
*
|
|
78
|
-
* | Code | Domain | Description |
|
|
79
|
-
* | :-------- | :---------------- | :-----------------------------------------------------------------------------------------------|
|
|
80
|
-
* | 23 | Player | load() failed due to remote player initialization error |
|
|
81
|
-
* | 98 | Player | load() failed due to remote player failure to send message to the client |
|
|
82
|
-
* | 99 | Player | load() failed due to remote player reporting invalid message |
|
|
83
|
-
* | 1000 | Encrypted content | Failed to create or initialise the CDM |
|
|
84
|
-
* | 1001 | Encrypted content | Failed to create a CDM session |
|
|
85
|
-
* | 1002 | Encrypted content | CDM failed to generate a license request |
|
|
86
|
-
* | 1003 | Encrypted content | The CDM rejected the license server response |
|
|
87
|
-
* | 1004 | Encrypted content | The CDM rejected the license server certificate |
|
|
88
|
-
* | 1005 | Encrypted content | All keys in the license have expired |
|
|
89
|
-
* | 1006 | Encrypted content | Output device is incompatible with the license requirements (HDCP) |
|
|
90
|
-
* | 1007 | Encrypted content | The device has been revoked |
|
|
91
|
-
* | 1008 | Encrypted content | The device secrets aren't available |
|
|
92
|
-
* | 1009 | Encrypted content | Keys are loaded but the KID requested by playback isn't found. The app has likely issued a license for the wrong content or there is a mismatch between the KIDs in the license and the data plane |
|
|
93
|
-
* | 1010 | Encrypted content | The CDM failed to provision, therefore it is not possible to play encrypted content |
|
|
94
|
-
* | 1100 | Encrypted content | The CDM session has already received a license response. The app has likely issued 2, or more, license responses for the same request. The subsequent licenses will be ignored so this error is informational only |
|
|
95
|
-
* | 1101 | Encrypted content | The license has been rejected since an error was received by the CDM. The app has likely sent a non-200 code to `WriteLicenseResponse` |
|
|
96
|
-
* | 1102 | Encrypted content | A license response wasn't received from the app within a pre-defined timeout |
|
|
97
|
-
* | 1103 | Encrypted content | The CDM session associated with this license response is in an invalid state. This is an internal Senza platform error |
|
|
98
|
-
* | 1104 | Encrypted content | The CDM failed to send a license request to the app. This is an internal Senza platform error |
|
|
99
|
-
* | 1999 | Encrypted content | An unknown encrypted content error |
|
|
100
|
-
* | 2000 | Player | Content makes reference to no or unsupported key system |
|
|
101
|
-
* | 3000 | Player | Unexpected problem with playback, only used if no more specific code in 3xxx range applies |
|
|
102
|
-
* | 3001 | Player | Problem accessing content manifest, only used if no more specific code in 8xxx range applies |
|
|
103
|
-
* | 3002 | Player | Unexpectedly stopped playback |
|
|
104
|
-
* | 3100 | Player | Problem parsing MP4 content |
|
|
105
|
-
* | 3200 | Player | Problem with decoder |
|
|
106
|
-
* | 3300 | Player | DRM keys unavailable, player waited for keys but none arrived |
|
|
107
|
-
* | 3400 | Player | Problem accessing segments, only used if no more specific code in 34xx range applies |
|
|
108
|
-
* | 3401 | Player | Problem accessing segments, connection issue or timeout |
|
|
109
|
-
* | 3402 | Player | Problem accessing segments, server returned HTTP error code |
|
|
110
|
-
* | 3403 | Player | Problem accessing segments, server authentication issue |
|
|
111
|
-
* | 3404 | Player | Problem accessing segments, server returned not found |
|
|
112
|
-
* | 3900-3999 | Player | Internal player error |
|
|
113
|
-
* | 6000 | Player | The remote player api call has reached the configurable timeout with no response from the remote player |
|
|
114
|
-
* | 6001 | Player | play() was called while the remote player is not loaded |
|
|
115
|
-
* | 6002 | Player | load() was called while the application was in state 'background' or 'inTransitionToBackground' |
|
|
116
|
-
* | 6500 | Player | remotePlayer api was called before initializing remotePlayer |
|
|
117
|
-
* | 6501 | Player | load() was called while previous load/unload was still in progress |
|
|
118
|
-
* | 6502 | Player | unload() was called while previous unload/load was still in progress |
|
|
119
|
-
* | 8001 | Player | Error pulling manifest. bad parameters |
|
|
120
|
-
* | 8002 | Player | Error pulling manifest. filters returned no data |
|
|
121
|
-
* | 8003 | Player | Error pulling manifest. fetch error |
|
|
122
|
-
* | 8004 | Player | Error pulling manifest. parse error |
|
|
123
|
-
* | 8005 | Player | Error pulling manifest. stale manifest detected |
|
|
124
|
-
* | 8006 | Player | Error updating manifest. internal cache error |
|
|
125
|
-
* | 8007 | Player | Error updating manifest. internal error during backoff |
|
|
126
|
-
* | 8008 | Player | Error pulling manifest. sidx parsing error |
|
|
127
|
-
* | 8009 | Player | Error pulling manifest. internal error |
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* remotePlayer.addEventListener("error", (event) => {
|
|
131
|
-
* console.error("received remotePlayer error:", event.detail.errorCode, event.detail.message);
|
|
132
|
-
* });
|
|
133
|
-
*/
|
|
134
|
-
/**
|
|
135
|
-
* @event RemotePlayer#playing
|
|
136
|
-
* @description playing event will be dispatched when the remote player started playback of the first audio frame. This event can be used to synchronize the local player with the remote player.
|
|
137
|
-
* @example
|
|
138
|
-
* remotePlayer.addEventListener("playing", () => {
|
|
139
|
-
* console.info("remotePlayer playing");
|
|
140
|
-
* });
|
|
141
|
-
* */
|
|
142
|
-
/**
|
|
143
|
-
* @event RemotePlayer#license-request
|
|
144
|
-
* @description Fired whenever the platform requires a license to play encrypted content.
|
|
145
|
-
* The Web App is responsible for passing the (opaque) license request blob to the license server and passing the (opaque) license server response to the CDM by calling the `writeLicenseResponse` method on the event.
|
|
146
|
-
* @type {LicenseRequestEvent}
|
|
147
|
-
* @property {object} detail - Object containing ievent data
|
|
148
|
-
* @property {string} detail.licenseRequest - Base64 coded opaque license request. The app is responsible for decoding the request before sending to the license server. Note that after decoding, the request may still be in Base64 form and this form should be sent to the license server without further decoding
|
|
149
|
-
* @property {writeLicenseResponse} writeLicenseResponse - Write the license server response to the platform
|
|
150
|
-
* @example
|
|
151
|
-
* Whilst the payload structure and access controls are specific to each license server implementation, the Widevine UAT license server requires no authentication and minimal payload formatting and therefore serves as a useful case study that may be adapted.
|
|
152
|
-
*
|
|
153
|
-
* remotePlayer.addEventListener("license-request", async (event) => {
|
|
154
|
-
* console.log("Got license-request event");
|
|
155
|
-
* const requestBuffer = event?.detail?.licenseRequest;
|
|
156
|
-
* const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
|
|
157
|
-
* console.log("License Request in base64:", requestBufferStr);
|
|
158
|
-
* const decodedLicenseRequest = window.atob(requestBufferStr); // from base 64
|
|
159
|
-
* const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
|
|
160
|
-
* // call Google API
|
|
161
|
-
* const res = await getLicenseFromServer(licenseRequestBytes.buffer);
|
|
162
|
-
* console.log("Writing response to platform ", res.code, res.responseBody);
|
|
163
|
-
* event.writeLicenseResponse(res.code, res.responseBody);
|
|
164
|
-
* });
|
|
165
|
-
|
|
166
|
-
* async function getLicenseFromServer(licenseRequest) {
|
|
167
|
-
* console.log("Requesting License from Widevine server");
|
|
168
|
-
* const response = await fetch("https://proxy.uat.widevine.com/proxy", {
|
|
169
|
-
* "method": "POST",
|
|
170
|
-
* "body": licenseRequest,
|
|
171
|
-
* "headers" : {
|
|
172
|
-
* "Content-Type": "application/octet-stream"
|
|
173
|
-
* }
|
|
174
|
-
* });
|
|
175
|
-
* const code = response.status;
|
|
176
|
-
* if (code !== 200) {
|
|
177
|
-
* console.error("failed to to get response from widevine:", code);
|
|
178
|
-
* const responseBody = await response.text();
|
|
179
|
-
* console.error(responseBody);
|
|
180
|
-
* return {code, responseBody};
|
|
181
|
-
* }
|
|
182
|
-
* const responseBody = await response.arrayBuffer();
|
|
183
|
-
* console.info("Got response: ");
|
|
184
|
-
* return {code, responseBody};
|
|
185
|
-
* }
|
|
186
|
-
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
17
|
/**
|
|
190
18
|
* RemotePlayer a singleton class to communicate with remote player
|
|
19
|
+
* @class RemotePlayer
|
|
191
20
|
* @fires timeupdate
|
|
192
21
|
* @fires tracksupdate
|
|
193
22
|
* @fires ended
|
|
@@ -200,20 +29,179 @@ export class RemotePlayerError extends Error {
|
|
|
200
29
|
* @fires license-request
|
|
201
30
|
*/
|
|
202
31
|
export class RemotePlayer extends EventTarget {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
this.LoadMode = Object.freeze({
|
|
35
|
+
NOT_LOADED: "notLoaded",
|
|
36
|
+
LOADING: "loading",
|
|
37
|
+
LOADED: "loaded",
|
|
38
|
+
UNLOADING: "unloading"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @event RemotePlayer#canplay
|
|
43
|
+
* @description canplay event will be dispatched when the remote player can start play the event
|
|
44
|
+
* @example
|
|
45
|
+
* remotePlayer.addEventListener("canplay", () => {
|
|
46
|
+
* console.info("remotePlayer canplay");
|
|
47
|
+
* remotePlayer.play();
|
|
48
|
+
* });
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @event RemotePlayer#loadedmetadata
|
|
53
|
+
* @description loadedmetadata event will be dispatched when the remote player metadata is loaded
|
|
54
|
+
* and the audio/video tracks are available
|
|
55
|
+
* @example
|
|
56
|
+
* remotePlayer.addEventListener("loadedmetadata", () => {
|
|
57
|
+
* console.info("remotePlayer loadedmetadata", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @event RemotePlayer#timeupdate
|
|
63
|
+
* @example
|
|
64
|
+
* remotePlayer.addEventListener("timeupdate", () => {
|
|
65
|
+
* console.info("remotePlayer timeupdate", remotePlayer.currentTime);
|
|
66
|
+
* localPlayer.getMediaElement().currentTime = remotePlayer.currentTime || 0;
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @event RemotePlayer#tracksupdate
|
|
72
|
+
* @example
|
|
73
|
+
* remotePlayer.addEventListener("tracksupdate", () => {
|
|
74
|
+
* console.info("remotePlayer tracksupdate", remotePlayer.getAudioTracks(), remotePlayer.getTextTracks());
|
|
75
|
+
* });
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @event RemotePlayer#ended
|
|
80
|
+
* @example
|
|
81
|
+
* remotePlayer.addEventListener("ended", () => {
|
|
82
|
+
* console.info("remotePlayer ended");
|
|
83
|
+
* });
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @event RemotePlayer#onloadmodechange
|
|
88
|
+
* @example
|
|
89
|
+
* remotePlayer.addEventListener("onloadmodechange", () => {
|
|
90
|
+
* console.info("remotePlayer load mode changed to", remotePlayer.getLoadMode());
|
|
91
|
+
* });
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @event RemotePlayer#error
|
|
96
|
+
* @type {object}
|
|
97
|
+
* @property {int} detail.errorCode
|
|
98
|
+
* @property {string} detail.message
|
|
99
|
+
*
|
|
100
|
+
* @see Possible error codes:
|
|
101
|
+
*
|
|
102
|
+
* | Code | Domain | Description |
|
|
103
|
+
* | :-------- | :---------------- | :-----------------------------------------------------------------------------------------------|
|
|
104
|
+
* | 23 | Player | load() failed due to remote player initialization error |
|
|
105
|
+
* | 98 | Player | load() failed due to remote player failure to send message to the client |
|
|
106
|
+
* | 99 | Player | load() failed due to remote player reporting invalid message |
|
|
107
|
+
* | 1000 | Encrypted content | Failed to create or initialise the CDM |
|
|
108
|
+
* | 1001 | Encrypted content | Failed to create a CDM session |
|
|
109
|
+
* | 1002 | Encrypted content | CDM failed to generate a license request |
|
|
110
|
+
* | 1003 | Encrypted content | The CDM rejected the license server response |
|
|
111
|
+
* | 1004 | Encrypted content | The CDM rejected the license server certificate |
|
|
112
|
+
* | 1005 | Encrypted content | All keys in the license have expired |
|
|
113
|
+
* | 1006 | Encrypted content | Output device is incompatible with the license requirements (HDCP) |
|
|
114
|
+
* | 1007 | Encrypted content | The device has been revoked |
|
|
115
|
+
* | 1008 | Encrypted content | The device secrets aren't available |
|
|
116
|
+
* | 1009 | Encrypted content | Keys are loaded but the KID requested by playback isn't found. The app has likely issued a license for the wrong content or there is a mismatch between the KIDs in the license and the data plane |
|
|
117
|
+
* | 1010 | Encrypted content | The CDM failed to provision, therefore it is not possible to play encrypted content |
|
|
118
|
+
* | 1100 | Encrypted content | The CDM session has already received a license response. The app has likely issued 2, or more, license responses for the same request. The subsequent licenses will be ignored so this error is informational only |
|
|
119
|
+
* | 1101 | Encrypted content | The license has been rejected since an error was received by the CDM. The app has likely sent a non-200 code to `WriteLicenseResponse` |
|
|
120
|
+
* | 1102 | Encrypted content | A license response wasn't received from the app within a pre-defined timeout |
|
|
121
|
+
* | 1103 | Encrypted content | The CDM session associated with this license response is in an invalid state. This is an internal Senza platform error |
|
|
122
|
+
* | 1104 | Encrypted content | The CDM failed to send a license request to the app. This is an internal Senza platform error |
|
|
123
|
+
* | 1999 | Encrypted content | An unknown encrypted content error |
|
|
124
|
+
* | 2000 | Player | Content makes reference to no or unsupported key system |
|
|
125
|
+
* | 3000 | Player | Unexpected problem with playback, only used if no more specific code in 3xxx range applies |
|
|
126
|
+
* | 3001 | Player | Problem accessing content manifest, only used if no more specific code in 8xxx range applies |
|
|
127
|
+
* | 3002 | Player | Unexpectedly stopped playback |
|
|
128
|
+
* | 3100 | Player | Problem parsing MP4 content |
|
|
129
|
+
* | 3200 | Player | Problem with decoder |
|
|
130
|
+
* | 3300 | Player | DRM keys unavailable, player waited for keys but none arrived |
|
|
131
|
+
* | 3400 | Player | Problem accessing segments, only used if no more specific code in 34xx range applies |
|
|
132
|
+
* | 3401 | Player | Problem accessing segments, connection issue or timeout |
|
|
133
|
+
* | 3402 | Player | Problem accessing segments, server returned HTTP error code |
|
|
134
|
+
* | 3403 | Player | Problem accessing segments, server authentication issue |
|
|
135
|
+
* | 3404 | Player | Problem accessing segments, server returned not found |
|
|
136
|
+
* | 3900-3999 | Player | Internal player error |
|
|
137
|
+
* | 6000 | Player | The remote player api call has reached the configurable timeout with no response from the remote player |
|
|
138
|
+
* | 6001 | Player | play() was called while the remote player is not loaded |
|
|
139
|
+
* | 6002 | Player | load() was called while the application was in state 'background' or 'inTransitionToBackground' |
|
|
140
|
+
* | 6500 | Player | remotePlayer api was called before initializing remotePlayer |
|
|
141
|
+
* | 6501 | Player | load() was called while previous load/unload was still in progress |
|
|
142
|
+
* | 6502 | Player | unload() was called while previous unload/load was still in progress |
|
|
143
|
+
* | 8001 | Player | Error pulling manifest. bad parameters |
|
|
144
|
+
* | 8002 | Player | Error pulling manifest. filters returned no data |
|
|
145
|
+
* | 8003 | Player | Error pulling manifest. fetch error |
|
|
146
|
+
* | 8004 | Player | Error pulling manifest. parse error |
|
|
147
|
+
* | 8005 | Player | Error pulling manifest. stale manifest detected |
|
|
148
|
+
* | 8006 | Player | Error updating manifest. internal cache error |
|
|
149
|
+
* | 8007 | Player | Error updating manifest. internal error during backoff |
|
|
150
|
+
* | 8008 | Player | Error pulling manifest. sidx parsing error |
|
|
151
|
+
* | 8009 | Player | Error pulling manifest. internal error |
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* remotePlayer.addEventListener("error", (event) => {
|
|
155
|
+
* console.error("received remotePlayer error:", event.detail.errorCode, event.detail.message);
|
|
156
|
+
* });
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @event RemotePlayer#license-request
|
|
161
|
+
* @description Fired whenever the platform requires a license to play encrypted content.
|
|
162
|
+
* The Web App is responsible for passing the (opaque) license request blob to the license server and passing the (opaque) license server response to the CDM by calling the `writeLicenseResponse` method on the event.
|
|
163
|
+
* @type {LicenseRequestEvent}
|
|
164
|
+
* @property {object} detail - Object containing ievent data
|
|
165
|
+
* @property {string} detail.licenseRequest - Base64 coded opaque license request. The app is responsible for decoding the request before sending to the license server. Note that after decoding, the request may still be in Base64 form and this form should be sent to the license server without further decoding
|
|
166
|
+
* @property {writeLicenseResponse} writeLicenseResponse - Write the license server response to the platform
|
|
167
|
+
* @example
|
|
168
|
+
* Whilst the payload structure and access controls are specific to each license server implementation, the Widevine UAT license server requires no authentication and minimal payload formatting and therefore serves as a useful case study that may be adapted.
|
|
169
|
+
*
|
|
170
|
+
* remotePlayer.addEventListener("license-request", async (event) => {
|
|
171
|
+
* console.log("Got license-request event");
|
|
172
|
+
* const requestBuffer = event?.detail?.licenseRequest;
|
|
173
|
+
* const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
|
|
174
|
+
* console.log("License Request in base64:", requestBufferStr);
|
|
175
|
+
* const decodedLicenseRequest = window.atob(requestBufferStr); // from base 64
|
|
176
|
+
* const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
|
|
177
|
+
* // call Google API
|
|
178
|
+
* const res = await getLicenseFromServer(licenseRequestBytes.buffer);
|
|
179
|
+
* console.log("Writing response to platform ", res.code, res.responseBody);
|
|
180
|
+
* event.writeLicenseResponse(res.code, res.responseBody);
|
|
181
|
+
* });
|
|
182
|
+
|
|
183
|
+
* async function getLicenseFromServer(licenseRequest) {
|
|
184
|
+
* console.log("Requesting License from Widevine server");
|
|
185
|
+
* const response = await fetch("https://proxy.uat.widevine.com/proxy", {
|
|
186
|
+
* "method": "POST",
|
|
187
|
+
* "body": licenseRequest,
|
|
188
|
+
* "headers" : {
|
|
189
|
+
* "Content-Type": "application/octet-stream"
|
|
190
|
+
* }
|
|
191
|
+
* });
|
|
192
|
+
* const code = response.status;
|
|
193
|
+
* if (code !== 200) {
|
|
194
|
+
* console.error("failed to to get response from widevine:", code);
|
|
195
|
+
* const responseBody = await response.text();
|
|
196
|
+
* console.error(responseBody);
|
|
197
|
+
* return {code, responseBody};
|
|
198
|
+
* }
|
|
199
|
+
* const responseBody = await response.arrayBuffer();
|
|
200
|
+
* console.info("Got response: ");
|
|
201
|
+
* return {code, responseBody};
|
|
202
|
+
* }
|
|
203
|
+
*/
|
|
204
|
+
}
|
|
217
205
|
|
|
218
206
|
/** get the player configuration.
|
|
219
207
|
* @returns {Config}
|
|
@@ -323,7 +311,7 @@ export class RemotePlayer extends EventTarget {
|
|
|
323
311
|
*
|
|
324
312
|
* */
|
|
325
313
|
getLoadMode() {
|
|
326
|
-
return
|
|
314
|
+
return this.LoadMode.NOT_LOADED;
|
|
327
315
|
}
|
|
328
316
|
|
|
329
317
|
/**
|
|
@@ -451,20 +439,3 @@ export class RemotePlayer extends EventTarget {
|
|
|
451
439
|
*/
|
|
452
440
|
}
|
|
453
441
|
|
|
454
|
-
/**
|
|
455
|
-
* @module
|
|
456
|
-
* @private
|
|
457
|
-
* @type {RemotePlayer}
|
|
458
|
-
* @example
|
|
459
|
-
* import { init, remotePlayer } from "senza-sdk";
|
|
460
|
-
* await init();
|
|
461
|
-
* try {
|
|
462
|
-
* await remotePlayer.load("http://playable.url/file.mp4");
|
|
463
|
-
* remotePlayer.play();
|
|
464
|
-
* } catch(err) {
|
|
465
|
-
* console.log("remotePlayer api failed with error code", err.code, err.msg);
|
|
466
|
-
* }
|
|
467
|
-
*
|
|
468
|
-
* @returns {RemotePlayer} pointer to the RemotePlayer singleton
|
|
469
|
-
*/
|
|
470
|
-
"needed for the module doc comment to be recognized";
|
|
@@ -138,9 +138,12 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/***
|
|
141
|
-
*
|
|
142
|
-
*
|
|
141
|
+
*
|
|
142
|
+
* Configure the Player instance.
|
|
143
|
+
* @param {Object} config the configuration object
|
|
144
|
+
* @returns {Boolean}
|
|
143
145
|
*/
|
|
146
|
+
|
|
144
147
|
async destroy() {
|
|
145
148
|
return super.destroy();
|
|
146
149
|
}
|