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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.5.9",
3
+ "version": "4.6.1",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@ export async function init(interfaceApiVersion, showSequenceFunc, initSequenceFu
60
60
 
61
61
  if (window.cefQuery) {
62
62
 
63
- // First, check compatability with ui-streamer api version
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
- // Initialize lifecycle first to make sure the state is updated.
144
- await lifecycle._init(sessionInfoObj?.settings?.["ui-streamer"], triggerEvent);
145
- await remotePlayer._init(sessionInfoObj, triggerEvent);
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._textTrackVisibility = newVisibility;
1649
- if (!this._textTrackVisibility) {
1650
- // Setting the visibility to false clears any previous selections user has done
1651
- this._selectedSubtitlesTrack = "";
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
- if (this._localPlayerLoadCurrentTime === 0 && this._videoElement.currentTime > ONE_DAY_SECONDS) {
1923
- sdkLogger.info(`Seeking ignored for video currentTime init: currentTime: ${this._videoElement.currentTime}`);
1924
- this._localPlayerLoadCurrentTime = this._videoElement.currentTime;
1925
- // 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
1926
- if (this._isPlaying && this.currentTime > 0 && Math.abs(this._videoElement.currentTime - this.currentTime) > this._loadStartSyncDiff) {
1927
- sdkLogger.info(`Remote player is playing, seeking from ${this._videoElement.currentTime} to ${this.currentTime}`);
1928
- this._isSeekingByPlatform = true;
1929
- this._videoElement.currentTime = this.currentTime;
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
- if (this._isPlaying) {
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._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
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._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
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 time after load; skip background pause logic.
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
- // If the first seeking after load started from time 0, and the current time is a UTC time (live asset), ignore the next seeked event to avoid background pause logic.
189
- if (this._seekCounterAfterLoad < 1 && this._loadStartedAtZero && this.videoElement.currentTime > ONE_DAY_SECONDS) {
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
- // Handle error while waiting for play event. If the error is not critical or the system is configured not to stop remote player on error,
790
- // the playback timeout will start the local playback
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) => {
@@ -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
@@ -1 +1 @@
1
- export const version = "4.5.9";
1
+ export const version = "4.6.1";