@applicaster/zapp-react-native-utils 13.0.0-alpha.5428512514 → 13.0.0-alpha.5771418585

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 (55) hide show
  1. package/README.md +0 -10
  2. package/actionsExecutor/ActionExecutor.ts +16 -9
  3. package/actionsExecutor/ActionExecutorContext.tsx +47 -7
  4. package/appUtils/HooksManager/Hook.ts +2 -2
  5. package/appUtils/HooksManager/types.ts +5 -2
  6. package/appUtils/accessibilityManager/index.ts +42 -1
  7. package/appUtils/contextKeysManager/contextResolver.ts +6 -2
  8. package/appUtils/contextKeysManager/index.ts +2 -2
  9. package/appUtils/contextKeysManager/utils/index.ts +2 -1
  10. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
  11. package/appUtils/focusManager/index.ts +11 -3
  12. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  13. package/appUtils/platform/const.ts +14 -0
  14. package/appUtils/platform/platformUtils.ts +47 -4
  15. package/appUtils/playerManager/player.ts +10 -9
  16. package/appUtils/playerManager/playerNative.ts +5 -5
  17. package/equalUtils/index.ts +10 -3
  18. package/focusManager/FocusManager.ts +11 -1
  19. package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
  20. package/idleUtils/index.ts +1 -1
  21. package/idleUtils/withTimeout.ts +15 -2
  22. package/logger/const.ts +9 -67
  23. package/manifestUtils/__tests__/createConfig.test.js +0 -2
  24. package/navigationUtils/index.ts +3 -3
  25. package/package.json +2 -2
  26. package/reactHooks/cell-click/__tests__/index.test.js +5 -0
  27. package/reactHooks/cell-click/index.ts +7 -1
  28. package/reactHooks/feed/useFeedLoader.tsx +1 -1
  29. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
  30. package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
  31. package/reactHooks/navigation/index.ts +7 -19
  32. package/reactHooks/navigation/useGetTabBarHeight.ts +1 -3
  33. package/reactHooks/navigation/useProfilerLogging.ts +15 -0
  34. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +40 -13
  35. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +10 -3
  36. package/reactHooks/state/useRefWithInitialValue.ts +14 -0
  37. package/reactHooks/state/useStateFromSubscribe.ts +23 -0
  38. package/reactHooks/usePluginConfiguration.ts +1 -1
  39. package/reactHooks/useSubscriberFor.ts +4 -3
  40. package/reactUtils/__tests__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
  41. package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
  42. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +6 -72
  43. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
  44. package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
  45. package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
  46. package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
  47. package/reactUtils/createContext/__tests__/provider.test.js +4 -6
  48. package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
  49. package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
  50. package/screenState/index.ts +24 -10
  51. package/testUtils/index.tsx +0 -10
  52. package/theme/index.ts +1 -1
  53. package/time/Timer.ts +5 -2
  54. package/types.ts +1 -0
  55. package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
package/README.md CHANGED
@@ -376,16 +376,6 @@ useEffect(() => {
376
376
  }, []);
