@applicaster/zapp-react-native-utils 12.0.0-alpha.4043183114 → 12.0.0-alpha.4081044014

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 (63) hide show
  1. package/analyticsUtils/AnalyticPlayerListener.ts +141 -0
  2. package/analyticsUtils/PlayerAnalyticsManager.ts +90 -0
  3. package/analyticsUtils/{analyticsTracker.ts → playerAnalyticsTracker.ts} +19 -10
  4. package/appUtils/HooksManager/{Hook.ts → Hook.js} +11 -17
  5. package/appUtils/HooksManager/{constants.ts → constants.js} +1 -1
  6. package/appUtils/HooksManager/{index.ts → index.js} +109 -98
  7. package/appUtils/RiverFocusManager/index.js +12 -17
  8. package/appUtils/accessibilityManager/hooks.ts +16 -7
  9. package/appUtils/accessibilityManager/index.ts +33 -28
  10. package/appUtils/focusManager/treeDataStructure/Tree/index.js +3 -2
  11. package/appUtils/focusManager/treeDataStructure/Utils/index.js +3 -1
  12. package/appUtils/{index.ts → index.js} +1 -1
  13. package/appUtils/keyCodes/{index.ts → index.js} +0 -2
  14. package/appUtils/keyCodes/{keys/keyEventMapper.ts → keys.android.js} +19 -0
  15. package/appUtils/keyCodes/keys.ios.js +46 -0
  16. package/appUtils/platform/platformUtils.ts +49 -2
  17. package/appUtils/playerManager/index.ts +9 -27
  18. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +57 -0
  19. package/appUtils/playerManager/playerNative.ts +1 -1
  20. package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -1
  21. package/appUtils/playerManager/usePlayerState.tsx +19 -34
  22. package/arrayUtils/index.ts +11 -0
  23. package/cellUtils/index.ts +38 -5
  24. package/domUtils/{cookieManager.ts → cookieManager.js} +2 -1
  25. package/focusManager/FocusManager.ts +20 -5
  26. package/focusManager/__tests__/FocusManager.test.ts +0 -1
  27. package/functionUtils/index.ts +49 -15
  28. package/index.d.ts +15 -0
  29. package/manifestUtils/defaultManifestConfigurations/player.js +121 -0
  30. package/manifestUtils/keys.js +2 -1
  31. package/modalState/index.ts +5 -1
  32. package/navigationUtils/index.ts +6 -15
  33. package/numberUtils/__tests__/toNumber.test.ts +2 -2
  34. package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +66 -0
  35. package/numberUtils/__tests__/toPositiveNumberWithDefaultZero.test.ts +66 -0
  36. package/numberUtils/index.ts +20 -0
  37. package/package.json +2 -2
  38. package/playerUtils/configurationGenerator.ts +120 -0
  39. package/playerUtils/imaScriptDom.ts +59 -0
  40. package/reactHooks/cell-click/index.ts +17 -3
  41. package/reactHooks/debugging/__tests__/index.test.js +81 -11
  42. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +69 -6
  43. package/reactHooks/feed/useBatchLoading.ts +52 -28
  44. package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +134 -0
  45. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -0
  46. package/reactHooks/navigation/index.ts +5 -8
  47. package/reactHooks/player/useAutoSeek.tsx +9 -7
  48. package/reactHooks/resolvers/useCellResolver.ts +5 -1
  49. package/reactHooks/useConfiguration.ts +2 -2
  50. package/reactUtils/index.ts +15 -2
  51. package/testUtils/index.tsx +42 -9
  52. package/appUtils/HooksManager/types.ts +0 -93
  53. package/appUtils/keyCodes/keys/index.ts +0 -3
  54. package/appUtils/keyCodes/keys/keys.android.ts +0 -18
  55. package/appUtils/keyCodes/keys/keys.ios.ts +0 -25
  56. package/appUtils/keyCodes/keys/keys.ts +0 -15
  57. /package/appUtils/HooksManager/{logger.ts → logger.js} +0 -0
  58. /package/appUtils/{directionHelper.ts → directionHelper.js} +0 -0
  59. /package/appUtils/keyCodes/{androidTVKeys.ts → androidTVKeys.js} +0 -0
  60. /package/appUtils/keyCodes/{keys/keys.web.ts → keys.web.js} +0 -0
  61. /package/appUtils/keyCodes/{keys/keysByPlatform.ts → keysByPlatform.js} +0 -0
  62. /package/appUtils/{localizationsHelper.ts → localizationsHelper.js} +0 -0
  63. /package/domUtils/{logger.ts → logger.js} +0 -0
