senza-sdk 4.5.9 → 4.6.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/implementation.bundle.js +1 -1
- package/package.json +1 -1
- package/src/implementation/api.js +7 -5
- package/src/implementation/displayManager.js +15 -1
- package/src/implementation/remotePlayer.js +145 -26
- package/src/implementation/senzaShakaPlayer.js +31 -11
- package/src/interface/overlay.js +0 -1
- package/src/interface/remotePlayer.js +12 -0
- package/src/interface/version.js +1 -1
package/package.json
CHANGED
|
@@ -60,7 +60,7 @@ export async function init(interfaceApiVersion, showSequenceFunc, initSequenceFu
|
|
|
60
60
|
|
|
61
61
|
if (window.cefQuery) {
|
|
62
62
|
|
|
63
|
-
// First, check
|
|
63
|
+
// First, check compatibility with ui-streamer api version
|
|
64
64
|
await new Promise((resolve, reject) => {
|
|
65
65
|
window.cefQuery({
|
|
66
66
|
request: "apiVersion " + API_VERSION,
|
|
@@ -140,13 +140,15 @@ export async function init(interfaceApiVersion, showSequenceFunc, initSequenceFu
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
await Promise.all([
|
|
144
|
+
lifecycle._init(sessionInfoObj?.settings?.["ui-streamer"], triggerEvent),
|
|
145
|
+
remotePlayer._init(sessionInfoObj, triggerEvent),
|
|
146
|
+
displayManager._init(triggerEvent)
|
|
147
|
+
]);
|
|
148
|
+
|
|
146
149
|
alarmManager._init();
|
|
147
150
|
messageManager._init();
|
|
148
151
|
backgroundRenderControl._init();
|
|
149
|
-
await displayManager._init();
|
|
150
152
|
sdkLogger.log("All submodules initialized");
|
|
151
153
|
|
|
152
154
|
|
|
@@ -4,6 +4,7 @@ import { parseEdid } from "./edidParser";
|
|
|
4
4
|
import { EventListenersManager } from "./eventListenersManager";
|
|
5
5
|
import { bus, Events } from "./eventBus";
|
|
6
6
|
import { lifecycle } from "./lifecycle";
|
|
7
|
+
import { sessionInfo } from "./SessionInfo";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export class DisplayManager extends DisplayManagerInterface {
|
|
@@ -37,7 +38,20 @@ export class DisplayManager extends DisplayManagerInterface {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
async _init() {
|
|
41
|
+
async _init(triggerEvent) {
|
|
42
|
+
const enableCec = sessionInfo?.sessionInfoObj?.settings?.client?.hdmi?.cec?.enable_cec;
|
|
43
|
+
if (enableCec === false) {
|
|
44
|
+
sdkLogger.log("CEC disabled via config (client.hdmi.cec.enable_cec=false), skipping DisplayManager init");
|
|
45
|
+
this._setDisplayProperties({ connection: this.DisplayConnectionStatus.UNKNOWN });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// An endOfSession session runs after the connector is gone and never gets another one, so
|
|
49
|
+
// the display-info request could only be answered by something that is no longer there.
|
|
50
|
+
if (triggerEvent?.type === "endOfSession") {
|
|
51
|
+
sdkLogger.log("Session was triggered by endOfSession, no connector to query, skipping DisplayManager init");
|
|
52
|
+
this._setDisplayProperties({ connection: this.DisplayConnectionStatus.UNKNOWN });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
41
55
|
sdkLogger.log("Initializing DisplayManager");
|
|
42
56
|
await this._updateDisplayInfo();
|
|
43
57
|
if (!this._isInitialized) {
|
|
@@ -29,6 +29,7 @@ const MULTI_SEEK_DELAY_MSEC = 250;
|
|
|
29
29
|
const LOAD_START_SYNC_DIFF = 1;
|
|
30
30
|
const INVALID_SET_PLAYABLE_URI_CODE = 99;
|
|
31
31
|
export const ONE_DAY_SECONDS = 86400;
|
|
32
|
+
export const VOD_INIT_SEEK_EPSILON_SEC = 1;
|
|
32
33
|
const BACKGROUND_LOAD_MIN_SDK_VERSION = "4.5.0";
|
|
33
34
|
|
|
34
35
|
// Default timeout for waiting for the remote player confirmation
|
|
@@ -145,6 +146,14 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
145
146
|
*/
|
|
146
147
|
this._isSeekingByPlatform = false;
|
|
147
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Start position (seconds) from the most recent load(). Used to ignore the local player's
|
|
151
|
+
* first seeking event when VOD is loaded from a non-zero position (Shaka init seek).
|
|
152
|
+
* @type {number}
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
this._loadStartPosition = 0;
|
|
156
|
+
|
|
148
157
|
/**
|
|
149
158
|
* @type {boolean}
|
|
150
159
|
* @description field indicating if the remote player is playing abr or not. Should be changed when starting/stoping playback
|
|
@@ -225,14 +234,18 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
225
234
|
this._autoTune = false;
|
|
226
235
|
|
|
227
236
|
this._isPlaying = window?.sessionStorage?.getItem("senzaSdk_isPlaying") === "true";
|
|
237
|
+
this._textTrackVisibility = window?.sessionStorage?.getItem("senzaSdk_textTrackVisibility") === "true";
|
|
228
238
|
if (this._isPlaying) {
|
|
229
239
|
this._playbackPositionAnchor = getPlaybackInfo()?.playbackPosition || 0;
|
|
230
240
|
this._startPlaybackClock();
|
|
241
|
+
|
|
242
|
+
// UI release while playing: local player may load and seek; seed load start so that seek is treated as init.
|
|
243
|
+
this._loadStartPosition = this.currentTime;
|
|
231
244
|
} else {
|
|
232
245
|
this._stopPlaybackClock();
|
|
233
246
|
}
|
|
234
247
|
|
|
235
|
-
sdkLogger.info(`RemotePlayer initialized with load mode: ${this._loadMode}, senzaSdk_isPlaying: ${this._isPlaying}`);
|
|
248
|
+
sdkLogger.info(`RemotePlayer initialized with load mode: ${this._loadMode}, senzaSdk_isPlaying: ${this._isPlaying}, senzaSdk_textTrackVisibility: ${this._textTrackVisibility}`);
|
|
236
249
|
this._loadedUrl = playerState?.playbackUrl || "";
|
|
237
250
|
// Make sure that the event listeners are registered only once
|
|
238
251
|
if (!this._isInitialized) {
|
|
@@ -601,6 +614,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
601
614
|
this._isSeekingByPlatform = false;
|
|
602
615
|
this._isSeekingByApplication = false;
|
|
603
616
|
this._isPlayingEventPending = false;
|
|
617
|
+
this._loadStartPosition = 0;
|
|
604
618
|
this._stopPlaybackClock();
|
|
605
619
|
this._playbackPositionAnchor = 0;
|
|
606
620
|
}
|
|
@@ -949,6 +963,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
949
963
|
this._changePlayMode(false);
|
|
950
964
|
return new Promise((resolve, reject) => {
|
|
951
965
|
const playbackPosition = position || 0;
|
|
966
|
+
this._loadStartPosition = playbackPosition;
|
|
952
967
|
if (reset) {
|
|
953
968
|
// first, zero playback position
|
|
954
969
|
setPlaybackInfo({ playbackPosition: 0 });
|
|
@@ -1239,6 +1254,67 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1239
1254
|
return this._pause();
|
|
1240
1255
|
}
|
|
1241
1256
|
|
|
1257
|
+
/**
|
|
1258
|
+
* Mute or unmute the remote player audio stream.
|
|
1259
|
+
* This API can be called in any player load state (including not loaded).
|
|
1260
|
+
* Muting sets the remote player volume to zero while keeping audio download/sync behavior intact.
|
|
1261
|
+
* The mute state persists across content changes and remains in effect until unmute is called.
|
|
1262
|
+
* @param {boolean} muted true to mute, false to unmute
|
|
1263
|
+
* @returns {Promise}
|
|
1264
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
1265
|
+
*/
|
|
1266
|
+
mute(muted) {
|
|
1267
|
+
if (!this._isInitialized) {
|
|
1268
|
+
throw new RemotePlayerError(6500, "Cannot call mute() if remote player is not initialized");
|
|
1269
|
+
}
|
|
1270
|
+
if (typeof muted !== "boolean") {
|
|
1271
|
+
throw new RemotePlayerError(6503, `mute() expected boolean muted parameter but got ${typeof muted}`);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (window.cefQuery) {
|
|
1275
|
+
const FCID = getFCID();
|
|
1276
|
+
const logger = sdkLogger.withFields({ FCID });
|
|
1277
|
+
logger.log(`remotePlayer mute: sending mute request muted=${muted}`);
|
|
1278
|
+
const message = {
|
|
1279
|
+
type: "remotePlayer.mute",
|
|
1280
|
+
class: "remotePlayer",
|
|
1281
|
+
action: "mute",
|
|
1282
|
+
fcid: FCID,
|
|
1283
|
+
muted
|
|
1284
|
+
};
|
|
1285
|
+
const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
|
|
1286
|
+
return new Promise((resolve, reject) => {
|
|
1287
|
+
let timerId = 0;
|
|
1288
|
+
const timeBeforeSendingRequest = Date.now();
|
|
1289
|
+
const queryId = window.cefQuery({
|
|
1290
|
+
request: JSON.stringify(request),
|
|
1291
|
+
persistent: false,
|
|
1292
|
+
onSuccess: () => {
|
|
1293
|
+
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1294
|
+
logger.withFields({ duration }).log(`mute completed successfully after ${duration} ms`);
|
|
1295
|
+
timerId = clearTimer(timerId);
|
|
1296
|
+
resolve();
|
|
1297
|
+
},
|
|
1298
|
+
onFailure: (code, msg) => {
|
|
1299
|
+
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1300
|
+
logger.withFields({ duration }).log(`mute failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
1301
|
+
timerId = clearTimer(timerId);
|
|
1302
|
+
reject(new RemotePlayerError(code, msg));
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
logger.log(`window.cefQuery for mute returned query id ${queryId}`);
|
|
1306
|
+
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
1307
|
+
timerId = setTimeout(() => {
|
|
1308
|
+
logger.log(`mute reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
1309
|
+
window.cefQueryCancel(queryId);
|
|
1310
|
+
reject(new RemotePlayerError(6000, `mute reached timeout of ${timeout} ms`));
|
|
1311
|
+
}, timeout, queryId);
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
sdkLogger.error("remotePlayer mute: window.cefQuery is undefined");
|
|
1315
|
+
return Promise.resolve(undefined);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1242
1318
|
/** Stop playback of all streams (audio, video and subtitles).
|
|
1243
1319
|
* @returns {Promise}
|
|
1244
1320
|
* @throws {RemotePlayerError} error object contains code & msg
|
|
@@ -1641,14 +1717,33 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1641
1717
|
// Convert to boolean in case apps pass 0/1 instead false/true.
|
|
1642
1718
|
const newVisibility = !!visible;
|
|
1643
1719
|
|
|
1720
|
+
this._textTrackVisibility = newVisibility;
|
|
1721
|
+
window?.sessionStorage?.setItem("senzaSdk_textTrackVisibility", String(newVisibility));
|
|
1722
|
+
|
|
1644
1723
|
if (oldVisibility === newVisibility) {
|
|
1645
1724
|
return;
|
|
1646
1725
|
}
|
|
1647
1726
|
|
|
1648
|
-
this.
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1727
|
+
if (this._remotePlayerApiVersion <= 2) {
|
|
1728
|
+
if (!this._textTrackVisibility) {
|
|
1729
|
+
// For older versions, we need to clear the selected subtitles track
|
|
1730
|
+
this._selectedSubtitlesTrack = "";
|
|
1731
|
+
}
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
1736
|
+
if (!this._textTrackVisibility) {
|
|
1737
|
+
this._stop(StreamType.SUBTITLE).catch((error) => {
|
|
1738
|
+
sdkLogger.warn(`remotePlayer setTextTrackVisibility: stop subtitles failed with error ${error.message}.`);
|
|
1739
|
+
});
|
|
1740
|
+
} else {
|
|
1741
|
+
this._play(StreamType.SUBTITLE).catch((error) => {
|
|
1742
|
+
sdkLogger.warn(`remotePlayer setTextTrackVisibility: play subtitles failed with error ${error.message}.`);
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
} else {
|
|
1746
|
+
sdkLogger.info("remotePlayer setTextTrackVisibility V3+: not updating remote subtitle stream outside background");
|
|
1652
1747
|
}
|
|
1653
1748
|
}
|
|
1654
1749
|
|
|
@@ -1919,16 +2014,24 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1919
2014
|
this._pendingSeekPosition = playbackPosition;
|
|
1920
2015
|
this._setPlaybackPositionAnchor(playbackPosition);
|
|
1921
2016
|
// If the local player is just initializing after loading the manifest, ignore the seeking event.
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2017
|
+
// Live: Shaka jumps from 0 to a UTC wall-clock time (> one day in seconds).
|
|
2018
|
+
// VOD: load(url, startPosition) causes Shaka to seek near that start position (within epsilon).
|
|
2019
|
+
if (this._videoElement) {
|
|
2020
|
+
const currentTime = this._videoElement.currentTime;
|
|
2021
|
+
const isLiveInitSeek = this._localPlayerLoadCurrentTime === 0 && currentTime > ONE_DAY_SECONDS;
|
|
2022
|
+
const isVodInitSeek = this._localPlayerLoadCurrentTime === 0 && this._loadStartPosition > 0 && Math.abs(currentTime - this._loadStartPosition) <= VOD_INIT_SEEK_EPSILON_SEC;
|
|
2023
|
+
sdkLogger.info(`Check for init seek: currentTime: ${currentTime}, loadStartPosition: ${this._loadStartPosition}, isLiveInitSeek: ${isLiveInitSeek}, isVodInitSeek: ${isVodInitSeek}`);
|
|
2024
|
+
if (isLiveInitSeek || isVodInitSeek) {
|
|
2025
|
+
sdkLogger.info(`Seeking ignored for video currentTime init: currentTime: ${currentTime}, loadStartPosition: ${this._loadStartPosition}`);
|
|
2026
|
+
this._localPlayerLoadCurrentTime = currentTime;
|
|
2027
|
+
// if the difference between the remote player and the video element current time is greater than the load start sync diff (default 1 second), seek to the remote player current time
|
|
2028
|
+
if (isLiveInitSeek && this._isPlaying && this.currentTime > 0 && Math.abs(currentTime - this.currentTime) > this._loadStartSyncDiff) {
|
|
2029
|
+
sdkLogger.info(`Remote player is playing, seeking from ${currentTime} to ${this.currentTime}`);
|
|
2030
|
+
this._isSeekingByPlatform = true;
|
|
2031
|
+
this._videoElement.currentTime = this.currentTime;
|
|
2032
|
+
}
|
|
2033
|
+
return;
|
|
1930
2034
|
}
|
|
1931
|
-
return;
|
|
1932
2035
|
}
|
|
1933
2036
|
}
|
|
1934
2037
|
const backgroundSeekSupported = isAppVersionAboveOrEqual(this._deviceAppVersion, backgroundSeekMinClientAppVersion);
|
|
@@ -1943,6 +2046,31 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1943
2046
|
}
|
|
1944
2047
|
}
|
|
1945
2048
|
|
|
2049
|
+
/**
|
|
2050
|
+
* Resolve the initial target playing state from current playback and lifecycle state.
|
|
2051
|
+
*
|
|
2052
|
+
* | _isPlaying | lifecycle.state | _inTransitionToForeground | Result |
|
|
2053
|
+
* |------------|-----------------------------|---------------------------|--------------|
|
|
2054
|
+
* | false | any | any | PAUSED |
|
|
2055
|
+
* | true | FOREGROUND | any | PLAYING_UI |
|
|
2056
|
+
* | true | BACKGROUND | true | PLAYING_UI |
|
|
2057
|
+
* | true | BACKGROUND | false | PLAYING_ABR |
|
|
2058
|
+
* | true | IN_TRANSITION_TO_BACKGROUND | any | PLAYING_ABR |
|
|
2059
|
+
*
|
|
2060
|
+
* @private
|
|
2061
|
+
* @returns {string} TargetPlayingState value
|
|
2062
|
+
*/
|
|
2063
|
+
_getInitialTargetPlayingState() {
|
|
2064
|
+
if (!this._isPlaying) {
|
|
2065
|
+
return TargetPlayingState.PAUSED;
|
|
2066
|
+
}
|
|
2067
|
+
if (!lifecycle._inTransitionToForeground
|
|
2068
|
+
&& (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
|
|
2069
|
+
return TargetPlayingState.PLAYING_ABR;
|
|
2070
|
+
}
|
|
2071
|
+
return TargetPlayingState.PLAYING_UI;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1946
2074
|
/** Performs the seek operation which comprises the following steps:
|
|
1947
2075
|
* 1) Possible pause if playing
|
|
1948
2076
|
* 2) Wait for any further changes in the currentTime
|
|
@@ -1955,16 +2083,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1955
2083
|
* */
|
|
1956
2084
|
async _atomicSeek() {
|
|
1957
2085
|
sdkLogger.info("Seeking: local video element seeking start while isPlaying: ", this._isPlaying);
|
|
1958
|
-
|
|
1959
|
-
if (!(lifecycle._inTransitionToForeground || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND) && (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
|
|
1960
|
-
sdkLogger.info("seek in background", this._isPlaying);
|
|
1961
|
-
this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
1962
|
-
} else {
|
|
1963
|
-
this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
1964
|
-
}
|
|
1965
|
-
} else {
|
|
1966
|
-
this._targetSeekPlayingState = TargetPlayingState.PAUSED;
|
|
1967
|
-
}
|
|
2086
|
+
this._targetSeekPlayingState = this._getInitialTargetPlayingState();
|
|
1968
2087
|
|
|
1969
2088
|
// The platform could be currently syncing audio/video using playback rate. Reset when performing seek.
|
|
1970
2089
|
if (this._videoElement) {
|
|
@@ -2057,7 +2176,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
2057
2176
|
return Promise.resolve();
|
|
2058
2177
|
}
|
|
2059
2178
|
|
|
2060
|
-
this._targetSetAudioPlayingState = this.
|
|
2179
|
+
this._targetSetAudioPlayingState = this._getInitialTargetPlayingState();
|
|
2061
2180
|
sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetAudioPlayingState}`);
|
|
2062
2181
|
|
|
2063
2182
|
this._abortSetAudioLanguage = false;
|
|
@@ -2124,7 +2243,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
2124
2243
|
return Promise.resolve();
|
|
2125
2244
|
}
|
|
2126
2245
|
|
|
2127
|
-
this._targetSetSubtitlePlayingState = this.
|
|
2246
|
+
this._targetSetSubtitlePlayingState = this._getInitialTargetPlayingState();
|
|
2128
2247
|
sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetSubtitlePlayingState}`);
|
|
2129
2248
|
|
|
2130
2249
|
this._abortSetSubtitleLanguage = false;
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { ONE_DAY_SECONDS } from "./remotePlayer.js";
|
|
4
|
+
import { ONE_DAY_SECONDS, VOD_INIT_SEEK_EPSILON_SEC } from "./remotePlayer.js";
|
|
5
5
|
import { sdkLogger, SwitchMode } from "./utils";
|
|
6
6
|
import moment from "moment";
|
|
7
7
|
import set from "lodash.set";
|
|
@@ -156,12 +156,19 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
156
156
|
_seekCounterAfterLoad = 0;
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
* When true, the next `seeked` is Shaka jumping to live-edge
|
|
159
|
+
* When true, the next `seeked` is Shaka jumping to live-edge / VOD startTime after load; skip background pause logic.
|
|
160
160
|
* @private
|
|
161
161
|
* @type {boolean}
|
|
162
162
|
*/
|
|
163
163
|
_ignoreNextSeekedForInit = false;
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Start position (seconds) from the most recent load(). Used to ignore Shaka's VOD init seeking/seeked.
|
|
167
|
+
* @private
|
|
168
|
+
* @type {number}
|
|
169
|
+
*/
|
|
170
|
+
_loadStartPosition = 0;
|
|
171
|
+
|
|
165
172
|
/**
|
|
166
173
|
* Whether the player should call lifecycle.moveToForeground on pause, remote ended/error, unload, and destroy.
|
|
167
174
|
* Only when auto-background is enabled (including streamer overrides) and the UI moved to background via the
|
|
@@ -185,8 +192,16 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
185
192
|
this._ignoreNextSeekedForInit = false;
|
|
186
193
|
},
|
|
187
194
|
"seeking": () => {
|
|
188
|
-
//
|
|
189
|
-
|
|
195
|
+
// Ignore the first seeking after load when it is Shaka's initialization jump:
|
|
196
|
+
// - Live: currentTime is a UTC wall-clock time (> one day in seconds)
|
|
197
|
+
// - VOD: currentTime is within epsilon of this._loadStartPosition
|
|
198
|
+
const isFirstSeekAfterLoadAtZero = this._seekCounterAfterLoad < 1 && this._loadStartedAtZero;
|
|
199
|
+
const isLiveInitSeek = isFirstSeekAfterLoadAtZero && this.videoElement.currentTime > ONE_DAY_SECONDS;
|
|
200
|
+
const isVodInitSeek = isFirstSeekAfterLoadAtZero
|
|
201
|
+
&& this._loadStartPosition > 0
|
|
202
|
+
&& Math.abs(this.videoElement.currentTime - this._loadStartPosition) <= VOD_INIT_SEEK_EPSILON_SEC;
|
|
203
|
+
if (isLiveInitSeek || isVodInitSeek) {
|
|
204
|
+
sdkLogger.info("senzaShakaPlayer seeking: ignoring next seeked for init");
|
|
190
205
|
this._ignoreNextSeekedForInit = true;
|
|
191
206
|
} else {
|
|
192
207
|
// Another seeking before seeked means this is no longer the load initialization jump.
|
|
@@ -562,12 +577,13 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
562
577
|
// when timeout reached start the local playback anyway
|
|
563
578
|
this._handlePlayingEvent();
|
|
564
579
|
}, this._playingTimeout);
|
|
565
|
-
await this.remotePlayer.play(autoTune, switchMode)
|
|
566
|
-
.catch(error => {
|
|
567
|
-
sdkLogger.error("Failed to play remote player:", error);
|
|
568
|
-
this.handleSenzaError(error.code, error.message || "Unknown play error");
|
|
569
580
|
|
|
570
|
-
|
|
581
|
+
try {
|
|
582
|
+
await this.remotePlayer.play(autoTune, switchMode);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
sdkLogger.error("Failed to play remote player:", error);
|
|
585
|
+
this.handleSenzaError(error.code, error.message || "Unknown play error");
|
|
586
|
+
}
|
|
571
587
|
|
|
572
588
|
// Create a new promise that will resolve when the real play succeeds
|
|
573
589
|
return returnPromise;
|
|
@@ -786,8 +802,9 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
786
802
|
} catch (stopError) {
|
|
787
803
|
sdkLogger.error("Error while trying to stop video element playback:", stopError);
|
|
788
804
|
}
|
|
789
|
-
|
|
790
|
-
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (isCritical) {
|
|
791
808
|
this._handlePlayPromiseError(error);
|
|
792
809
|
}
|
|
793
810
|
|
|
@@ -820,6 +837,9 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
|
|
|
820
837
|
startTime = -minInitialLiveOffset;
|
|
821
838
|
}
|
|
822
839
|
|
|
840
|
+
// Used to ignore Shaka's init seeking/seeked when currentTime matches load start.
|
|
841
|
+
this._loadStartPosition = startTime ?? 0;
|
|
842
|
+
|
|
823
843
|
// This callback will be activated when the manifest is loaded. It will trigger load of the remote player.
|
|
824
844
|
// This will ensure that the remote player is loaded only after the manifest is loaded by local player.
|
|
825
845
|
const responseFilterCallback = async (type, response) => {
|
package/src/interface/overlay.js
CHANGED
|
@@ -10,7 +10,6 @@ import { noop } from "./utils.js";
|
|
|
10
10
|
/**
|
|
11
11
|
* Overlay is a singleton class that manages displaying UI frames as image overlays. The image can be displayed over background video playback.
|
|
12
12
|
* It can be used in any lifecycle state.
|
|
13
|
-
* @private
|
|
14
13
|
* @fires error
|
|
15
14
|
*/
|
|
16
15
|
class Overlay extends EventTarget {
|
|
@@ -340,6 +340,18 @@ class RemotePlayer extends EventTarget {
|
|
|
340
340
|
return noop("RemotePlayer.pause");
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/** Mute or unmute the remote player audio stream.
|
|
344
|
+
* This API can be called in any player load state (including not loaded).
|
|
345
|
+
* Muting sets the remote player volume to zero while keeping audio download/sync behavior intact.
|
|
346
|
+
* The mute state persists across content changes and remains in effect until unmute is called.
|
|
347
|
+
* @param {boolean} muted true to mute, false to unmute
|
|
348
|
+
* @returns {Promise}
|
|
349
|
+
* @throws {RemotePlayerError} error object contains code & msg
|
|
350
|
+
*/
|
|
351
|
+
async mute(muted) {
|
|
352
|
+
return noop("RemotePlayer.mute", muted);
|
|
353
|
+
}
|
|
354
|
+
|
|
343
355
|
/** Stop playback of all streams (audio, video and subtitles).
|
|
344
356
|
* @returns {Promise}
|
|
345
357
|
* @throws {RemotePlayerError} error object contains code & msg
|
package/src/interface/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "4.
|
|
1
|
+
export const version = "4.6.1";
|