377
377
  ```
378
378
 
379
- ### testUtils
380
-
381
- `@applicaster/zapp-react-native-utils/testUtils`
382
-
383
- - `testIDEquals: (testId: any) => boolean | { width: number, height: number }` - Function to be used in tandem with enzyme findWhere
384
-
385
- ```javascript
386
- wrapper.findWhere(testIDEquals("text"));
387
- ```
388
-
389
379
  ### appUtils - contextKeysManager
390
380
 
391
381
  `@applicaster/zapp-react-native-utils/appUtils/contextKeyManager`
@@ -17,9 +17,10 @@ class ActionExecutor {
17
17
 
18
18
  handleAction = async (
19
19
  action: ActionType,
20
- entry: ZappEntry = null
20
+ context?: Record<string, any>
21
21
  ): Promise<ActionResult> => {
22
22
  const handler = this.registeredActions[action.type];
23
+ const entry = context?.entry as ZappEntry;
23
24
 
24
25
  if (!handler) {
25
26
  log_error(
@@ -38,11 +39,11 @@ class ActionExecutor {
38
39
  );
39
40
 
40
41
  try {
41
- return handler(action, entry);
42
+ return handler(action, context);
42
43
  } catch (error) {
43
44
  log_error(
44
45
  `handleAction: error handling action type: ${action.type}, message: ${error.message}`,
45
- { action, entry, error }
46
+ { action, context, error }
46
47
  );
47
48
 
48
49
  throw error;
@@ -51,11 +52,11 @@ class ActionExecutor {
51
52
 
52
53
  handleActions = async (
53
54
  actionTypes: ActionType[],
54
- entry: ZappEntry = null
55
+ context?: Record<string, any>
55
56
  ): Promise<ActionResult> => {
56
57
  for (const action of actionTypes) {
57
58
  // TODO: Handle action result error
58
- const result = await this.handleAction(action, entry);
59
+ const result = await this.handleAction(action, context);
59
60
 
60
61
  if (result === ActionResult.Cancel) {
61
62
  log_info(`handleActions: action: ${action.type} canceled`);
@@ -67,7 +68,10 @@ class ActionExecutor {
67
68
  return ActionResult.Success;
68
69
  };
69
70
 
70
- handleEntryActions = async (entry: ZappEntry): Promise<ActionResult> => {
71
+ handleEntryActions = async (
72
+ entry: ZappEntry,
73
+ context?: Record<string, any>
74
+ ): Promise<ActionResult> => {
71
75
  const tapActions = entry.extensions?.tap_actions?.actions;
72
76
 
73
77
  if (!tapActions) {
@@ -77,7 +81,7 @@ class ActionExecutor {
77
81
  if (!Array.isArray(tapActions)) {
78
82
  log_error(
79
83
  `handleEntryActions: tapActions is not an array for id: ${entry.id} title: ${entry.title}`,
80
- { entry }
84
+ { context, entry }
81
85
  );
82
86
 
83
87
  return;
@@ -87,7 +91,7 @@ class ActionExecutor {
87
91
  return;
88
92
  }
89
93
 
90
- await this.handleActions(tapActions, entry);
94
+ await this.handleActions(tapActions, { ...context, entry });
91
95
  };
92
96
 
93
97
  unregisterAction = (actionType: string) => {
@@ -96,7 +100,10 @@ class ActionExecutor {
96
100
 
97
101
  registerAction = (
98
102
  type: string,
99
- handler: (action: ActionType, entry?: ZappEntry) => Promise<ActionResult>
103
+ handler: (
104
+ action: ActionType,
105
+ context?: Record<string, any>
106
+ ) => Promise<ActionResult>
100
107
  ) => {
101
108
  if (this.registeredActions[type]) {
102
109
  log_error(`registerAction: action type: ${type} already registered`);
@@ -13,6 +13,8 @@ import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
13
13
  import { createLogger } from "../logger";
14
14
  import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
15
15
  import { ACTIVE_LAYOUT_ID_STORAGE_KEY } from "@applicaster/quick-brick-core/App/remoteContextReloader/consts";
16
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
17
+ import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
16
18
  import {
17
19
  EntryResolver,
18
20
  resolveObjectValues,
@@ -26,15 +28,24 @@ const { log_error, log_info } = createLogger({
26
28
  type ActionExecutorContextType = {
27
29
  registerAction: (
28
30
  type: string,
29
- handler: (action: ActionType, entry: ZappEntry) => Promise<ActionResult>
31
+ handler: (
32
+ action: ActionType,
33
+ context?: Record<string, any>
34
+ ) => Promise<ActionResult>
30
35
  ) => void;
31
36
  unregisterAction: (type: string) => void;
32
- handleAction: (action: ActionType, entry: ZappEntry) => Promise<ActionResult>;
37
+ handleAction: (
38
+ action: ActionType,
39
+ context?: Record<string, any>
40
+ ) => Promise<ActionResult>;
33
41
  handleActions: (
34
42
  actions: ActionType[],
35
- entry: ZappEntry
43
+ context: Record<string, any>
44
+ ) => Promise<ActionResult>;
45
+ handleEntryActions: (
46
+ entry: ZappEntry,
47
+ context?: Record<string, any>
36
48
  ) => Promise<ActionResult>;
37
- handleEntryActions: (entry: ZappEntry) => Promise<ActionResult>;
38
49
  };
39
50
  type Props = {
40
51
  children: React.ReactNode;
@@ -49,6 +60,31 @@ const prepareDefaultActions = (actionExecutor) => {
49
60
  return ActionResult.Success;
50
61
  });
51
62
 
63
+ actionExecutor.registerAction(
64
+ "refreshComponent",
65
+ async (_action, context) => {
66
+ const dispatch = appStore.getDispatch();
67
+
68
+ log_info("handleAction: refreshComponent event");
69
+
70
+ const dataSource = context?.component?.data?.source;
71
+
72
+ // TODO: Later we can pass component ids to do refresh instead of current one
73
+ // TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
74
+ // TODO: handle focused item removal
75
+ dispatch(
76
+ loadPipesData(dataSource, {
77
+ silentRefresh: false,
78
+ clearCache: true,
79
+ callback: (_data) => {},
80
+ riverId: context?.screenData?.id,
81
+ })
82
+ );
83
+
84
+ return ActionResult.Success;
85
+ }
86
+ );
87
+
52
88
  actionExecutor.registerAction("switchLayout", async (action) => {
53
89
  log_info("handleAction: switchLayout event");
54
90
 
@@ -84,10 +120,11 @@ const prepareDefaultActions = (actionExecutor) => {
84
120
  return confirmationPromise;
85
121
  });
86
122
 
87
- actionExecutor.registerAction("sendCloudEvent", async (action, entry) => {
123
+ actionExecutor.registerAction("sendCloudEvent", async (action, context) => {
88
124
  try {
89
125
  const options = action.options;
90
126
 
127
+ const entry = context?.entry || {};
91
128
  const entryResolver = new EntryResolver(entry);
92
129
 
93
130
  const data =
@@ -128,9 +165,12 @@ const prepareDefaultActions = (actionExecutor) => {
128
165
 
129
166
  actionExecutor.registerAction(
130
167
  "localStorageToggleFlag",
131
- async (action: ActionType, entry?: ZappEntry): Promise<ActionResult> => {
168
+ async (
169
+ action: ActionType,
170
+ context?: Record<string, any>
171
+ ): Promise<ActionResult> => {
132
172
  const keyNamespace = action.options?.key;
133
- const tag = entry?.extensions?.tag;
173
+ const tag = context?.entry?.extensions?.tag;
134
174
 
135
175
  if (keyNamespace && tag) {
136
176
  const multiSelectProvider =
@@ -16,7 +16,7 @@ const BASE_HOOK_PROPERTIES = ["screen_id", "identifier", "weight"];
16
16
  * @returns {number} index
17
17
  */
18
18
  const hookState: (
19
- arg0: typeof HOOKS_EVENTS[keyof typeof HOOKS_EVENTS]
19
+ arg0: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS]
20
20
  ) => number = R.indexOf(R.__, R.values(HOOKS_EVENTS));
21
21
 
22
22
  /**
@@ -62,7 +62,7 @@ export class Hook implements HookInterface {
62
62
  * @param {Array<Any>} args variadic arguments to invoke the handler with
63
63
  */
64
64
  setStateAndNotify(
65
- event: typeof HOOKS_EVENTS[keyof typeof HOOKS_EVENTS],
65
+ event: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS],
66
66
  ...args
67
67
  ) {
68
68
  this.state = hookState(event);
@@ -4,6 +4,8 @@ import { HOOKS_EVENTS } from "./constants";
4
4
  export interface HookConfiguration {}
5
5
 
6
6
  /* existing Hook Plugn props: sample */
7
+
8
+ /*
7
9
  interface _HookModulePlugin {
8
10
  Component:
9
11
  | React.FC<HookPluginProps>
@@ -23,6 +25,7 @@ interface _HookModulePlugin {
23
25
  presentUI: (payload?: HookPluginProps["payload"]) => void
24
26
  ) => any;
25
27
  }
28
+ */
26
29
 
27
30
  //* Derived from usage */
28
31
  export interface HookModule {
@@ -100,7 +103,7 @@ export interface Hook<T extends HookModule = HookModule> {
100
103
  identifier: string;
101
104
  configuration: T["configuration"];
102
105
  setStateAndNotify(
103
- event: typeof HOOKS_EVENTS[keyof typeof HOOKS_EVENTS],
106
+ event: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS],
104
107
  opts: {
105
108
  error?: typeof Error;
106
109
  route?: string;
@@ -109,7 +112,7 @@ export interface Hook<T extends HookModule = HookModule> {
109
112
  }
110
113
  );
111
114
  general?: {
112
- transition_type: typeof HOOKS_EVENTS[keyof typeof HOOKS_EVENTS];
115
+ transition_type: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS];
113
116
  allow_screen_plugin_presentation: boolean;
114
117
  is_flow_blocker: boolean;
115
118
  };
@@ -6,6 +6,9 @@ import { AccessibilityRole } from "react-native";
6
6
 
7
7
  export class AccessibilityManager {
8
8
  private static _instance: AccessibilityManager | null = null;
9
+ private headingTimeout: NodeJS.Timeout | null = null;
10
+ private WORDS_PER_MINUTE = 160;
11
+ private MINIMUM_PAUSE = 500;
9
12
  private state$ = new BehaviorSubject<AccessibilityState>({
10
13
  screenReaderEnabled: false,
11
14
  reduceMotionEnabled: false,
@@ -20,6 +23,7 @@ export class AccessibilityManager {
20
23
 
21
24
  private ttsManager = TTSManager.getInstance();
22
25
  private localizations: { [key: string]: string } = {};
26
+ private headingQueue: string[] = [];
23
27
 
24
28
  private constructor() {}
25
29
 
@@ -52,6 +56,28 @@ export class AccessibilityManager {
52
56
  return this.state$.asObservable();
53
57
  }
54
58
 
59
+ /** Calculates the reading time for a given text
60
+ * This method is a bit of a hack because we don't have a callback, or promise from VIZIO API
61
+ * @param text - The text to calculate the reading time for
62
+ * @returns The reading time in milliseconds
63
+ */
64
+ private calculateReadingTime(text: string): number {
65
+ const words = text.trim().split(/\s+/).length;
66
+
67
+ return Math.max(
68
+ this.MINIMUM_PAUSE,
69
+ (words / this.WORDS_PER_MINUTE) * 60 * 1000
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Adds a heading to the queue, headings will be read before the next text
75
+ * Each heading will be read once and removed from the queue
76
+ */
77
+ public addHeading(heading: string) {
78
+ this.headingQueue.push(heading);
79
+ }
80
+
55
81
  /**
56
82
  * text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
57
83
  * keyOfLocalizedText is the key to the localized text
@@ -85,7 +111,22 @@ export class AccessibilityManager {
85
111
  textToRead = localizedMessage;
86
112
  }
87
113
 
88
- this.ttsManager && this.ttsManager.readText(textToRead);
114
+ if (this.headingQueue.length > 0) {
115
+ const heading = this.headingQueue.shift()!;
116
+ this.ttsManager?.readText(heading);
117
+
118
+ if (this.headingTimeout) {
119
+ clearTimeout(this.headingTimeout);
120
+ }
121
+
122
+ const pauseTime = this.calculateReadingTime(heading);
123
+
124
+ this.headingTimeout = setTimeout(() => {
125
+ this.ttsManager?.readText(textToRead);
126
+ }, pauseTime);
127
+ } else {
128
+ this.ttsManager?.readText(textToRead);
129
+ }
89
130
 
90
131
  this.announcements$.next({
91
132
  message: textToRead,
@@ -42,12 +42,16 @@ export const resolveObjectValues = async (
42
42
 
43
43
  const resolvedEntries = await Promise.all(
44
44
  entries.map(async ([key, value]) => {
45
- const regex = /@{([^/]+)\/([^}]*)}/;
46
-
47
45
  if (typeof value !== "string") {
48
46
  return [key, value];
49
47
  }
50
48
 
49
+ if (!value.startsWith("@")) {
50
+ return [key, value];
51
+ }
52
+
53
+ const regex = /@{([^/]+)\/([^}]*)}/;
54
+
51
55
  const match = (value as string).match(regex);
52
56
 
53
57
  if (match) {
@@ -16,12 +16,12 @@ import {
16
16
 
17
17
  export { StorageLevel };
18
18
 
19
- type KeyNameObj = {
19
+ export type KeyNameObj = {
20
20
  namespace: string;
21
21
  key: string;
22
22
  };
23
23
 
24
- type KeyName = string | KeyNameObj;
24
+ export type KeyName = string | KeyNameObj;
25
25
 
26
26
  type ParsedKeyAndNamespace = {
27
27
  namespace?: string;
@@ -1,10 +1,11 @@
1
1
  import * as R from "ramda";
2
2
  import { DEFAULT_NAMESPACE } from "../consts";
3
+ import { KeyName, KeyNameObj } from "../index";
3
4
 
4
5
  const lastDotRegex = /\.([^.]+)$/;
5
6
  const splitByLastDot = R.compose(R.init, R.split(lastDotRegex));
6
7
 
7
- export function getNamespaceAndKey(namespacedKey) {
8
+ export function getNamespaceAndKey(namespacedKey: KeyName): KeyNameObj {
8
9
  if (typeof namespacedKey !== "string") return namespacedKey;
9
10
 
10
11
  const namespacedKeyProps = namespacedKey.includes(".")
@@ -28,6 +28,7 @@ exports[`focusManager should be defined 1`] = `
28
28
  "moveFocus": [Function],
