@applicaster/zapp-react-native-utils 12.0.0-alpha.5895113034 → 12.0.0-alpha.5933014273

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.
Files changed (68) hide show
  1. package/analyticsUtils/AnalyticPlayerListener.ts +2 -16
  2. package/analyticsUtils/AnalyticsEvents/index.ts +7 -0
  3. package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
  4. package/analyticsUtils/events.ts +6 -0
  5. package/analyticsUtils/index.tsx +6 -0
  6. package/appUtils/accessibilityManager/const.ts +50 -0
  7. package/appUtils/accessibilityManager/hooks.ts +40 -0
  8. package/appUtils/accessibilityManager/index.ts +166 -0
  9. package/appUtils/accessibilityManager/logger.ts +5 -0
  10. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
  11. package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
  12. package/appUtils/focusManager/__tests__/index.test.ts +2 -3
  13. package/appUtils/focusManager/index.ts +76 -57
  14. package/appUtils/focusManager/treeDataStructure/Utils/index.js +75 -44
  15. package/appUtils/focusManager/utils/index.ts +100 -0
  16. package/appUtils/focusManagerAux/index.ts +76 -0
  17. package/appUtils/focusManagerAux/utils/index.ts +100 -0
  18. package/appUtils/index.js +2 -0
  19. package/appUtils/keyCodes/keys.web.js +8 -0
  20. package/appUtils/keyCodes/keysByPlatform.js +11 -1
  21. package/appUtils/platform/const.ts +25 -0
  22. package/appUtils/platform/index.ts +3 -0
  23. package/appUtils/platform/platformUtils.ts +217 -0
  24. package/appUtils/playerManager/conts.ts +1 -0
  25. package/appUtils/playerManager/index.ts +11 -5
  26. package/appUtils/playerManager/nativePlayerManager.ts +12 -0
  27. package/appUtils/playerManager/player.ts +14 -16
  28. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +57 -0
  29. package/appUtils/playerManager/playerNative.ts +5 -15
  30. package/appUtils/playerManager/usePlayerControllerSetup.tsx +5 -18
  31. package/appUtils/playerManager/usePlayerState.tsx +19 -34
  32. package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
  33. package/arrayUtils/index.ts +5 -0
  34. package/booleanUtils/index.ts +4 -4
  35. package/cellUtils/index.ts +38 -5
  36. package/functionUtils/index.ts +38 -0
  37. package/index.d.ts +15 -0
  38. package/keyboardState/index.ts +0 -0
  39. package/navigationUtils/index.ts +48 -0
  40. package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
  41. package/numberUtils/__tests__/toNumber.test.ts +2 -2
  42. package/numberUtils/index.ts +5 -18
  43. package/package.json +2 -2
  44. package/playerUtils/imaScriptDom.ts +59 -0
  45. package/pluginUtils/index.ts +9 -0
  46. package/reactHooks/cell-click/__tests__/index.test.js +9 -5
  47. package/reactHooks/cell-click/index.ts +11 -11
  48. package/reactHooks/debugging/__tests__/index.test.js +81 -11
  49. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
  50. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +6 -2
  51. package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
  52. package/reactHooks/feed/useFeedRefresh.tsx +9 -7
  53. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -0
  54. package/reactHooks/navigation/index.ts +44 -7
  55. package/reactHooks/player/__tests__/useAutoSeek.test.tsx +1 -8
  56. package/reactHooks/player/useAutoSeek.tsx +8 -28
  57. package/reactHooks/player/useSeek.tsx +2 -4
  58. package/reactHooks/player/useTapSeek.tsx +7 -34
  59. package/reactHooks/resolvers/useCellResolver.ts +5 -1
  60. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
  61. package/reactHooks/screen/index.ts +2 -0
  62. package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
  63. package/reactHooks/screen/useScreenContext.ts +1 -2
  64. package/reactUtils/index.ts +104 -0
  65. package/appUtils/focusManager/FocusableEvents.ts +0 -8
  66. package/appUtils/focusManager/treeDataStructure/Utils/__tests__/Utils_android.test.js +0 -1094
  67. package/appUtils/focusManager/treeDataStructure/Utils/index.android.js +0 -261
  68. package/appUtils/playerManager/DelayedSeekPlayerWrapper.tsx +0 -179
