@applicaster/zapp-react-native-utils 13.0.0-rc.14 → 13.0.0-rc.141

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 (185) hide show
  1. package/README.md +0 -10
  2. package/actionsExecutor/ActionExecutor.ts +16 -9
  3. package/actionsExecutor/ActionExecutorContext.tsx +214 -16
  4. package/actionsExecutor/consts.ts +4 -0
  5. package/alertUtils/alert.ts +148 -0
  6. package/alertUtils/index.ts +1 -148
  7. package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
  8. package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
  9. package/analyticsUtils/currentScreenProperities.ts +6 -4
  10. package/analyticsUtils/helpers/hooks.ts +1 -1
  11. package/appUtils/HooksManager/Hook.ts +6 -2
  12. package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +7 -7
  13. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +186 -186
  14. package/appUtils/HooksManager/index.ts +3 -1
  15. package/appUtils/HooksManager/types.ts +5 -2
  16. package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
  17. package/appUtils/__tests__/allZappLocales.ts +79 -0
  18. package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
  19. package/appUtils/accessibilityManager/const.ts +18 -0
  20. package/appUtils/accessibilityManager/index.ts +46 -2
  21. package/appUtils/contextKeysManager/contextResolver.ts +66 -0
  22. package/appUtils/contextKeysManager/index.ts +2 -2
  23. package/appUtils/contextKeysManager/utils/index.ts +2 -1
  24. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +9 -6
  25. package/appUtils/focusManager/index.ios.ts +14 -4
  26. package/appUtils/focusManager/index.ts +108 -69
  27. package/appUtils/focusManager/treeDataStructure/Tree/index.js +23 -0
  28. package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
  29. package/appUtils/focusManager/utils/enums.ts +4 -0
  30. package/appUtils/focusManager/utils/index.ts +5 -0
  31. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  32. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
  33. package/appUtils/keyCodes/__tests__/keyCodes.test.js +1 -1
  34. package/appUtils/keyCodes/index.ts +11 -7
  35. package/appUtils/keyCodes/keys/keysByPlatform.ts +4 -0
  36. package/appUtils/keyInputHandler/KeyInputHandler.ts +68 -0
  37. package/appUtils/localizationsHelper.ts +31 -0
  38. package/appUtils/orientationHelper.ts +11 -13
  39. package/appUtils/platform/const.ts +14 -0
  40. package/appUtils/platform/platformUtils.ts +47 -4
  41. package/appUtils/playerManager/index.ts +1 -1
  42. package/appUtils/playerManager/player.ts +42 -18
  43. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
  44. package/appUtils/playerManager/playerNative.ts +18 -2
  45. package/appUtils/playerManager/usePlayerControllerSetup.tsx +3 -3
  46. package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +7 -1
  47. package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
  48. package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
  49. package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
  50. package/arrayUtils/index.ts +7 -2
  51. package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
  52. package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
  53. package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
  54. package/audioPlayerUtils/assets/index.ts +2 -0
  55. package/audioPlayerUtils/index.ts +242 -0
  56. package/cellUtils/index.ts +9 -5
  57. package/componentsUtils/__tests__/isFirstComponentGallery.test.ts +33 -0
  58. package/componentsUtils/__tests__/isFirstComponentScreenPicker.test.ts +33 -0
  59. package/componentsUtils/__tests__/isGallery.test.ts +33 -0
  60. package/componentsUtils/__tests__/isScreenPicker.test.ts +33 -0
  61. package/componentsUtils/index.ts +39 -0
  62. package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
  63. package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
  64. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
  65. package/configurationUtils/index.ts +63 -34
  66. package/dateUtils/index.ts +19 -0
  67. package/equalUtils/__tests__/isEqual/combination.test.ts +263 -0
  68. package/equalUtils/index.ts +49 -30
  69. package/focusManager/FocusManager.ts +14 -2
  70. package/focusManager/index.ts +23 -4
  71. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
  72. package/functionUtils/index.ts +46 -8
  73. package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
  74. package/idleUtils/index.ts +1 -1
  75. package/idleUtils/withTimeout.ts +15 -2
  76. package/index.d.ts +0 -12
  77. package/localizationUtils/index.ts +8 -13
  78. package/logger/const.ts +9 -67
  79. package/manifestUtils/__tests__/createConfig.test.js +16 -16
  80. package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
  81. package/manifestUtils/defaultManifestConfigurations/player.js +9 -3
  82. package/manifestUtils/fonts/index.js +5 -6
  83. package/manifestUtils/index.js +2 -0
  84. package/manifestUtils/keys.js +27 -2
  85. package/manifestUtils/sharedConfiguration/screenPicker/__tests__/utils.test.js +304 -0
  86. package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +874 -0
  87. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +130 -0
  88. package/modalState/__tests__/index.test.ts +51 -0
  89. package/modalState/index.ts +32 -2
  90. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +7 -6
  91. package/navigationUtils/__tests__/itemTypes.test.js +2 -2
  92. package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
  93. package/navigationUtils/index.ts +6 -4
  94. package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +23 -3
  95. package/numberUtils/index.ts +11 -5
  96. package/package.json +5 -10
  97. package/playerUtils/configurationGenerator.ts +9 -11
  98. package/playerUtils/index.ts +23 -0
  99. package/reactHooks/advertising/index.ts +5 -5
  100. package/reactHooks/app/useAppState.ts +2 -2
  101. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  102. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +6 -8
  103. package/reactHooks/autoscrolling/useTrackedView.ts +12 -1
  104. package/reactHooks/cell-click/__tests__/index.test.js +10 -2
  105. package/reactHooks/cell-click/index.ts +7 -1
  106. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
  107. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
  108. package/reactHooks/feed/useBatchLoading.ts +12 -14
  109. package/reactHooks/feed/useFeedLoader.tsx +18 -9
  110. package/reactHooks/feed/usePipesCacheReset.ts +5 -5
  111. package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
  112. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +39 -0
  113. package/reactHooks/layout/__tests__/index.test.tsx +10 -10
  114. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
  115. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
  116. package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
  117. package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
  118. package/reactHooks/navigation/__tests__/index.test.tsx +6 -21
  119. package/reactHooks/navigation/getTabBarHeight.ts +10 -0
  120. package/reactHooks/navigation/index.ts +34 -236
  121. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +19 -0
  122. package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
  123. package/reactHooks/navigation/useIsScreenActive.ts +17 -0
  124. package/reactHooks/navigation/useNavigationPluginData.ts +19 -0
  125. package/reactHooks/navigation/useNavigationType.ts +17 -0
  126. package/reactHooks/navigation/usePathname.ts +4 -0
  127. package/reactHooks/navigation/useProfilerLogging.ts +15 -0
  128. package/reactHooks/navigation/useRoute.ts +98 -0
  129. package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
  130. package/reactHooks/player/TVSeekControlller/ISeekController.ts +32 -0
  131. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +71 -0
  132. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +258 -0
  133. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +53 -0
  134. package/reactHooks/player/__tests__/{useAutoSeek.test.tsx → useAutoSeek._test.tsx} +2 -3
  135. package/reactHooks/player/const.ts +0 -4
  136. package/reactHooks/player/index.ts +0 -6
  137. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
  138. package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
  139. package/reactHooks/state/README.md +79 -0
  140. package/reactHooks/state/ZStoreProvider.tsx +71 -0
  141. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +66 -0
  142. package/reactHooks/state/index.ts +2 -0
  143. package/reactHooks/state/useRefWithInitialValue.ts +14 -0
  144. package/reactHooks/state/useStateFromSubscribe.ts +23 -0
  145. package/reactHooks/useListenEventBusEvent.ts +1 -1
  146. package/reactHooks/usePluginConfiguration.ts +1 -1
  147. package/reactHooks/useSubscriberFor.ts +4 -3
  148. package/reactUtils/__tests__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
  149. package/reactUtils/__tests__/reactUtils.test.js +1 -37
  150. package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
  151. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +13 -79
  152. package/reactUtils/createContext/__tests__/__snapshots__/{index.test.js.snap → index.test.ts.snap} +2 -2
  153. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
  154. package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
  155. package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
  156. package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
  157. package/reactUtils/createContext/__tests__/{index.test.js → index.test.ts} +2 -2
  158. package/reactUtils/createContext/__tests__/provider.test.js +4 -6
  159. package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
  160. package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
  161. package/reactUtils/createContext/helpers.ts +6 -1
  162. package/reactUtils/createContext/index.ts +11 -25
  163. package/reactUtils/index.ts +9 -11
  164. package/screenState/index.ts +24 -10
  165. package/services/js2native.ts +673 -0
  166. package/testUtils/index.tsx +0 -10
  167. package/theme/__mocks__/index.ts +4 -0
  168. package/theme/index.ts +1 -1
  169. package/time/BackgroundTimer.ts +55 -0
  170. package/time/Timer.ts +9 -6
  171. package/typeGuards/index.ts +3 -0
  172. package/types.ts +1 -0
  173. package/utils/README.md +30 -0
  174. package/utils/chunk.ts +5 -0
  175. package/utils/index.ts +16 -0
  176. package/utils/times.ts +5 -0
  177. package/zappFrameworkUtils/localStorageHelper.ts +38 -10
  178. package/zappFrameworkUtils/loginPluginUtils.ts +6 -2
  179. package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
  180. package/zappFrameworkUtils/types.ts +11 -0
  181. package/reactHooks/player/useAutoSeek.tsx +0 -87
  182. package/reactHooks/player/useSeek.tsx +0 -66
  183. package/reactHooks/player/useTapSeek.tsx +0 -58
  184. package/reactUtils/createContext/__tests__/index-no-react.test.js +0 -27
  185. /package/reactHooks/player/__tests__/{useTapSeek.test.ts → useTapSeek._test.ts} +0 -0