@@ -26,7 +26,7 @@ export const riverFocusManager = (function () {
26
26
  /**
27
27
  * Retrieve screen focus data by id
28
28
  *
29
- * @param {{screenFocusableGroupId: string}} screenFocusableGroupId Unique Id of the screen from layout.json
29
+ * @param {String} screenFocusableGroupId Id that was used to save screen data.
30
30
  *
31
31
  */
32
32
  function screenFocusableData({ screenFocusableGroupId }) {
@@ -36,9 +36,8 @@ export const riverFocusManager = (function () {
36
36
  /**
37
37
  * Clear screen data for specific screen
38
38
  *
39
- * @param {{ screenId: string, isInsideContainer: boolean }}
40
- * screenId Unique Id of the screen from layout.json
41
- * isInsideContainer If this screen a screen picker child
39
+ * @param {String} screenId Unique Id of the screen from layout.json
40
+ * @param {boolean} isInsideContainer If this screen a screen picker child
42
41
  *
43
42
  */
44
43
  function clearScreenData({ screenId, isInsideContainer }) {
@@ -61,13 +60,9 @@ export const riverFocusManager = (function () {
61
60
  /**
62
61
  * Focus on selected items
63
62
  *
64
- * @param {{
65
- * screenFocusableGroupId: string,
66
- * screenFocusedOnItem: () => void,
67
- * previousAction: QuickBrickNavigationActionType,
68
- * isDeepLink: boolean }} object contains data for focus
69
- * - `screenFocusedOnItem` focusable item to focus
70
- * - `previousActionhistory` navigator history object
63
+ * @param {String} screenFocusableGroupId Top group id that was saved as screenn
64
+ * @param {String} screenFocusedOnItem focusable item to focus
65
+ * @param {object} history navigator history object
71
66
  *
72
67
  */
73
68
  function focusOnSelectedItem({
@@ -120,15 +115,15 @@ export const riverFocusManager = (function () {
120
115
 
121
116
  /**
122
117
  * Create unique key that will be used for save focused group data inside specific screen
123
- * @param {{ screenId: string, isInsideContainer: boolean }}
124
- * screenId Unique Id of the screen from layout.json
125
- * isInsideContainer If this screen a screen picker child
118
+ *
119
+ * @param {String} screenId Unique Id of the screen from layout.json
120
+ * @param {boolean} isInsideContainer If this screen a screen picker child
126
121
  *
127
122
  */
128
123
  function screenFocusableGroupId({ screenId, isInsideContainer }) {
129
- return `RiverFocusableGroup-${screenId}${
130
- R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
131
- }`;
124
+ return R.isNil(isInsideContainer)
125
+ ? `RiverFocusableGroup-${screenId}`
126
+ : `RiverFocusableGroup-${screenId}-isInsideContainer`;
132
127
  }
133
128
 
134
129
  return {
@@ -1,18 +1,27 @@
1
1
  import { useEffect, useMemo } from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
2
  import { AccessibilityManager } from "./index";
4
3
 
5
- export const useAccessibilityManager = () => {
6
- const { remoteConfigurations } = usePickFromState(["remoteConfigurations"]);
7
- const { localizations } = remoteConfigurations;
8
-
4
+ /**
5
+ * This hook is used to instantiate the accessibility manager and update localizations used by the class.
6
+ * If the plugin config is not provided or it does not contain any localizations, the accessibility manager will not be updated.
7
+ * We use the plugin configuration because it offers a flattened structure where we can easily access the accessibility labels and hints
8
+ * i.e. pluginConfiguration = {
9
+ * accessibility_close_label: "Close",
10
+ * accessibility_close_hint: "Press here to close",
11
+ * }
12
+ */
13
+ export const useAccessibilityManager = (
14
+ pluginConfiguration: Record<string, any> = {}
15
+ ) => {
9
16
  const accessibilityManager = useMemo(() => {
10
17
  return AccessibilityManager.getInstance();
11
18
  }, []);
12
19
 
13
20
  useEffect(() => {
14
- accessibilityManager.updateLocalizations(localizations);
15
- }, [localizations]);
21
+ if (pluginConfiguration) {
22
+ accessibilityManager.updateLocalizations(pluginConfiguration);
23
+ }
24
+ }, [pluginConfiguration, accessibilityManager]);
16
25
 
17
26
  useEffect(() => {
18
27
  const subscription = accessibilityManager.getStateAsObservable().subscribe({
@@ -1,21 +1,8 @@
1
1
  import { BehaviorSubject } from "rxjs";
2
2
  import { accessibilityManagerLogger as logger } from "./logger";
3
3
  import { TTSManager } from "../platform/platformUtils";
4
- import { getLocalizations } from "../../localizationUtils";
5
4
  import { BUTTON_ACCESSIBILITY_KEYS } from "./const";
6
-
7
- export interface AccessibilityState {
8
- screenReaderEnabled: boolean;
9
- reduceMotionEnabled: boolean;
10
- boldTextEnabled: boolean;
11
- }
12
-
13
- export interface AccessibilityProps {
14
- accessibilityLabel?: string;
15
- accessibilityHint?: string;
16
- "aria-label"?: string;
17
- "aria-description"?: string;
18
- }
5
+ import { AccessibilityRole } from "react-native";
19
6
 
20
7
  export class AccessibilityManager {
21
8
  private static _instance: AccessibilityManager | null = null;
@@ -32,13 +19,9 @@ export class AccessibilityManager {
32
19
  } | null>(null);
33
20
 
34
21
  private ttsManager = TTSManager.getInstance();
35
- private localizations: { [key: string]: string };
22
+ private localizations: { [key: string]: string } = {};
36
23
 
37
- private constructor(localizations?: { [key: string]: string }) {
38
- if (localizations) {
39
- this.localizations = getLocalizations({ localizations });
40
- }
41
- }
24
+ private constructor() {}
42
25
 
43
26
  public static getInstance(): AccessibilityManager {
44
27
  if (!AccessibilityManager._instance) {
@@ -48,8 +31,17 @@ export class AccessibilityManager {
48
31
  return AccessibilityManager._instance;
49
32
  }
50
33
 
34
+ /**
35
+ * The method now accepts any object with localizations using a flattened structure
36
+ *
37
+ * i.e. { accessibility_close_label: "Close", accessibility_close_hint: "Press here to close" }
38
+ *
39
+ * No longer accepts:
40
+ *
41
+ * i.e. localizations: [{ en: { accessibility_close_label: "Close", accessibility_close_hint: "Press here to close" } }]
42
+ */
51
43
  public updateLocalizations(localizations: { [key: string]: string }) {
52
- this.localizations = getLocalizations({ localizations });
44
+ this.localizations = localizations;
53
45
  }
54
46
 
55
47
  public getState(): AccessibilityState {
@@ -71,7 +63,7 @@ export class AccessibilityManager {
71
63
  text: string;
72
64
  keyOfLocalizedText?: string;
73
65
  }) {
74
- logger.debug("Reading text", { text, keyOfLocalizedText });
66
+ let textToRead = text;
75
67
 
76
68
  if (keyOfLocalizedText) {
77
69
  if (!this.localizations) {
@@ -90,16 +82,14 @@ export class AccessibilityManager {
90
82
  return;
91
83
  }
92
84
 
93
- text = localizedMessage;
85
+ textToRead = localizedMessage;
94
86
  }
95
87
 
96
- if (this.ttsManager) {
97
- this.ttsManager.readText(text);
98
- }
88
+ this.ttsManager && this.ttsManager.readText(textToRead);
99
89
 
100
90
  this.announcements$.next({
101
- message: text,
102
- localizedMessage: keyOfLocalizedText ? text : undefined,
91
+ message: textToRead,
92
+ localizedMessage: keyOfLocalizedText ? textToRead : undefined,
103
93
  timestamp: Date.now(),
104
94
  });
105
95
  }
@@ -113,6 +103,8 @@ export class AccessibilityManager {
113
103
  accessibilityHint: `Press button to perform action on ${buttonName}`,
114
104
  "aria-label": buttonName,
115
105
  "aria-description": `Press button to perform action on ${buttonName}`,
106
+ accessibilityRole: "button" as AccessibilityRole,
107
+ "aria-role": "button",
116
108
  };
117
109
  }
118
110
 
@@ -130,6 +122,19 @@ export class AccessibilityManager {
130
122
  accessibilityHint: hint,
131
123
  "aria-label": label,
132
124
  "aria-description": hint,
125
+ accessibilityRole: "button" as AccessibilityRole,
126
+ "aria-role": "button",
127
+ };
128
+ }
129
+
130
+ public getInputAccessibilityProps(inputName: string): AccessibilityProps {
131
+ return {
132
+ accessibilityLabel: inputName,
133
+ accessibilityHint: `Enter text into ${inputName}`,
134
+ "aria-label": inputName,
135
+ "aria-description": `Enter text into ${inputName}`,
136
+ accessibilityRole: "textbox" as AccessibilityRole,
137
+ "aria-role": "textbox",
133
138
  };
134
139
  }
135
140
 
@@ -59,7 +59,7 @@ export class Tree {
59
59
  /**
60
60
  * Update node with provided data from newNode
61
61
  * @private
62
- * @param {Object} currentNode to update
62
+ * @param {Object} node to update
63
63
  * @param {Object} newNode node to take data from
64
64
  */
65
65
  updateNode(currentNode, newNode) {
@@ -175,6 +175,7 @@ export class Tree {
175
175
  * @private
176
176
  * @param {Object} node to check
177
177
  * @returns True if node has defined groupID
178
+ * @returns False if node has NOT defined groupID
178
179
  */
179
180
  hasGroupID(node) {
180
181
  return node.component && node.component.props.groupId;
@@ -190,7 +191,7 @@ export class Tree {
190
191
  const findChildNode = this.findInTree(node.component.props.groupId);
191
192
 
192
193
  if (findChildNode) {
193
- this.root.children.pop();
194
+ this.root.children.pop(findChildNode);
194
195
  node.children.push(findChildNode);
195
196
  }
196
197
  }
@@ -191,6 +191,7 @@ const toDistance = ({ target, direction, comparisonRect }) => {
191
191
  * Get target in shortest distance in specified direction
192
192
  * @private
193
193
  * @param {Object} direction of the navigation, which led to this focus change
194
+ * @param {Object} excludedNode node which should be excluded from target group
194
195
  * @param {Array} targetNodes array of input target nodes
195
196
  * @param {Object} comparisonRect rectangle to compare sides in direction.
196
197
  * @returns {Object} target in direction.
@@ -299,7 +300,8 @@ export function findFocusableNode(direction, currentFocusNode) {
299
300
  * @public
300
301
  * @param {Object} direction of the navigation, which led to this focus change
301
302
  * @param {Object} component - object to check props.prioritiseFocusOn
302
- * @returns {Boolean} TRUE if object has priority in direction. FALSE if object has NO priority in direction.
303
+ * @returns {Boolean} TRUE if object has priority in direction.
304
+ * @returns {Boolean} FALSE if object has NO priority in direction.
303
305
  */
304
306
  export function hasPriorityInDirection(direction, component) {
305
307
  const { prioritiseFocusOn = 0 } = component.props;
@@ -9,7 +9,7 @@ export { KEYS, ARROW_KEYS } from "./keyCodes/keys";
9
9
 
10
10
  export { playerManager } from "./playerManager";
11
11
 
12
- export { AccessibilityManager } from "./accessibilityManager";
12
+ export { accessibilityManager } from "./accessibilityManager";
13
13
 
14
14
  export function deprecationMessage(fn, replacement) {
15
15
  if (__DEV__ && process.env.NODE_ENV !== "test") {
@@ -6,8 +6,6 @@ import * as R from "ramda";
6
6
  import { directionHelper } from "../directionHelper";
7
7
  import { KEYS, ARROW_KEYS } from "./keys";
8
8
 
9
- export { KEYS, ARROW_KEYS, keyEventMapper } from "./keys";
10
-
11
9
  /**
12
10
  * @typedef KeyCode
13
11
  * @property {Object} value of the Key
@@ -1,3 +1,22 @@
1
+ export const KEYS = {
2
+ Enter: { code: "Enter", keyCode: 13 },
3
+ EnterKeyboard: { code: "EnterKeyboard", keyCode: 66 },
4
+ Back: { code: "Back", keyCode: 8 },
5
+ Space: { code: "Space", keyCode: 32 },
6
+ m: { code: "m", keyCode: 77 },
7
+ ArrowLeft: { code: "ArrowLeft", keyCode: 37 },
8
+ ArrowUp: { code: "ArrowUp", keyCode: 38 },
9
+ ArrowRight: { code: "ArrowRight", keyCode: 39 },
10
+ ArrowDown: { code: "ArrowDown", keyCode: 40 },
11
+ };
12
+
13
+ export const ARROW_KEYS = [
14
+ KEYS.ArrowUp,
15
+ KEYS.ArrowLeft,
16
+ KEYS.ArrowDown,
17
+ KEYS.ArrowRight,
18
+ ];
19
+
1
20
  export function keyEventMapper(keyCode) {
2
21
  switch (keyCode) {
3
22
  case 19: // ArrowUp
@@ -0,0 +1,46 @@
1
+ /* global tizen */
2
+ const isTizen = typeof tizen !== "undefined";
3
+
4
+ export const KEYS = {
5
+ Enter: { code: "Enter", keyCode: 13 },
6
+ Space: { code: "Space", keyCode: 32 },
7
+ m: { code: "m", keyCode: 77 },
8
+ ArrowLeft: { code: "ArrowLeft", keyCode: 37 },
9
+ ArrowUp: { code: "ArrowUp", keyCode: 38 },
10
+ ArrowRight: { code: "ArrowRight", keyCode: 39 },
11
+ ArrowDown: { code: "ArrowDown", keyCode: 40 },
12
+ Back: isTizen
13
+ ? { code: "Back", keyCode: 10009 }
14
+ : { code: "Backspace", keyCode: 8 },
15
+ Play: isTizen
16
+ ? { code: "MediaPlay", keyCode: 10252 }
17
+ : { code: "PlayPause", keyCode: 179 },
18
+ Pause: isTizen
19
+ ? { code: "MediaPause", keyCode: 19 }
20
+ : { code: "Pause", keyCode: 179 },
21
+ Forward: isTizen
22
+ ? { code: "MediaFastForward", keyCode: 417 }
23
+ : { code: "Forward", keyCode: 176 },
24
+ Rewind: isTizen
25
+ ? { code: "MediaRewind", keyCode: 412 }
26
+ : { code: "Rewind", keyCode: 177 },
27
+ VolumeUp: isTizen
28
+ ? { code: "VolumeUp", keyCode: 447 }
29
+ : { code: "VolumeUp", keyCode: 175 },
30
+ VolumeDown: isTizen
31
+ ? { code: "VolumeDown", keyCode: 448 }
32
+ : { code: "VolumeDown", keyCode: 174 },
33
+ ToggleMute: isTizen
34
+ ? { code: "VolumeMute", keyCode: 449 }
35
+ : { code: "VolumeMute", keyCode: 173 },
36
+ Stop: isTizen
37
+ ? { code: "MediaStop", keyCode: 413 }
38
+ : { code: "Q", keyCode: 81 },
39
+ };
40
+
41
+ export const ARROW_KEYS = [
42
+ KEYS.ArrowUp,
43
+ KEYS.ArrowLeft,
44
+ KEYS.ArrowDown,
45
+ KEYS.ArrowRight,
46
+ ];
@@ -1,18 +1,35 @@
1
1
  import { BehaviorSubject } from "rxjs";
2
2
 
3
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";
4
8
 
5
9
  import { PLATFORM_KEYS, PLATFORMS } from "./const";
6
10
  import { createLogger, utilsLogger } from "../../logger";
7
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
+
8
26
  const { log_debug } = createLogger({
9
27
  category: "General",
10
28
  subsystem: "PlatformUtils",
11
29
  parent: utilsLogger,
12
30
  });
13
31
 
14
- export const getUserAgent = () =>
15
- window?.navigator?.userAgent || global?.navigator?.userAgent;
32
+ export const getUserAgent = () => window?.navigator?.userAgent;
16
33
 
17
34
  /** VIZIO UTILS */
18
35
 
@@ -25,6 +42,8 @@ export const getUserAgent = () =>
25
42
  export const isVizioPlatform = () => {
26
43
  const userAgent = getUserAgent();
27
44
 
45
+ if (!userAgent) return false;
46
+
28
47
  const isVizioAgent =
29
48
  userAgent.includes(PLATFORMS.vizio) ||
30
49
  userAgent.includes(PLATFORMS.smartcast) ||
@@ -164,6 +183,34 @@ export class TTSManager {
164
183
  }
165
184
 
166
185
  readText(text: string) {
186
+ if (isSamsungPlatform() && window.speechSynthesis) {
187
+ const utterance = new SpeechSynthesisUtterance(text);
188
+ window.speechSynthesis.speak(utterance);
189
+ }
190
+
191
+ if (isLgPlatform() && window.webOS?.service) {
192
+ try {
193
+ window.webOS.service.request("luna://com.webos.service.tts", {
194
+ method: "speak",
195
+ onFailure(error: any) {
196
+ log_debug("There was a failure setting up webOS TTS service", {
197
+ error,
198
+ });
199
+ },
200
+ onSuccess(response: any) {
201
+ log_debug("webOS TTS service is configured successfully", {
202
+ response,
203
+ });
204
+ },
205
+ parameters: {
206
+ text,
207
+ },
208
+ });
209
+ } catch (error) {
210
+ log_debug("webOS TTS service error", { error });
211
+ }
212
+ }
213
+
167
214
  if (!window.VIZIO?.Chromevox) return;
168
215
 
169
216
  window.VIZIO.Chromevox.play(text);
@@ -3,7 +3,6 @@ import * as R from "ramda";
3
3
  import { subscriber } from "../../functionUtils";
4
4
  import { utilsLogger, createLogger } from "../../logger";
5
5
  import { Player } from "./player";
6
- import { AnalyticsTracker } from "../../analyticsUtils/analyticsTracker";
7
6
  import {
8
7
  hasTizenAPIs,
9
8
  subscribeToDeviceNetworkChanges,
@@ -37,7 +36,10 @@ export interface PlayerLifecycleListener {
37
36
  }
38
37
 
39
38
  type PlayersMap = {
40
- [id: string]: { player: Player; isInViewport: boolean };
39
+ [id: string]: {
40
+ player: Player;
41
+ isInViewport: boolean;
42
+ };
41
43
  };
42
44
 
43
45
  export class PlayerManager {
@@ -46,7 +48,6 @@ export class PlayerManager {
46
48
  private playersMap: PlayersMap = {};
47
49
  private manager = subscriber();
48
50
  private castingReceiver: Player;
49
- private analyticsTracker: AnalyticsTracker;
50
51
  private lifecycleListeners: PlayerLifecycleListener[] = [];
51
52
 
52
53
  public static getInstance() {
@@ -57,7 +58,7 @@ export class PlayerManager {
57
58
  return PlayerManager.instance;
58
59
  }
59
60
 
60
- public getActivePlayerId = (): String | null => {
61
+ public getActivePlayerId = (): string | null => {
61
62
  return this.activePlayerId;
62
63
  };
63
64
 
@@ -102,7 +103,10 @@ export class PlayerManager {
102
103
  }
103
104
 
104
105
  private addPlayer = (id: string, player: Player) => {
105
- this.playersMap[id] = { player: player, isInViewport: false };
106
+ this.playersMap[id] = {
107
+ player: player,
108
+ isInViewport: false,
109
+ };
106
110
  };
107
111
 
108
112
  public isPlayerRegistered = (id: string) => !!this.playersMap[id];
@@ -251,28 +255,6 @@ export class PlayerManager {
251
255
  this.castingReceiver = null;
252
256
  }
253
257
 
254
- /**
255
- * Handles analytic events, generates a new analytics tracker and transmits events
256
- * Passes entry data, and playerState to the tracker for additional context
257
- * The end result is an event formatted and passed to AnalyticsBridge.postEvent
258
- * When we get the synthetic event we need to pull the data in the e object
259
- * e contains the return value from native
260
- */
261
- public handleAnalyticEvent(event, data = {}, entry: ZappEntry) {
262
- const playerState = this.getActivePlayer()?.getState();
263
-
264
- if (!this.analyticsTracker?.handleAnalyticEvent) {
265
- this.analyticsTracker = new AnalyticsTracker(entry);
266
- }
267
-
268
- this.analyticsTracker?.handleAnalyticEvent?.(
269
- event,
270
- data,
271
- entry,
272
- playerState
273
- );
274
- }
275
-
276
258
  /**
277
259
  * Method retrieves the instantiated player instance controller
278
260
  * Instance controller exposes methods like getContentDuration, getContentPosision
@@ -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
+ };
@@ -61,7 +61,7 @@ export class PlayerNative extends Player {
61
61
  currentPlayerComponent = (): ReactComponent<PlayerProps> =>
62
62
  this.playerComponent?.current;
63
63
 
64
- getState = () => this.currentPlayerComponent()?.["getState"]?.() ?? {};
64
+ getState = () => this.playerState;
65
65
 
66
66
  // TODO: Try to add module interface
67
67
  invokeNativeFunction = (funcName: string, ...params: any[]): boolean => {
@@ -58,7 +58,7 @@ export const usePlayerControllerSetup = ({
58
58
  }
59
59
 
60
60
  if (!playerManager) {
61
- return null;
61
+ return;
62
62
  }
63
63
 
64
64
  if (playerManager.isPlayerRegistered(playerId)) {
@@ -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
  };
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable unused-imports/no-unused-vars */
2
2
  import * as R from "ramda";
3
+ import { invariant } from "@applicaster/zapp-react-native-utils/errorUtils";
3
4
 
4
5
  /**
5
6
  * shifts an array by the given offset. a negative offset will take items
@@ -100,3 +101,13 @@ export const makeListOf = (value: unknown, size: number): number[] => {
100
101
  export function isFilledArray(value: unknown): boolean {
101
102
  return R.is(Array, value) && R.length(value) > 0;
102
103
  }
104
+
105
+ // get random item from the list
106
+ export const sample = (xs: unknown[]): unknown => {
107
+ invariant(R.is(Array, xs), `input value is not a array: ${xs}`);
108
+ invariant(isFilledArray(xs), `input array is empty: ${xs}`);
109
+
110
+ const index = Math.floor(Math.random() * xs.length);
111
+
112
+ return xs[index];
113
+ };