@@ -0,0 +1,217 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+
3
+ import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
4
+ import {
5
+ isLgPlatform,
6
+ isSamsungPlatform,
7
+ } from "@applicaster/zapp-react-dom-app/App/Loader/utils/platform";
8
+
9
+ import { PLATFORM_KEYS, PLATFORMS } from "./const";
10
+ import { createLogger, utilsLogger } from "../../logger";
11
+
12
+ declare global {
13
+ interface Window {
14
+ VIZIO?: {
15
+ exitApplication: () => void;
16
+ Chromevox: {
17
+ play: (text: string) => void;
18
+ };
19
+ setClosedCaptionHandler: (
20
+ callback: (isCCEnabled: boolean) => void
21
+ ) => void;
22
+ };
23
+ }
24
+ }
25
+
26
+ const { log_debug } = createLogger({
27
+ category: "General",
28
+ subsystem: "PlatformUtils",
29
+ parent: utilsLogger,
30
+ });
31
+
32
+ export const getUserAgent = () =>
33
+ window?.navigator?.userAgent || global?.navigator?.userAgent;
34
+
35
+ /** VIZIO UTILS */
36
+
37
+ /**
38
+ * Checks if we are on the Vizio platform by using the userAgent string
39
+ *
40
+ * Use this method when you need to identify the platform on runtime
41
+ * before the VIZIO Companion Library has loaded
42
+ */
43
+ export const isVizioPlatform = () => {
44
+ const userAgent = getUserAgent();
45
+
46
+ const isVizioAgent =
47
+ userAgent.includes(PLATFORMS.vizio) ||
48
+ userAgent.includes(PLATFORMS.smartcast) ||
49
+ userAgent.includes(PLATFORMS.conjure);
50
+
51
+ return isVizioAgent;
52
+ };
53
+
54
+ /**
55
+ * Checks if we are on the Vizio platform by checking for the global VIZIO object
56
+ * and the VIZIO Companion Library. This method does not use the userAgent string,
57
+ * because it mainly checks whether we have access to apis, which may not be available yet
58
+ */
59
+ export const hasVizioAPIs = () => {
60
+ const hasAPIs =
61
+ typeof window.VIZIO !== "undefined" &&
62
+ window?.applicaster?.vizioLibraryDidLoad;
63
+
64
+ return hasAPIs;
65
+ };
66
+
67
+ /**
68
+ * Manages device based closed captioning state
69
+ * Returns an observable that emits the current state and updates whenever it changes
70
+ */
71
+ export class ClosedCaptioningManager {
72
+ private ccState$ = new BehaviorSubject<boolean>(false);
73
+ private static ccManagerInstance: ClosedCaptioningManager;
74
+
75
+ private constructor() {
76
+ this.initialize();
77
+ }
78
+
79
+ static getInstance(): ClosedCaptioningManager {
80
+ if (!ClosedCaptioningManager.ccManagerInstance) {
81
+ ClosedCaptioningManager.ccManagerInstance = new ClosedCaptioningManager();
82
+ }
83
+
84
+ return ClosedCaptioningManager.ccManagerInstance;
85
+ }
86
+
87
+ async initialize() {
88
+ const existingState = await sessionStorage.getItem(
89
+ PLATFORM_KEYS.deviceClosedCaptioningEnabled
90
+ );
91
+
92
+ log_debug("Initializing closed captioning manager", { existingState });
93
+
94
+ if (existingState !== null) {
95
+ this.ccState$.next(existingState === "true" || existingState === true);
96
+ }
97
+
98
+ if (hasVizioAPIs()) {
99
+ window.VIZIO!.setClosedCaptionHandler((isCCEnabled) => {
100
+ log_debug("CC state changed", { isCCEnabled });
101
+
102
+ sessionStorage.setItem(
103
+ PLATFORM_KEYS.deviceClosedCaptioningEnabled,
104
+ isCCEnabled
105
+ );
106
+
107
+ this.ccState$.next(isCCEnabled);
108
+ });
109
+ }
110
+ }
111
+
112
+ getCurrentState(): boolean {
113
+ return this.ccState$.getValue();
114
+ }
115
+
116
+ getStateAsObservable() {
117
+ return this.ccState$.asObservable();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Subscribe to closed captioning state changes
123
+ * @returns Observable<boolean>
124
+ * @example
125
+ * const subscription = getClosedCaptionState().subscribe(
126
+ * isEnabled => setClosedCaptionState(isEnabled)
127
+ * );
128
+ * subscription.unsubscribe(), []);
129
+ */
130
+ export const getClosedCaptionState = () => {
131
+ const ccManager = ClosedCaptioningManager.getInstance();
132
+
133
+ return ccManager.getStateAsObservable();
134
+ };
135
+
136
+ /**
137
+ * Manages device based closed captioning state
138
+ * Returns an observable that emits the current state and updates whenever it changes
139
+ */
140
+ export class TTSManager {
141
+ private ttsState$ = new BehaviorSubject<boolean>(false);
142
+ private static ttsManagerInstance: TTSManager;
143
+
144
+ private constructor() {
145
+ this.initialize();
146
+ }
147
+
148
+ static getInstance(): TTSManager {
149
+ if (!TTSManager.ttsManagerInstance) {
150
+ TTSManager.ttsManagerInstance = new TTSManager();
151
+ }
152
+
153
+ return TTSManager.ttsManagerInstance;
154
+ }
155
+
156
+ async initialize() {
157
+ if (!isVizioPlatform()) return;
158
+
159
+ document.addEventListener(
160
+ "VIZIO_TTS_ENABLED",
161
+ () => {
162
+ this.ttsState$.next(true);
163
+ },
164
+ false
165
+ );
166
+
167
+ document.addEventListener(
168
+ "VIZIO_TTS_DISABLED",
169
+ () => {
170
+ this.ttsState$.next(false);
171
+ },
172
+ false
173
+ );
174
+ }
175
+
176
+ getCurrentState(): boolean {
177
+ return this.ttsState$.getValue();
178
+ }
179
+
180
+ getStateAsObservable() {
181
+ return this.ttsState$.asObservable();
182
+ }
183
+
184
+ readText(text: string) {
185
+ if (isSamsungPlatform() && window.speechSynthesis) {
186
+ const utterance = new SpeechSynthesisUtterance(text);
187
+ window.speechSynthesis.speak(utterance);
188
+ }
189
+
190
+ if (isLgPlatform() && window.webOS?.service) {
191
+ try {
192
+ window.webOS.service.request("luna://com.webos.service.tts", {
193
+ method: "speak",
194
+ onFailure(error: any) {
195
+ log_debug("There was a failure setting up webOS TTS service", {
196
+ error,
197
+ });
198
+ },
199
+ onSuccess(response: any) {
200
+ log_debug("webOS TTS service is configured successfully", {
201
+ response,
202
+ });
203
+ },
204
+ parameters: {
205
+ text,
206
+ },
207
+ });
208
+ } catch (error) {
209
+ log_debug("webOS TTS service error", { error });
210
+ }
211
+ }
212
+
213
+ if (!window.VIZIO?.Chromevox) return;
214
+
215
+ window.VIZIO.Chromevox.play(text);
216
+ }
217
+ }
@@ -7,6 +7,7 @@ export enum SharedPlayerCallBacksKeys {
7
7
  OnPlayerPause = "onPlayerPause",
8
8
  OnLoad = "onLoad",
9
9
  OnError = "onError",
10
+ OnVideoError = "onVideoError",
10
11
  OnVideoEnd = "onVideoEnd",
11
12
  OnPlaybackRateChange = "onPlaybackRateChange",
12
13
  OnVideoLoad = "onVideoLoad",
@@ -10,6 +10,7 @@ import {
10
10
  } from "@applicaster/quick-brick-core/App/NetworkStatusProvider/utils";
11
11
  import { setUserLanguagePreference } from "./userLanguagePreference";
12
12
  import { PlayerRole } from "./conts";
13
+ import { RNPlayerManager } from "./nativePlayerManager";
13
14
 
14
15
  const { log_error, log_debug, log_warning } = createLogger({
15
16
  category: "General",
@@ -168,6 +169,10 @@ export class PlayerManager {
168
169
  this.addPlayer(id, playerController);
169
170
 
170
171
  if (playerController.playerRole === PlayerRole.Primary) {
172
+ if (playerController.playerPluginId !== "QuickBrickPlayerPlugin") {
173
+ RNPlayerManager?.["QuickBrickPlayerPlugin"]?.stopBackgroundPlayback?.();
174
+ }
175
+
171
176
  playerController.addListener({
172
177
  id: this.getPlayerListenerId(id),
173
178
  listener: {
@@ -343,9 +348,9 @@ export class PlayerManager {
343
348
  // TODO: Remove
344
349
  if (this.castingReceiver) {
345
350
  this.castingReceiver.rewind(deltaTime);
351
+ } else {
352
+ this.getActivePlayer()?.rewind(deltaTime);
346
353
  }
347
-
348
- this.getActivePlayer()?.rewind(deltaTime);
349
354
  }
350
355
 
351
356
  public forward(deltaTime = null) {
@@ -353,9 +358,9 @@ export class PlayerManager {
353
358
 
354
359
  if (this.castingReceiver) {
355
360
  this.castingReceiver.forward(deltaTime);
361
+ } else {
362
+ this.getActivePlayer()?.forward(deltaTime);
356
363
  }
357
-
358
- this.getActivePlayer()?.forward(deltaTime);
359
364
  }
360
365
 
361
366
  public seeking(time) {
@@ -418,7 +423,7 @@ export class PlayerManager {
418
423
  }
419
424
 
420
425
  // Call method on player just in case
421
- return this.getActivePlayer()?.setLanguage(track);
426
+ return this.getActivePlayer()?.selectTrack(track);
422
427
  }
423
428
 
424
429
  public isRegistered() {
@@ -443,6 +448,7 @@ export class PlayerManager {
443
448
  if (this.castingReceiver) {
444
449
  this.castingReceiver.closeNativePlayer();
445
450
  } else {
451
+ this.getActivePlayer().closeNativePlayer();
446
452
  this.invokeHandler("close");
447
453
  }
448
454
  }
@@ -0,0 +1,12 @@
1
+ import { NativeModules } from "react-native";
2
+ import { isApplePlatform } from "../../reactUtils";
3
+
4
+ export const RNPlayerManager = {
5
+ QuickBrickPlayerPlugin: isApplePlatform()
6
+ ? NativeModules.PlayerModule
7
+ : NativeModules.QuickBrickDefaultPlayerModule,
8
+ KalturaPlayerPlugin: isApplePlatform()
9
+ ? NativeModules.KalturaPlayerModule
10
+ : NativeModules.QBKalturaPlayerModule,
11
+ "quick-brick-player-brightcove": NativeModules.BrightcovePlayerModule,
12
+ };
@@ -91,8 +91,6 @@ export class Player {
91
91
  this.onVideoProgress = this.onVideoProgress.bind(this);
92
92
  this.onVideoLoad = this.onVideoLoad.bind(this);
93
93
  this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
94
- this.onPlayerSeekComplete = this.onPlayerSeekComplete.bind(this);
95
-
96
94
  this.entryObservable = null;
97
95
  }
98
96
 
@@ -323,7 +321,7 @@ export class Player {
323
321
  * You should use dispatchPlayerEvent at PlayerContainer or call playerManager.invokeHandler directly
324
322
  * This allows you to use one method for everything.
325
323
  */
326
- onPlayerSeekStart = (event: QuickBrickPlayer.OnSeekStartEvent) => {
324
+ onSeekStart = (event: QuickBrickPlayer.OnSeekStartEvent) => {
327
325
  const { isLive, seekableDuration, currentTime, duration, seekTime } = event;
328
326
 
329
327
  this.setPlaybackState({ currentTime, duration, seekableDuration, isLive });
@@ -338,11 +336,10 @@ export class Player {
338
336
  });
339
337
  };
340
338
 
341
- onPlayerSeekComplete(event: QuickBrickPlayer.OnSeekCompleteEvent) {
339
+ onSeekComplete = (event: QuickBrickPlayer.OnSeekCompleteEvent) => {
342
340
  const { isLive, seekableDuration, currentTime, duration } = event;
343
341
 
344
342
  this.setPlaybackState({ currentTime, duration, seekableDuration, isLive });
345
-
346
343
  this.playerState.seekPosition = null;
347
344
 
348
345
  if (!isLive) {
@@ -353,7 +350,7 @@ export class Player {
353
350
  callbackName: SharedPlayerCallBacksKeys.OnPlayerSeekComplete,
354
351
  event,
355
352
  });
356
- }
353
+ };
357
354
 
358
355
  onVideoEnd = (event) => {
359
356
  this.invokeListenersUpdate({
@@ -362,6 +359,13 @@ export class Player {
362
359
  });
363
360
  };
364
361
 
362
+ onVideoError = (error: Error) => {
363
+ this.invokeListenersUpdate({
364
+ callbackName: SharedPlayerCallBacksKeys.OnVideoError,
365
+ event: error,
366
+ });
367
+ };
368
+
365
369
  onError = (error: Error) => {
366
370
  this.invokeListenersUpdate({
367
371
  callbackName: SharedPlayerCallBacksKeys.OnError,
@@ -649,10 +653,11 @@ export class Player {
649
653
  };
650
654
 
651
655
  public getListener = (): QuickBrickPlayer.SharedPlayerCallBacks => ({
652
- onPlayerSeekStart: this.onPlayerSeekStart,
653
- onPlayerSeekComplete: this.onPlayerSeekComplete,
656
+ onPlayerSeekStart: this.onSeekStart,
657
+ onPlayerSeekComplete: this.onSeekComplete,
654
658
  onVideoEnd: this.onVideoEnd,
655
659
  onError: this.onError,
660
+ onVideoError: this.onVideoError,
656
661
  onLoad: this.onLoad,
657
662
  onPlayerPause: this.onPlayerPause,
658
663
  onPlayerResume: this.onPlayerResume,
@@ -687,9 +692,6 @@ export class Player {
687
692
  play = () => {};
688
693
  pause = () => {};
689
694
  disableBufferAnimation = (): boolean => true;
690
- setLanguage = (
691
- _selected?: QuickBrickPlayer.TextTrack | QuickBrickPlayer.AudioTrack
692
- ) => {};
693
695
 
694
696
  setPlaybackRate = (_rate) => {};
695
697
  startSleepTimer = (_timestamp) => {};
@@ -731,9 +733,5 @@ export class Player {
731
733
 
732
734
  public supportsNativeControls = (): boolean => false;
733
735
  public supportNativeCast = (): boolean => false;
734
-
735
- public isInDelayedSeek = (): boolean => false;
736
- public enterDelayedSeek() {}
737
-
738
- public exitDelayedSeek() {}
736
+ public destroy = () => {};
739
737
  }
@@ -0,0 +1,57 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { Player } from "../player";
3
+ import { usePlayer } from "../usePlayer";
4
+ import { useThrottle } from "../../../functionUtils";
5
+
6
+ /**
7
+ *
8
+ * @param listenerId - A unique identifier for the listener.
9
+ * @param playerId - (Optional) The ID of the player to observe.
10
+ * @returns The current playback time in seconds.
11
+ */
12
+ export const usePlayerCurrentTime = (
13
+ listenerId: string,
14
+ playerId?: string
15
+ ): number | null => {
16
+ const [currentTime, setCurrentTime] = useState<number | null>(null);
17
+ const player: Player = usePlayer(playerId);
18
+
19
+ const updateCurrentTime = useCallback(() => {
20
+ if (player) {
21
+ const newCurrentTime = player.getContentPosition();
22
+
23
+ setCurrentTime((prevTime) =>
24
+ prevTime !== newCurrentTime ? newCurrentTime : prevTime
25
+ );
26
+ }
27
+ }, [player]);
28
+
29
+ const { throttledFunction: throttledUpdate, cancel } = useThrottle(
30
+ updateCurrentTime,
31
+ 1000
32
+ );
33
+
34
+ useEffect(() => {
35
+ if (player) {
36
+ throttledUpdate();
37
+
38
+ const listeners = {
39
+ onVideoProgress: throttledUpdate,
40
+ };
41
+
42
+ const id = `${listenerId}_player_current_time`;
43
+
44
+ player.addListener({
45
+ id: id,
46
+ listener: listeners,
47
+ });
48
+
49
+ return () => {
50
+ player.removeListener(id);
51
+ cancel();
52
+ };
53
+ }
54
+ }, [player, listenerId, throttledUpdate, cancel]);
55
+
56
+ return currentTime;
57
+ };
@@ -2,19 +2,10 @@ import * as R from "ramda";
2
2
  import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
3
3
  import { createLogger, utilsLogger } from "../../logger";
4
4
  import { Player } from "./player";
5
- import { findNodeHandle, NativeModules } from "react-native";
6
- import { isApplePlatform } from "../../reactUtils";
5
+ import { findNodeHandle } from "react-native";
7
6
  import { PlayerRole } from "./conts";
8
7
  import { toBooleanWithDefaultFalse } from "../../booleanUtils";
9
-
10
- const RNPlayerManager = {
11
- QuickBrickPlayerPlugin: isApplePlatform()
12
- ? NativeModules.PlayerModule
13
- : NativeModules.QuickBrickDefaultPlayerModule,
14
- KalturaPlayerPlugin: isApplePlatform()
15
- ? NativeModules.KalturaPlayerModule
16
- : NativeModules.QBKalturaPlayerModule,
17
- };
8
+ import { RNPlayerManager } from "./nativePlayerManager";
18
9
 
19
10
  const logger = createLogger({
20
11
  category: "PlayerNativeController",
@@ -197,10 +188,10 @@ export class PlayerNative extends Player {
197
188
 
198
189
  disableBufferAnimation = (): boolean => false;
199
190
 
200
- setLanguage = (
191
+ selectTrack = (
201
192
  selected: QuickBrickPlayer.TextTrack | QuickBrickPlayer.AudioTrack
202
193
  ) => {
203
- this.currentPlayerComponent()?.["setLanguage"]?.(selected);
194
+ this.currentPlayerComponent()?.["selectTrack"]?.(selected);
204
195
  };
205
196
 
206
197
  setPlaybackRate = (rate: number) => {
@@ -231,10 +222,9 @@ export class PlayerNative extends Player {
231
222
 
232
223
  onVideoLoad = (event) => {
233
224
  if (this.startPosition) {
225
+ super.onVideoLoad({ ...event, ignoreContinueWatching: true });
234
226
  this.seekTo(this.startPosition);
235
227
  this.startPosition = null;
236
-
237
- super.onVideoLoad({ ...event, ignoreContinueWatching: true });
238
228
  } else {
239
229
  super.onVideoLoad(event);
240
230
  }
@@ -5,8 +5,6 @@ import { playerManager } from "./index";
5
5
  import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
6
6
  import { PlayerRole } from "./conts";
7
7
  import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
8
- import { isTV } from "../../reactUtils";
9
- import { DelayedSeekPlayerWrapper } from "./DelayedSeekPlayerWrapper";
10
8
 
11
9
  // TODO: Rename to ControllerType
12
10
  export const usePlayerControllerSetup = ({
@@ -41,7 +39,7 @@ export const usePlayerControllerSetup = ({
41
39
  ? { supports_native_controls: true, ...config }
42
40
  : config;
43
41
 
44
- const combinedProps = {
42
+ const newPlayerController = new ControllerType({
45
43
  player: ref,
46
44
  listener: props as any,
47
45
  config: newConfig,
@@ -49,22 +47,10 @@ export const usePlayerControllerSetup = ({
49
47
  entry,
50
48
  playerPluginId: pluginId,
51
49
  playerRole: PlayerRole.Primary,
52
- };
53
-
54
- if (isTV()) {
55
- const wrappedPlayerController = new ControllerType({
56
- ...combinedProps,
57
- playerRole: PlayerRole.Unspecified,
58
- playerId: playerId + "." + "wrapped",
59
- });
50
+ });
60
51
 
61
- setPlayerController(
62
- new DelayedSeekPlayerWrapper(combinedProps, wrappedPlayerController)
63
- );
64
- } else {
65
- setPlayerController(new ControllerType(combinedProps));
66
- }
67
- }, [playerId, entry?.id]);
52
+ setPlayerController(newPlayerController);
53
+ }, [playerId, entry?.id, ref.current]);
68
54
 
69
55
  React.useEffect(() => {
70
56
  if (!playerController) {
@@ -86,6 +72,7 @@ export const usePlayerControllerSetup = ({
86
72
 
87
73
  return () => {
88
74
  playerManager.unregisterPlayer(playerId);
75
+ playerController.destroy?.();
89
76
  };
90
77
  }, [playerId, playerController]);
91
78
 
@@ -16,24 +16,28 @@ export const usePlayerState = (
16
16
  listenerId: string,
17
17
  playerId?: string
18
18
  ): PlayerState => {
19
- const [currentTime, setCurrentTime] = React.useState<number>(null);
20
- const [duration, setDuration] = React.useState<number>(null);
21
- const [seekableDuration, setSeekableDuration] = React.useState<number>(null);
22
- const [isLive, setIsLive] = React.useState<boolean>(null);
23
- const [isBuffering, setIsBuffering] = React.useState<boolean>(false);
24
- const [isReadyToPlay, setIsReadyToPlay] = React.useState<boolean>(false);
25
- const [isPaused, setIsPaused] = React.useState<boolean>(false);
19
+ const [state, setState] = React.useState<PlayerState>({
20
+ currentTime: null,
21
+ duration: null,
22
+ seekableDuration: null,
23
+ isLive: null,
24
+ isPaused: null,
25
+ isBuffering: false,
26
+ isReadyToPlay: false,
27
+ });
26
28
 
27
29
  const player: Player = usePlayer(playerId);
28
30
 
29
31
  const onPlayerChangeState = React.useCallback(() => {
30
- setIsBuffering(player.isBuffering());
31
- setIsReadyToPlay(player.isReadyToPlay);
32
- setCurrentTime(player.getContentPosition());
33
- setDuration(player.getDuration());
34
- setSeekableDuration(player.getSeekableDuration());
35
- setIsLive(player.isLive());
36
- setIsPaused(player.isPaused());
32
+ setState({
33
+ currentTime: player.getContentPosition(),
34
+ duration: player.getDuration(),
35
+ seekableDuration: player.getSeekableDuration(),
36
+ isLive: player.isLive(),
37
+ isPaused: player.isPaused(),
38
+ isBuffering: player.isBuffering(),
39
+ isReadyToPlay: player.isReadyToPlay(),
40
+ });
37
41
  }, [player]);
38
42
 
39
43
  React.useEffect(() => {
@@ -55,24 +59,5 @@ export const usePlayerState = (
55
59
  }
56
60
  }, [player]);
57
61
 
58
- return React.useMemo(
59
- () => ({
60
- currentTime,
61
- duration,
62
- seekableDuration,
63
- isLive,
64
- isPaused,
65
- isBuffering,
66
- isReadyToPlay,
67
- }),
68
- [
69
- currentTime,
70
- duration,
71
- seekableDuration,
72
- isLive,
73
- isPaused,
74
- isBuffering,
75
- isReadyToPlay,
76
- ]
77
- );
62
+ return state;
78
63
  };
@@ -0,0 +1,63 @@
1
+ import { isFilledArray } from "..";
2
+
3
+ describe("isFilledArray", () => {
4
+ it("non-empty array is not empty", () => {
5
+ const value = [1, 2, 3];
6
+
7
+ expect(isFilledArray(value)).toBe(true);
8
+ });
9
+
10
+ it("empty array is empty", () => {
11
+ const value = [];
12
+
13
+ expect(isFilledArray(value)).toBe(false);
14
+ });
15
+
16
+ it("number is not array", () => {
17
+ const value = 123;
18
+
19
+ expect(isFilledArray(value)).toBe(false);
20
+ });
21
+
22
+ it("string is not array", () => {
23
+ const value = "vfnjdk";
24
+
25
+ expect(isFilledArray(value)).toBe(false);
26
+ });
27
+
28
+ it("empty string is not array", () => {
29
+ const value = "";
30
+
31
+ expect(isFilledArray(value)).toBe(false);
32
+ });
33
+
34
+ it("NaN is not array", () => {
35
+ const value = "";
36
+
37
+ expect(isFilledArray(value)).toBe(false);
38
+ });
39
+
40
+ it("object is not array", () => {
41
+ const value = { test: 1 };
42
+
43
+ expect(isFilledArray(value)).toBe(false);
44
+ });
45
+
46
+ it("empty object is not array", () => {
47
+ const value = {};
48
+
49
+ expect(isFilledArray(value)).toBe(false);
50
+ });
51
+
52
+ it("undefined is not array", () => {
53
+ const value = undefined;
54
+
55
+ expect(isFilledArray(value)).toBe(false);
56
+ });
57
+
58
+ it("null is not array", () => {
59
+ const value = null;
60
+
61
+ expect(isFilledArray(value)).toBe(false);
62
+ });
63
+ });
@@ -95,3 +95,8 @@ export const makeListOfIndexes = (size: number): number[] =>
95
95
  export const makeListOf = (value: unknown, size: number): number[] => {
96
96
  return Array(size).fill(value);
97
97
  };
98
+
99
+ /** Checks if a value is a non-empty array */
100
+ export function isFilledArray(value: unknown): boolean {
101
+ return R.is(Array, value) && R.length(value) > 0;
102
+ }