@@ -6,8 +6,10 @@ import {
6
6
  isSamsungPlatform,
7
7
  } from "@applicaster/zapp-react-dom-app/App/Loader/utils/platform";
8
8
 
9
- import { PLATFORM_KEYS, PLATFORMS } from "./const";
9
+ import { PLATFORM_KEYS, PLATFORMS, ZappPlatform } from "./const";
10
10
  import { createLogger, utilsLogger } from "../../logger";
11
+ import { getPlatform } from "../../reactUtils";
12
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
11
13
 
12
14
  const { log_debug } = createLogger({
13
15
  category: "General",
@@ -15,6 +17,20 @@ const { log_debug } = createLogger({
15
17
  parent: utilsLogger,
16
18
  });
17
19
 
20
+ export const getZappPlatform = (): ZappPlatform => {
21
+ const platform = appStore.get("appData")?.platform;
22
+
23
+ switch (platform) {
24
+ case "amazon_fire_tv":
25
+ return ZappPlatform.AmazonFireTV;
26
+ case ZappPlatform.Vizio:
27
+ return ZappPlatform.Vizio;
28
+ default:
29
+ // TODO: Use only ZappPlatform enum
30
+ return getPlatform() as ZappPlatform;
31
+ }
32
+ };
33
+
18
34
  export const getUserAgent = () => window?.navigator?.userAgent;
19
35
 
20
36
  /** VIZIO UTILS */
@@ -61,13 +77,22 @@ export class ClosedCaptioningManager {
61
77
 
62
78
  private constructor() {
63
79
  this.initialize();
80
+
81
+ window["vizioDebug"] = {
82
+ setCCEnabled: (isCCEnabled: boolean) => {
83
+ this.ccState$.next(isCCEnabled);
84
+ },
85
+ };
64
86
  }
65
87
 
66
88
  static getInstance(): ClosedCaptioningManager {
67
89
  if (!ClosedCaptioningManager.ccManagerInstance) {
90
+ log_debug("ClosedCaptioningManager: Creating new instance");
68
91
  ClosedCaptioningManager.ccManagerInstance = new ClosedCaptioningManager();
69
92
  }
70
93
 
94
+ log_debug("ClosedCaptioningManager: Returning existing instance");
95
+
71
96
  return ClosedCaptioningManager.ccManagerInstance;
72
97
  }
73
98
 
@@ -76,23 +101,41 @@ export class ClosedCaptioningManager {
76
101
  PLATFORM_KEYS.deviceClosedCaptioningEnabled
77
102
  );
78
103
 
79
- log_debug("Initializing closed captioning manager", { existingState });
80
-
81
104
  if (existingState !== null) {
105
+ log_debug(
106
+ `ClosedCaptioningManager: Setting initial state to ${existingState}`
107
+ );
108
+
82
109
  this.ccState$.next(existingState === "true" || existingState === true);
110
+ } else {
111
+ log_debug("ClosedCaptioningManager: No existing state found");
83
112
  }
84
113
 
85
114
  if (hasVizioAPIs()) {
115
+ log_debug("ClosedCaptioningManager: Setting closed captioning handler");
116
+
86
117
  window.VIZIO!.setClosedCaptionHandler((isCCEnabled) => {
87
- log_debug("CC state changed", { isCCEnabled });
118
+ log_debug(
119
+ "ClosedCaptioningManager: There was a change in the CC state",
120
+ { isCCEnabled }
121
+ );
88
122
 
89
123
  sessionStorage.setItem(
90
124
  PLATFORM_KEYS.deviceClosedCaptioningEnabled,
91
125
  isCCEnabled
92
126
  );
93
127
 
128
+ log_debug(
129
+ "ClosedCaptioningManager: Saved CC state to session storage",
130
+ { isCCEnabled }
131
+ );
132
+
94
133
  this.ccState$.next(isCCEnabled);
95
134
  });
135
+ } else {
136
+ log_debug(
137
+ "ClosedCaptioningManager: Vizio APIs are not available, skipping setup"
138
+ );
96
139
  }
97
140
  }
98
141
 
@@ -452,7 +452,7 @@ export class PlayerManager {
452
452
  if (this.castingReceiver) {
453
453
  this.castingReceiver.closeNativePlayer();
454
454
  } else {
455
- this.getActivePlayer().closeNativePlayer();
455
+ this.getActivePlayer()?.closeNativePlayer();
456
456
  this.invokeHandler("close");
457
457
  }
458
458
  }
@@ -5,6 +5,7 @@ import { createLogger, utilsLogger } from "../../logger";
5
5
  import { getBoolFromConfigValue } from "../../configurationUtils";
6
6
  import { PlayerRole, SharedPlayerCallBacksKeys } from "./conts";
7
7
  import { OverlaysObserver } from "./OverlayObserver/OverlaysObserver";
8
+ import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
8
9
 
9
10
  const logger = createLogger({
10
11
  category: "PlayerController",
@@ -49,6 +50,8 @@ export class Player {
49
50
  [key: string]: QuickBrickPlayer.SharedPlayerCallBacks;
50
51
  } = {};
51
52
 
53
+ public seekController?: TVSeekController | null;
54
+
52
55
  get entry(): ZappEntry | null {
53
56
  return this._entry;
54
57
  }
@@ -59,6 +62,8 @@ export class Player {
59
62
  }
60
63
 
61
64
  constructor(props) {
65
+ this.seekController = null;
66
+
62
67
  this.playerState = {
63
68
  // We want isLive null by default to be able not to show controls if we do not know type
64
69
  isLive: null,
@@ -91,6 +96,8 @@ export class Player {
91
96
  this.onVideoProgress = this.onVideoProgress.bind(this);
92
97
  this.onVideoLoad = this.onVideoLoad.bind(this);
93
98
  this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
99
+ this.onPlayerSeekComplete = this.onPlayerSeekComplete.bind(this);
100
+ this.onAdBreakEnd = this.onAdBreakEnd.bind(this);
94
101
  this.entryObservable = null;
95
102
  }
96
103
 
@@ -147,8 +154,8 @@ export class Player {
147
154
  this.isSeeking()
148
155
  ? this.playerState.seekPosition
149
156
  : this.playerState.adState
150
- ? this.playerState.adState.adPosition
151
- : this.playerState.contentPosition;
157
+ ? this.playerState.adState.adPosition
158
+ : this.playerState.contentPosition;
152
159
 
153
160
  getEntry = (): ZappEntry | null => this.entry;
154
161
 
@@ -194,17 +201,25 @@ export class Player {
194
201
  adPosition: currentTime,
195
202
  };
196
203
  } else {
197
- if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
198
- this.playerState.seekableDuration = seekableDuration;
199
- }
204
+ this.updateContentDuration({ seekableDuration, duration, currentTime });
205
+ }
206
+ };
200
207
 
201
- if (!isEmptyOrNil(duration) && duration >= 0) {
202
- this.playerState.contentDuration = duration;
203
- }
208
+ protected updateContentDuration = ({
209
+ seekableDuration = null,
210
+ duration = null,
211
+ currentTime = null,
212
+ }) => {
213
+ if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
214
+ this.playerState.seekableDuration = seekableDuration;
215
+ }
204
216
 
205
- if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
206
- this.playerState.contentPosition = currentTime;
207
- }
217
+ if (!isEmptyOrNil(duration) && duration >= 0) {
218
+ this.playerState.contentDuration = duration;
219
+ }
220
+
221
+ if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
222
+ this.playerState.contentPosition = currentTime;
208
223
  }
209
224
  };
210
225
 
@@ -321,7 +336,7 @@ export class Player {
321
336
  * You should use dispatchPlayerEvent at PlayerContainer or call playerManager.invokeHandler directly
322
337
  * This allows you to use one method for everything.
323
338
  */
324
- onSeekStart = (event: QuickBrickPlayer.OnSeekStartEvent) => {
339
+ onPlayerSeekStart = (event: QuickBrickPlayer.OnSeekStartEvent) => {
325
340
  const { isLive, seekableDuration, currentTime, duration, seekTime } = event;
326
341
 
327
342
  this.setPlaybackState({ currentTime, duration, seekableDuration, isLive });
@@ -336,10 +351,11 @@ export class Player {
336
351
  });
337
352
  };
338
353
 
339
- onSeekComplete = (event: QuickBrickPlayer.OnSeekCompleteEvent) => {
354
+ onPlayerSeekComplete = (event: QuickBrickPlayer.OnSeekCompleteEvent) => {
340
355
  const { isLive, seekableDuration, currentTime, duration } = event;
341
356
 
342
357
  this.setPlaybackState({ currentTime, duration, seekableDuration, isLive });
358
+
343
359
  this.playerState.seekPosition = null;
344
360
 
345
361
  if (!isLive) {
@@ -551,7 +567,7 @@ export class Player {
551
567
  });
552
568
  };
553
569
 
554
- onAdBreakEnd = (event) => {
570
+ onAdBreakEnd(event) {
555
571
  this.playerState.adState = null;
556
572
  this.playerState.isInAdBreak = false;
557
573
 
@@ -559,7 +575,7 @@ export class Player {
559
575
  callbackName: SharedPlayerCallBacksKeys.OnAdBreakEnd,
560
576
  event,
561
577
  });
562
- };
578
+ }
563
579
 
564
580
  onAdEnd = (event) => {
565
581
  this.playerState.adState = null;
@@ -620,14 +636,18 @@ export class Player {
620
636
  selectTrack = (_track) => {};
621
637
 
622
638
  protected notifyPlayHeadPositionUpdate = () => {
639
+ const currentTime = this.getPosition();
640
+
623
641
  const event = {
624
- currentTime: this.getPosition(),
642
+ currentTime,
625
643
  duration: this.getDuration(),
626
644
  item: this.getEntry(),
627
645
  seekableDuration: this.playerState.seekableDuration,
628
646
  isLive: this.isLive(),
629
647
  };
630
648
 
649
+ this.seekController?.onVideoProgress(currentTime);
650
+
631
651
  this.invokeListenersUpdate({
632
652
  callbackName: SharedPlayerCallBacksKeys.OnVideoProgress,
633
653
  event,
@@ -653,8 +673,8 @@ export class Player {
653
673
  };
654
674
 
655
675
  public getListener = (): QuickBrickPlayer.SharedPlayerCallBacks => ({
656
- onPlayerSeekStart: this.onSeekStart,
657
- onPlayerSeekComplete: this.onSeekComplete,
676
+ onPlayerSeekStart: this.onPlayerSeekStart,
677
+ onPlayerSeekComplete: this.onPlayerSeekComplete,
658
678
  onVideoEnd: this.onVideoEnd,
659
679
  onError: this.onError,
660
680
  onVideoError: this.onVideoError,
@@ -724,6 +744,10 @@ export class Player {
724
744
  return null;
725
745
  };
726
746
 
747
+ public hasResumePosition() {
748
+ return false;
749
+ }
750
+
727
751
  public getConfig = () => this.config;
728
752
 
729
753
  // TODO: replace with enum of supported orientations
@@ -12,7 +12,7 @@ import { useThrottle } from "../../../functionUtils";
12
12
  export const usePlayerCurrentTime = (
13
13
  listenerId: string,
14
14
  playerId?: string
15
- ): number | null => {
15
+ ): { currentTime: number | null; cancel: () => void } => {
16
16
  const [currentTime, setCurrentTime] = useState<number | null>(null);
17
17
  const player: Player = usePlayer(playerId);
18
18
 
@@ -20,15 +20,17 @@ export const usePlayerCurrentTime = (
20
20
  if (player) {
21
21
  const newCurrentTime = player.getContentPosition();
22
22
 
23
- setCurrentTime((prevTime) =>
24
- prevTime !== newCurrentTime ? newCurrentTime : prevTime
25
- );
23
+ setCurrentTime(newCurrentTime);
26
24
  }
27
25
  }, [player]);
28
26
 
27
+ // We update the current time every ~250ms since player can have rate set to 2x.
28
+ // We update more often than 2x a second to avoid noticiable 'pulsation' in update,
29
+ // since native level also updates the current time every ~250ms,
30
+ // and combined with assorted delays it creates interference pattern.
29
31
  const { throttledFunction: throttledUpdate, cancel } = useThrottle(
30
32
  updateCurrentTime,
31
- 1000
33
+ 1000 / 4
32
34
  );
33
35
 
34
36
  useEffect(() => {
@@ -36,7 +38,9 @@ export const usePlayerCurrentTime = (
36
38
  throttledUpdate();
37
39
 
38
40
  const listeners = {
39
- onVideoProgress: throttledUpdate,
41
+ onVideoProgress: () => {
42
+ setCurrentTime(player.getContentPosition());
43
+ },
40
44
  };
41
45
 
42
46
  const id = `${listenerId}_player_current_time`;
@@ -53,5 +57,5 @@ export const usePlayerCurrentTime = (
53
57
  }
54
58
  }, [player, listenerId, throttledUpdate, cancel]);
55
59
 
56
- return currentTime;
60
+ return { currentTime, cancel };
57
61
  };
@@ -54,11 +54,15 @@ export class PlayerNative extends Player {
54
54
  });
55
55
  }
56
56
 
57
+ public hasResumePosition() {
58
+ return true;
59
+ }
60
+
57
61
  getContentPosition = () => {
58
62
  return this.playerState.contentPosition;
59
63
  };
60
64
 
61
- currentPlayerComponent = (): ReactComponent<PlayerProps> =>
65
+ currentPlayerComponent = (): ReactComponent<PlayerRefProps> =>
62
66
  this.playerComponent?.current;
63
67
 
64
68
  getState = () => this.playerState;
@@ -221,8 +225,11 @@ export class PlayerNative extends Player {
221
225
  };
222
226
 
223
227
  onVideoLoad = (event) => {
224
- if (this.startPosition) {
228
+ this.updateContentDuration(event);
229
+
230
+ if (this.startPosition && !this.playerState.isInAdBreak) {
225
231
  super.onVideoLoad({ ...event, ignoreContinueWatching: true });
232
+
226
233
  this.seekTo(this.startPosition);
227
234
  this.startPosition = null;
228
235
  } else {
@@ -248,6 +255,15 @@ export class PlayerNative extends Player {
248
255
  }
249
256
  };
250
257
 
258
+ onAdBreakEnd = (event) => {
259
+ super.onAdBreakEnd(event);
260
+
261
+ if (this.startPosition) {
262
+ this.seekTo(this.startPosition);
263
+ this.startPosition = null;
264
+ }
265
+ };
266
+
251
267
  // This function is called when native player used directly without wrapped component,
252
268
  // Since event from native comes with wrapped in NativeEvent property
253
269
  getNativeViewListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
@@ -39,7 +39,7 @@ export const usePlayerControllerSetup = ({
39
39
  ? { supports_native_controls: true, ...config }
40
40
  : config;
41
41
 
42
- const newPlayerController = new ControllerType({
42
+ const combinedProps = {
43
43
  player: ref,
44
44
  listener: props as any,
45
45
  config: newConfig,
@@ -47,9 +47,9 @@ export const usePlayerControllerSetup = ({
47
47
  entry,
48
48
  playerPluginId: pluginId,
49
49
  playerRole: PlayerRole.Primary,
50
- });
50
+ };
51
51
 
52
- setPlayerController(newPlayerController);
52
+ setPlayerController(new ControllerType(combinedProps));
53
53
  }, [playerId, entry?.id]);
54
54
 
55
55
  React.useEffect(() => {
@@ -3,12 +3,18 @@ import { OverlaysObserver } from "./OverlayObserver/OverlaysObserver";
3
3
  import { useFeedLoader } from "../../reactHooks";
4
4
  import { usePlayer } from "./usePlayer";
5
5
 
6
+ const getLiveUrl = (url: string) => {
7
+ // Use the replace method with a regular expression to replace all instances of subClass with livePage
8
+ return url.replace(new RegExp("subClass", "g"), "livePage");
9
+ };
10
+
6
11
  export const useTitleSummaryOverlay = (
7
- feedUrl,
8
12
  entry,
9
13
  isLive = false,
10
14
  interval: number
11
15
  ) => {
16
+ // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
17
+ const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
12
18
  const player = usePlayer();
13
19
  const [title, setTitle] = useState<string | number>(entry?.title || "");
14
20
  const [summary, setSummary] = useState<string | number>(entry?.summary || "");
@@ -1,7 +1,7 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`shiftArray shifts the n first items from the head to the tail of the array with a positive offset 1`] = `
4
- Array [
4
+ [
5
5
  "bar",
6
6
  "baz",
7
7
  42,
@@ -10,7 +10,7 @@ Array [
10
10
  `;
11
11
 
12
12
  exports[`shiftArray shifts the n first items from the head to the tail of the array with a positive offset 2`] = `
13
- Array [
13
+ [
14
14
  "baz",
15
15
  42,
16
16
  "foo",
@@ -19,7 +19,7 @@ Array [
19
19
  `;
20
20
 
21
21
  exports[`shiftArray shifts the n last items from the tail to the head of the array with a negative offset 1`] = `
22
- Array [
22
+ [
23
23
  42,
24
24
  "foo",
25
25
  "bar",
@@ -28,7 +28,7 @@ Array [
28
28
  `;
29
29
 
30
30
  exports[`shiftArray shifts the n last items from the tail to the head of the array with a negative offset 2`] = `
31
- Array [
31
+ [
32
32
  "baz",
33
33
  42,
34
34
  "foo",
@@ -0,0 +1,63 @@
1
+ import { isEmptyArray } from "..";
2
+
3
+ describe("isEmptyArray", () => {
4
+ it("non-empty array is not empty", () => {
5
+ const value = [1, 2, 3];
6
+
7
+ expect(isEmptyArray(value)).toBe(false);
8
+ });
9
+
10
+ it("empty array is empty", () => {
11
+ const value = [];
12
+
13
+ expect(isEmptyArray(value)).toBe(true);
14
+ });
15
+
16
+ it("number is not array", () => {
17
+ const value = 123;
18
+
19
+ expect(isEmptyArray(value)).toBe(false);
20
+ });
21
+
22
+ it("string is not array", () => {
23
+ const value = "vfnjdk";
24
+
25
+ expect(isEmptyArray(value)).toBe(false);
26
+ });
27
+
28
+ it("empty string is not array", () => {
29
+ const value = "";
30
+
31
+ expect(isEmptyArray(value)).toBe(false);
32
+ });
33
+
34
+ it("NaN is not array", () => {
35
+ const value = NaN;
36
+
37
+ expect(isEmptyArray(value)).toBe(false);
38
+ });
39
+
40
+ it("object is not array", () => {
41
+ const value = { test: 1 };
42
+
43
+ expect(isEmptyArray(value)).toBe(false);
44
+ });
45
+
46
+ it("empty object is not array", () => {
47
+ const value = {};
48
+
49
+ expect(isEmptyArray(value)).toBe(false);
50
+ });
51
+
52
+ it("undefined is not array", () => {
53
+ const value = undefined;
54
+
55
+ expect(isEmptyArray(value)).toBe(false);
56
+ });
57
+
58
+ it("null is not array", () => {
59
+ const value = null;
60
+
61
+ expect(isEmptyArray(value)).toBe(false);
62
+ });
63
+ });
@@ -32,7 +32,7 @@ describe("isFilledArray", () => {
32
32
  });
33
33
 
34
34
  it("NaN is not array", () => {
35
- const value = "";
35
+ const value = NaN;
36
36
 
37
37
  expect(isFilledArray(value)).toBe(false);
38
38
  });
@@ -99,12 +99,17 @@ export const makeListOf = (value: unknown, size: number): number[] => {
99
99
 
100
100
  /** Checks if a value is a non-empty array */
101
101
  export function isFilledArray(value: unknown): boolean {
102
- return R.is(Array, value) && R.length(value) > 0;
102
+ return Array.isArray(value) && value.length > 0;
103
+ }
104
+
105
+ /** Checks if a value is a empty array */
106
+ export function isEmptyArray(value: unknown): boolean {
107
+ return Array.isArray(value) && value.length === 0;
103
108
  }
104
109
 
105
110
  // get random item from the list
106
111
  export const sample = (xs: unknown[]): unknown => {
107
- invariant(R.is(Array, xs), `input value is not a array: ${xs}`);
112
+ invariant(Array.isArray(xs), `input value is not an array: ${xs}`);
108
113
  invariant(isFilledArray(xs), `input array is empty: ${xs}`);
109
114
 
110
115
  const index = Math.floor(Math.random() * xs.length);
@@ -0,0 +1,144 @@
1
+ import { getArtworkImage } from "..";
2
+ import { DEFAULT_IMAGE } from "../assets";
3
+
4
+ describe("getArtworkImage", () => {
5
+ const entryWithImage = {
6
+ media_group: [
7
+ {
8
+ type: "image",
9
+ media_item: [
10
+ { key: "artwork_key", src: "image_from_entry" },
11
+ { key: "other_key", src: "other_image" },
12
+ ],
13
+ },
14
+ ],
15
+ extensions: {
16
+ artwork_key: "artwork_key",
17
+ },
18
+ };
19
+
20
+ const entryWithoutImage = {
21
+ media_group: [
22
+ {
23
+ type: "image",
24
+ media_item: [{ key: "other_key", src: "other_image" }],
25
+ },
26
+ ],
27
+ extensions: {
28
+ artwork_key: "artwork_key",
29
+ },
30
+ };
31
+
32
+ const pluginConfigWithImage = {
33
+ artwork_key: "plugin_artwork_key",
34
+ };
35
+
36
+ it("returns image from entry extensions key", () => {
37
+ const result = getArtworkImage({
38
+ key: "artwork_key",
39
+ entry: entryWithImage,
40
+ plugin_configuration: {},
41
+ });
42
+
43
+ expect(result).toBe("image_from_entry");
44
+ });
45
+
46
+ it("returns image from plugin configuration key if not in entry", () => {
47
+ const entryNoMatch = {
48
+ ...entryWithoutImage,
49
+ extensions: { artwork_key: "not_found_key" },
50
+ };
51
+
52
+ const pluginConfig = {
53
+ artwork_key: "plugin_artwork_key",
54
+ };
55
+
56
+ const entryWithPluginImage = {
57
+ ...entryNoMatch,
58
+ media_group: [
59
+ {
60
+ type: "image",
61
+ media_item: [{ key: "plugin_artwork_key", src: "image_from_plugin" }],
62
+ },
63
+ ],
64
+ };
65
+
66
+ const result = getArtworkImage({
67
+ key: "artwork_key",
68
+ entry: entryWithPluginImage,
69
+ plugin_configuration: pluginConfig,
70
+ });
71
+
72
+ expect(result).toBe("image_from_plugin");
73
+ });
74
+
75
+ it("returns DEFAULT_IMAGE if neither entry nor plugin configuration has image", () => {
76
+ const entryNoImage = {
77
+ media_group: [
78
+ {
79
+ type: "image",
80
+ media_item: [{ key: "other_key", src: "other_image" }],
81
+ },
82
+ ],
83
+ extensions: { artwork_key: "not_found_key" },
84
+ };
85
+
86
+ const pluginConfig = { artwork_key: "not_found_key" };
87
+
88
+ const result = getArtworkImage({
89
+ key: "artwork_key",
90
+ entry: entryNoImage,
91
+ plugin_configuration: pluginConfig,
92
+ });
93
+
94
+ expect(result).toBe(DEFAULT_IMAGE);
95
+ });
96
+
97
+ it("handles undefined key gracefully", () => {
98
+ const result = getArtworkImage({
99
+ key: undefined,
100
+ entry: entryWithImage,
101
+ plugin_configuration: pluginConfigWithImage,
102
+ });
103
+
104
+ expect(result).toBe(DEFAULT_IMAGE);
105
+ });
106
+
107
+ it("handles missing entry or plugin_configuration gracefully", () => {
108
+ const result = getArtworkImage({
109
+ key: "artwork_key",
110
+ entry: undefined,
111
+ plugin_configuration: undefined,
112
+ });
113
+
114
+ expect(result).toBe(DEFAULT_IMAGE);
115
+ });
116
+
117
+ it("handles empty media_group", () => {
118
+ const entryEmptyMedia = {
119
+ media_group: [],
120
+ extensions: { artwork_key: "artwork_key" },
121
+ };
122
+
123
+ const result = getArtworkImage({
124
+ key: "artwork_key",
125
+ entry: entryEmptyMedia,
126
+ plugin_configuration: pluginConfigWithImage,
127
+ });
128
+
129
+ expect(result).toBe(DEFAULT_IMAGE);
130
+ });
131
+
132
+ it("handles missing extensions/config keys", () => {
133
+ const entryNoExt = { media_group: [], extensions: {} };
134
+ const pluginConfig = {};
135
+
136
+ const result = getArtworkImage({
137
+ key: "artwork_key",
138
+ entry: entryNoExt,
139
+ plugin_configuration: pluginConfig,
140
+ });
141
+
142
+ expect(result).toBe(DEFAULT_IMAGE);
143
+ });
144
+ });