29
29
  "on": [Function],
30
30
  "press": [Function],
31
+ "pressIn": [Function],
31
32
  "pressOut": [Function],
32
33
  "recoverFocus": [Function],
33
34
  "register": [Function],
@@ -13,6 +13,7 @@ import {
13
13
  import { subscriber } from "../../functionUtils";
14
14
  import { coreLogger } from "../../logger";
15
15
  import { ACTION } from "./utils/enums";
16
+
16
17
  const logger = coreLogger.addSubsystem("focusManager");
17
18
 
18
19
  const isFocusEnabled = (focusableItem): boolean => {
@@ -325,8 +326,6 @@ export const focusManager = (function () {
325
326
  },
326
327
  });
327
328
 
328
- currentFocusNode = focusCandidate;
329
-
330
329
  focusableItem && setFocus(focusCandidate.id, null);
331
330
 
332
331
  return { success: true };
@@ -346,6 +345,14 @@ export const focusManager = (function () {
346
345
  });
347
346
  }
348
347
 
348
+ function pressIn() {
349
+ const currentFocusable = getCurrentFocus();
350
+
351
+ if (currentFocusable) {
352
+ currentFocusable.pressIn(null);
353
+ }
354
+ }
355
+
349
356
  /**
350
357
  * calls the `press` method on the current focus component
351
358
  * @public
@@ -477,7 +484,7 @@ export const focusManager = (function () {
477
484
  function isCurrentFocusOnTheTopScreen() {
478
485
  const routes = R.pathOr([], ["root", "children"], focusableTree);
479
486
 
480
- if (routes.length <= 1) {
487
+ if (currentFocusNode && routes.length <= 1) {
481
488
  // there is only one or zero routes
482
489
  return true;
483
490
  }
@@ -553,6 +560,7 @@ export const focusManager = (function () {
553
560
  invokeHandler,
554
561
  removeHandler,
555
562
  longPress,
563
+ pressIn,
556
564
  pressOut,
557
565
  enableFocus,
558
566
  setInitialFocus,
@@ -1,8 +1,8 @@
1
1
  import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
- import { find, last, startsWith, pathOr } from "ramda";
2
+ import { find, last, pathOr, startsWith } from "ramda";
3
3
  import {
4
- QUICK_BRICK_NAVBAR,
5
4
  QUICK_BRICK_CONTENT,
5
+ QUICK_BRICK_NAVBAR,
6
6
  } from "@applicaster/quick-brick-core/const";
7
7
 
8
8
  // run check each 300 ms
@@ -66,7 +66,8 @@ export const getContentNode = (focusableTree) => {
66
66
  const content = find(
67
67
  (child) =>
68
68
  startsWith(QUICK_BRICK_CONTENT, child.id) ||
69
- startsWith("player", child.id),
69
+ startsWith("player", child.id) ||
70
+ startsWith("/hooks", child?.parent?.id), // For startup hooks case
70
71
  getActiveScreenChildren(focusableTree)
71
72
  );
72
73
 
@@ -23,3 +23,17 @@ export const PLATFORMS = {
23
23
  export const PLATFORM_KEYS = {
24
24
  deviceClosedCaptioningEnabled: "device_closed_captioning_enabled",
25
25
  };
26
+
27
+ export enum ZappPlatform {
28
+ Android = "android",
29
+ AndroidTV = "android_tv",
30
+ AmazonFireTV = "amazon",
31
+ iOS = "ios",
32
+ tvOS = "tvos",
33
+ Roku = "roku",
34
+ Tizen = "tizen",
35
+ Web = "web",
36
+ WebOS = "webos",
37
+ Vizio = "vizio",
38
+ Unknown = "unknown",
39
+ }
@@ -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
 
@@ -98,6 +98,7 @@ export class Player {
98
98
  this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
99
99
  this.onPlayerSeekComplete = this.onPlayerSeekComplete.bind(this);
100
100
 
101
+ this.isPaused = this.isPaused.bind(this);
101
102
  this.entryObservable = null;
102
103
  }
103
104
 
@@ -154,8 +155,8 @@ export class Player {
154
155
  this.isSeeking()
155
156
  ? this.playerState.seekPosition
156
157
  : this.playerState.adState
157
- ? this.playerState.adState.adPosition
158
- : this.playerState.contentPosition;
158
+ ? this.playerState.adState.adPosition
159
+ : this.playerState.contentPosition;
159
160
 
160
161
  getEntry = (): ZappEntry | null => this.entry;
161
162
 
@@ -253,9 +254,9 @@ export class Player {
253
254
  return this.playerState.seekPosition !== null;
254
255
  };
255
256
 
256
- isPaused = () => {
257
+ isPaused() {
257
258
  return !!this.playerState.isPaused;
258
- };
259
+ }
259
260
 
260
261
  isPlaying = () => {
261
262
  return !!this.playerState.isPaused === false;
@@ -290,11 +291,11 @@ export class Player {
290
291
  };
291
292
 
292
293
  // Methods to call actions on the player
293
- seekTo = (_position: number) => {};
294
+ seekTo = async (_position: number) => {};
294
295
 
295
- forward = (_deltaTime: number) => {};
296
+ forward = async (_deltaTime: number) => {};
296
297
 
297
- rewind = (_deltaTime: number) => {};
298
+ rewind = async (_deltaTime: number) => {};
298
299
 
299
300
  /**
300
301
  * Determine whether content is live using three checks
@@ -701,8 +702,8 @@ export class Player {
701
702
  onPlayerAttached: this.onPlayerAttached,
702
703
  });
703
704
 
704
- play = () => {};
705
- pause = () => {};
705
+ play = async () => {};
706
+ pause = async () => {};
706
707
  disableBufferAnimation = (): boolean => true;
707
708
 
708
709
  setPlaybackRate = (_rate) => {};
@@ -95,7 +95,7 @@ export class PlayerNative extends Player {
95
95
  };
96
96
 
97
97
  // Methods to call actions on the player
98
- seekTo = (position: number) => {
98
+ seekTo = async (position: number) => {
99
99
  if (!this.playerState.seekableDuration) {
100
100
  log_warning("seekTo: Can not seek, video is not seekable");
101
101
 
@@ -123,7 +123,7 @@ export class PlayerNative extends Player {
123
123
  this.logState(PlayerModuleFuncNames.seekTo, { position });
124
124
  };
125
125
 
126
- forward = (deltaTime: number) => {
126
+ forward = async (deltaTime: number) => {
127
127
  if (isEmptyOrNil(this.playerState.contentPosition)) {
128
128
  return;
129
129
  }
@@ -139,7 +139,7 @@ export class PlayerNative extends Player {
139
139
  this.logState(PlayerModuleFuncNames.forward, { deltaTime });
140
140
  };
141
141
 
142
- rewind = (deltaTime: number) => {
142
+ rewind = async (deltaTime: number) => {
143
143
  if (isEmptyOrNil(this.playerState.contentPosition)) {
144
144
  return;
145
145
  }
@@ -155,13 +155,13 @@ export class PlayerNative extends Player {
155
155
  this.logState(PlayerModuleFuncNames.rewind, { deltaTime });
156
156
  };
157
157
 
158
- play = () => {
158
+ play = async () => {
159
159
  if (!this.invokeNativeFunction(PlayerModuleFuncNames.play)) {
160
160
  this.currentPlayerComponent()?.[PlayerModuleFuncNames.play]?.();
161
161
  }
162
162
  };
163
163
 
164
- pause = () => {
164
+ pause = async () => {
165
165
  if (!this.invokeNativeFunction(PlayerModuleFuncNames.pause)) {
166
166
  this.currentPlayerComponent()?.[PlayerModuleFuncNames.pause]?.();
167
167
  }
@@ -49,9 +49,16 @@ export const isEqual =
49
49
  }
50
50
 
51
51
  if (compareRemainingKeysBySameValue) {
52
- const undeclaredKeys = R.difference(
53
- [...byValue, ...byReference, byComparator.keys],
54
- Object.keys(prev)
52
+ const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]);
53
+
54
+ const declaredKeys = new Set([
55
+ ...byValue,
56
+ ...byReference,
57
+ ...byComparator.keys,
58
+ ]);
59
+
60
+ const undeclaredKeys = Array.from(allKeys).filter(
61
+ (key) => !declaredKeys.has(key)
55
62
  );
56
63
 
57
64
  const isRestKeysEqualAllBySameValue = undeclaredKeys.every((key) =>