@applicaster/zapp-react-native-utils 13.0.0-alpha.1632352572 → 13.0.0-alpha.1673005505

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 (123) hide show
  1. package/README.md +0 -10
  2. package/actionsExecutor/ActionExecutor.ts +16 -9
  3. package/actionsExecutor/ActionExecutorContext.tsx +74 -9
  4. package/alertUtils/alert.ts +148 -0
  5. package/alertUtils/index.ts +1 -148
  6. package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
  7. package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
  8. package/analyticsUtils/helpers/hooks.ts +1 -1
  9. package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
  10. package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +10 -8
  11. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +693 -0
  12. package/appUtils/HooksManager/__tests__/hook.test.js +196 -184
  13. package/appUtils/HooksManager/__tests__/hooksManager.test.js +96 -99
  14. package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
  15. package/appUtils/HooksManager/{index.js → index.ts} +121 -112
  16. package/appUtils/HooksManager/types.ts +119 -0
  17. package/appUtils/RiverFocusManager/index.js +17 -12
  18. package/appUtils/accessibilityManager/index.ts +42 -1
  19. package/appUtils/contextKeysManager/contextResolver.ts +77 -0
  20. package/appUtils/contextKeysManager/index.ts +2 -2
  21. package/appUtils/contextKeysManager/utils/index.ts +2 -1
  22. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -6
  23. package/appUtils/focusManager/index.ts +100 -65
  24. package/appUtils/focusManager/treeDataStructure/Tree/index.js +25 -3
  25. package/appUtils/focusManager/treeDataStructure/Utils/index.js +1 -3
  26. package/appUtils/focusManager/utils/enums.ts +4 -0
  27. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  28. package/appUtils/{index.js → index.ts} +1 -1
  29. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
  30. package/appUtils/keyCodes/__tests__/keyCodes.test.js +4 -4
  31. package/appUtils/keyCodes/{index.js → index.ts} +2 -0
  32. package/appUtils/keyCodes/keys/index.ts +3 -0
  33. package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
  34. package/appUtils/keyCodes/keys/keys.android.ts +18 -0
  35. package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
  36. package/appUtils/keyCodes/keys/keys.ts +15 -0
  37. package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +3 -3
  38. package/appUtils/keyCodes/keysByPlatform.ts +8 -0
  39. package/appUtils/orientationHelper.ts +1 -1
  40. package/appUtils/platform/const.ts +14 -0
  41. package/appUtils/platform/platformUtils.ts +47 -18
  42. package/appUtils/playerManager/player.ts +5 -4
  43. package/appUtils/playerManager/playerNative.ts +1 -1
  44. package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
  45. package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
  46. package/equalUtils/index.ts +10 -3
  47. package/focusManager/FocusManager.ts +11 -1
  48. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
  49. package/functionUtils/index.ts +15 -11
  50. package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
  51. package/idleUtils/index.ts +1 -1
  52. package/idleUtils/withTimeout.ts +15 -2
  53. package/logger/const.ts +9 -67
  54. package/manifestUtils/__tests__/createConfig.test.js +16 -16
  55. package/modalState/index.ts +1 -5
  56. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +6 -6
  57. package/navigationUtils/__tests__/itemTypes.test.js +2 -2
  58. package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
  59. package/navigationUtils/index.ts +17 -8
  60. package/package.json +3 -2
  61. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  62. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -1
  63. package/reactHooks/cell-click/__tests__/index.test.js +10 -2
  64. package/reactHooks/cell-click/index.ts +7 -1
  65. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
  66. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
  67. package/reactHooks/feed/useFeedLoader.tsx +1 -1
  68. package/reactHooks/feed/usePipesCacheReset.ts +5 -5
  69. package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
  70. package/reactHooks/layout/__tests__/index.test.tsx +10 -10
  71. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
  72. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
  73. package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
  74. package/reactHooks/navigation/__tests__/index.test.tsx +0 -4
  75. package/reactHooks/navigation/index.ts +35 -236
  76. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +12 -0
  77. package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
  78. package/reactHooks/navigation/useGetTabBarHeight.ts +10 -0
  79. package/reactHooks/navigation/useIsScreenActive.ts +17 -0
  80. package/reactHooks/navigation/useNavigationPluginData.ts +15 -0
  81. package/reactHooks/navigation/useNavigationType.ts +15 -0
  82. package/reactHooks/navigation/usePathname.ts +4 -0
  83. package/reactHooks/navigation/useProfilerLogging.ts +15 -0
  84. package/reactHooks/navigation/useRoute.ts +100 -0
  85. package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
  86. package/reactHooks/player/TVSeekControlller/ISeekController.ts +27 -2
  87. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +23 -61
  88. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +143 -48
  89. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +29 -51
  90. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +115 -0
  91. package/reactHooks/player/__tests__/useTapSeek._test.ts +94 -0
  92. package/reactHooks/player/const.ts +1 -5
  93. package/reactHooks/state/useRefWithInitialValue.ts +14 -0
  94. package/reactHooks/state/useStateFromSubscribe.ts +23 -0
  95. package/reactHooks/usePluginConfiguration.ts +1 -1
  96. package/reactHooks/useSubscriberFor.ts +4 -3
  97. package/reactUtils/__tests__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
  98. package/reactUtils/__tests__/reactUtils.test.js +10 -28
  99. package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
  100. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +13 -79
  101. package/reactUtils/createContext/__tests__/__snapshots__/index.test.js.snap +2 -2
  102. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
  103. package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
  104. package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
  105. package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
  106. package/reactUtils/createContext/__tests__/provider.test.js +4 -6
  107. package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
  108. package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
  109. package/reactUtils/index.ts +0 -14
  110. package/screenState/index.ts +24 -10
  111. package/testUtils/index.tsx +0 -10
  112. package/theme/__mocks__/index.ts +4 -0
  113. package/theme/index.ts +1 -1
  114. package/time/Timer.ts +5 -2
  115. package/types.ts +1 -0
  116. package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
  117. package/appUtils/keyCodes/keys.ios.js +0 -46
  118. /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
  119. /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
  120. /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
  121. /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
  122. /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
  123. /package/domUtils/{logger.js → logger.ts} +0 -0
@@ -0,0 +1,100 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ /* eslint max-len: off */
3
+
4
+ import { useContext } from "react";
5
+
6
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
7
+
8
+ import { legacyScreenData } from "@applicaster/quick-brick-core/App/NavigationProvider/utils";
9
+
10
+ import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
11
+
12
+ import { getTargetScreenData } from "../screen/useTargetScreenData";
13
+
14
+ import { useNavigation } from "./useNavigation";
15
+ import { useModalStore } from "../../modalState";
16
+ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
17
+ import { usePathname } from "./usePathname";
18
+
19
+ // starts with modal/
20
+ const isModalPathname = (pathname: string) => /^modal\//.test(pathname);
21
+
22
+ const isVideoModalPathname = (pathname: string) =>
23
+ /^video-modal\//.test(pathname);
24
+
25
+ const isHookModalPathname = (pathname: string) =>
26
+ /^hooks-modal\//.test(pathname);
27
+
28
+ const isHookPathname = (pathname: string) => /^\/hooks\//.test(pathname);
29
+
30
+ type VariousScreenData = LegacyNavigationScreenData | ZappRiver | ZappEntry;
31
+
32
+ export const useRoute = (): {
33
+ screenData: VariousScreenData;
34
+ pathname: string;
35
+ } => {
36
+ const pathname = usePathname() || "";
37
+ const navigator = useNavigation();
38
+
39
+ const screenDataContext = legacyScreenData(useContext(ScreenDataContext));
40
+
41
+ const { plugins, contentTypes, rivers } = usePickFromState([
42
+ "plugins",
43
+ "rivers",
44
+ "contentTypes",
45
+ ]);
46
+
47
+ const { modalState } = useModalStore();
48
+ const hookModalState = useZappHooksModalState();
49
+
50
+ const modalScreenData = modalState.screen;
51
+
52
+ const hookModalScreenData = hookModalState.state.screenData?.payload;
53
+
54
+ const videoModalScreenData =
55
+ navigator?.videoModalState?.item &&
56
+ legacyScreenData(
57
+ {
58
+ entry: navigator.videoModalState?.item as ZappEntry,
59
+ screen: getTargetScreenData(
60
+ navigator.videoModalState?.item as ZappEntry,
61
+ rivers,
62
+ contentTypes
63
+ ),
64
+ },
65
+ plugins
66
+ );
67
+
68
+ // There are 4 route scenarios
69
+ // For regular screens take date from navigator stack.
70
+ // is path is model grab screenData from modal state
71
+ // if path is video modal grab screenData from video state
72
+ // if path is hook modal grab screenData from hook state
73
+ // if path is hook grab screenData from screenData
74
+
75
+ if (isModalPathname(pathname)) {
76
+ const screenData = modalScreenData ?? ({} as ZappEntry);
77
+
78
+ return { screenData, pathname };
79
+ }
80
+
81
+ if (isVideoModalPathname(pathname)) {
82
+ const screenData = videoModalScreenData as LegacyNavigationScreenData;
83
+
84
+ return { screenData, pathname };
85
+ }
86
+
87
+ if (isHookModalPathname(pathname)) {
88
+ const screenData = hookModalScreenData;
89
+
90
+ return { screenData, pathname };
91
+ }
92
+
93
+ if (isHookPathname(pathname)) {
94
+ return { screenData: screenDataContext, pathname };
95
+ }
96
+
97
+ const screenData = screenDataContext;
98
+
99
+ return { screenData, pathname };
100
+ };
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { isWeb } from "../../reactUtils";
3
+ import { useNavigation } from "./useNavigation";
4
+ import { usePathname } from "./usePathname";
5
+ import {
6
+ QUICK_BRICK_CONTENT,
7
+ QUICK_BRICK_NAVBAR,
8
+ } from "@applicaster/quick-brick-core/const";
9
+
10
+ type Return = (prefix: string) => string;
11
+
12
+ export const useUniqueRouteSuffix = (): Return => {
13
+ const pathname = usePathname();
14
+ const { currentRoute } = useNavigation();
15
+
16
+ const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
17
+
18
+ const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
19
+ if (isWeb()) {
20
+ // we should use extended route prefix only on Web, where we have stack-navigator
21
+ return `${prefix}___${suffix}`;
22
+ }
23
+
24
+ // for other platforms use previous version
25
+ return prefix;
26
+ }, []);
27
+
28
+ return withUniqueRouteSuffix;
29
+ };
30
+
31
+ export const useNavbarId = () => {
32
+ const withUniqueRouteSuffix = useUniqueRouteSuffix();
33
+
34
+ return withUniqueRouteSuffix(QUICK_BRICK_NAVBAR);
35
+ };
36
+
37
+ export const useContentId = () => {
38
+ const withUniqueRouteSuffix = useUniqueRouteSuffix();
39
+
40
+ return withUniqueRouteSuffix(QUICK_BRICK_CONTENT);
41
+ };
@@ -1,7 +1,32 @@
1
- import { SEEK_TYPE } from "../const";
1
+ import { MAX_SEEK_TIME, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
2
+
3
+ export interface ISeekControllerDelegate {
4
+ handleDelayedSeek(offset: number): void;
5
+ handleSeek(offset: number): void;
6
+ handleSeekComplete(): void;
7
+ handlePlayerPause(): void;
8
+ }
2
9
 
3
10
  export interface ISeekController {
11
+ title: string;
4
12
  onStartSeek(seekType: SEEK_TYPE): void;
5
- onSeekEnd?(): void;
13
+ onSeekEnd(): void;
6
14
  reset(): void;
7
15
  }
16
+
17
+ export class SeekIncrementalBaseStrategy {
18
+ private skipMultiplier = 1;
19
+
20
+ getSkipTime = () =>
21
+ Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * this.skipMultiplier);
22
+
23
+ increment = () => {
24
+ this.skipMultiplier++;
25
+ };
26
+
27
+ getSkipMultiplier = () => this.skipMultiplier;
28
+
29
+ reset = () => {
30
+ this.skipMultiplier = 1;
31
+ };
32
+ }
@@ -1,102 +1,64 @@
1
- import { ISeekController } from "./ISeekController";
2
- import { SEEK_TYPE } from "../const";
3
1
  import {
4
- log_debug,
5
- log_error,
6
- log_verbose,
7
- TVSeekController,
8
- } from "./TVSeekController";
2
+ ISeekController,
3
+ ISeekControllerDelegate,
4
+ SeekIncrementalBaseStrategy,
5
+ } from "./ISeekController";
6
+ import { SEEK_TYPE } from "../const";
7
+ import { log_debug, log_error, log_verbose } from "./TVSeekController";
9
8
  import { interval, Subscription } from "rxjs";
10
9
  import { tap } from "rxjs/operators";
11
10
 
12
11
  interface LongPressConfig {
13
- LONG_PRESS_THRESHOLD: number; // ms
14
- TIMER_INTERVAL_MS: number; // ms
15
- SKIP_INCREMENT_STEP: number; // seconds
16
- MAX_SKIP_PER_TICK: number; // seconds
17
- INITIAL_SKIP_AMOUNT: number; // e.g., 10s to start
12
+ TIMER_INTERVAL_MS: number;
18
13
  }
19
14
 
20
15
  export class LongPressSeekController implements ISeekController {
21
- private pressStartTime = 0;
22
- private isLongPressActive = false;
23
- private currentSkipAmount = 0;
24
16
  private subscription: Subscription | null = null;
25
-
17
+ readonly title = "LongPressSeek";
26
18
  constructor(
27
- private parent: TVSeekController,
19
+ private seekControllerDelegate: ISeekControllerDelegate,
20
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
28
21
  private config: LongPressConfig
29
22
  ) {}
30
23
 
31
24
  onStartSeek(seekType: SEEK_TYPE) {
32
25
  this.cleanUpSubscription();
33
26
 
34
- log_debug(
35
- `LongPressSeekController: start long press seek type:${seekType}.`
36
- );
27
+ log_debug(`${this.title}: start long press seek type: ${seekType}.`);
37
28
 
38
- this.parent.currentSeekType = seekType;
39
- this.parent.pausePlayer();
40
-
41
- this.pressStartTime = Date.now();
42
- this.isLongPressActive = true;
43
- this.currentSkipAmount = this.config.INITIAL_SKIP_AMOUNT;
29
+ this.seekControllerDelegate.handlePlayerPause();
44
30
 
45
31
  this.subscription = interval(this.config.TIMER_INTERVAL_MS)
46
32
  .pipe(
47
33
  tap(() => {
48
- this.parent.handleDelayedSeek(
49
- this.parent.currentSeekType,
50
- this.currentSkipAmount
34
+ this.seekControllerDelegate.handleDelayedSeek(
35
+ this.seekIncrementalStrategy.getSkipTime()
51
36
  );
52
37
 
53
38
  log_debug(
54
- `LongPressSeekController: handle delayed seek type: ${seekType} - currentSkipAmount: ${this.currentSkipAmount}.`
39
+ `${
40
+ this.title
41
+ }: handle delayed seek type: ${seekType} - multiplier: ${this.seekIncrementalStrategy.getSkipMultiplier()}, currentSkipAmount: ${this.seekIncrementalStrategy.getSkipTime()}.`
55
42
  );
56
43
 
57
- this.currentSkipAmount = Math.min(
58
- this.currentSkipAmount + this.config.SKIP_INCREMENT_STEP,
59
- this.config.MAX_SKIP_PER_TICK
60
- );
44
+ this.seekIncrementalStrategy.increment();
61
45
  })
62
46
  )
63
47
  .subscribe({
64
48
  complete: () =>
65
- log_verbose(
66
- "LongPressSeekController: Long press subscription complete"
67
- ),
49
+ log_verbose(`${this.title}: Long press subscription complete`),
68
50
  error: (err) =>
69
- log_error(
70
- "LongPressSeekController: Long press subscription error",
71
- err
72
- ),
51
+ log_error(`${this.title}: Long press subscription error`, err),
73
52
  });
74
53
  }
75
54
 
76
55
  onSeekEnd() {
77
- const pressDuration = Date.now() - this.pressStartTime;
78
-
79
- if (
80
- this.isLongPressActive &&
81
- pressDuration > this.config.LONG_PRESS_THRESHOLD
82
- ) {
83
- const offset = this.parent.delayedSeekOffset;
84
-
85
- if (offset) {
86
- log_debug(
87
- `LongPressSeekController: onPressEnd for LongPressSeekController, seek offset: ${offset}`
88
- );
89
-
90
- this.parent.seekPlayer(offset);
91
- }
92
- }
93
-
94
- this.parent.resetAll();
56
+ this.cleanUpSubscription();
57
+ this.seekControllerDelegate.handleSeekComplete();
95
58
  }
96
59
 
97
60
  reset() {
98
- this.isLongPressActive = false;
99
- this.currentSkipAmount = 0;
61
+ this.seekIncrementalStrategy.reset();
100
62
  this.cleanUpSubscription();
101
63
  }
102
64
 
@@ -5,7 +5,11 @@ import { SEEK_TYPE } from "../const";
5
5
  import { createLogger } from "../../../logger";
6
6
  import { Player } from "../../../appUtils/playerManager/player";
7
7
  import { SeekKeyHandlerListener } from "../../../appUtils/keyInputHandler/KeyInputHandler";
8
- import { ISeekController } from "./ISeekController";
8
+ import {
9
+ ISeekController,
10
+ ISeekControllerDelegate,
11
+ SeekIncrementalBaseStrategy,
12
+ } from "./ISeekController";
9
13
 
10
14
  export const { log_warning, log_debug, log_info, log_error, log_verbose } =
11
15
  createLogger({
@@ -18,34 +22,48 @@ export enum SEEK_STRATEGY {
18
22
  AUTO_SEEK = "AUTO_SEEK",
19
23
  }
20
24
 
21
- export class TVSeekController implements SeekKeyHandlerListener {
22
- public currentSeekType: SEEK_TYPE | null = null;
23
- public delayedSeekOffset: number | null = null;
25
+ const CONFIG_TAP_SEEK = {
26
+ CLICKS_BEFORE_PAUSE: 0,
27
+ };
28
+
29
+ const CONFIG_LONG_PRESS = {
30
+ TIMER_INTERVAL_MS: 200,
31
+ };
32
+
33
+ const LONG_PRESS_THRESHOLD = 500;
34
+ const SEEK_END_TIMEOUT_DURATION = 700;
35
+ // prettier-ignore
36
+
37
+ export class TVSeekController
38
+ implements SeekKeyHandlerListener, ISeekControllerDelegate {
39
+ public delayedSeekPosition: number | null = null;
24
40
  private readonly playerController: Player;
25
41
  private delayedSeekProgressSubject = new BehaviorSubject<number | null>(null);
42
+ isPlayerInitiallyPaused: null | boolean = null;
43
+ currentSeekType: SEEK_TYPE | null = null;
26
44
 
27
45
  private currentSeekController: ISeekController;
28
-
29
- private tapSeekController: TapSeekController;
30
- private longPressSeekController: LongPressSeekController;
46
+ private readonly tapSeekController: TapSeekController;
47
+ private readonly longPressSeekController: LongPressSeekController;
31
48
  private longPressThresholdTimeout: any | null = null;
32
-
33
- private config = {
34
- LONG_PRESS_THRESHOLD: 500,
35
- TIMER_INTERVAL_MS: 300,
36
- SKIP_INCREMENT_STEP: 5,
37
- MAX_SKIP_PER_TICK: 300,
38
- INITIAL_SKIP_AMOUNT: 10,
39
- };
49
+ private seekEndTimeout: any | null = null;
40
50
 
41
51
  constructor(playerController: Player) {
42
52
  this.playerController = playerController;
43
- this.tapSeekController = new TapSeekController(this);
53
+
54
+ this.tapSeekController = new TapSeekController(
55
+ this,
56
+ new SeekIncrementalBaseStrategy(),
57
+ CONFIG_TAP_SEEK
58
+ );
44
59
 
45
60
  this.longPressSeekController = new LongPressSeekController(
46
61
  this,
47
- this.config
62
+ new SeekIncrementalBaseStrategy(),
63
+ CONFIG_LONG_PRESS
48
64
  );
65
+
66
+ this.currentSeekController = this.tapSeekController;
49
67
  }
50
68
 
51
69
  getDelayedSeekProgressObservable = () => {
@@ -55,27 +73,37 @@ export class TVSeekController implements SeekKeyHandlerListener {
55
73
  setStrategy = (seekStrategy: SEEK_STRATEGY) => {
56
74
  switch (seekStrategy) {
57
75
  case SEEK_STRATEGY.TAP_SEEK:
58
- clearTimeout(this.longPressThresholdTimeout);
59
- this.longPressThresholdTimeout = null;
60
76
  this.currentSeekController = this.tapSeekController;
77
+ log_debug("TVSeekController: setStrategy - TAP_SEEK");
61
78
 
62
79
  break;
63
80
  case SEEK_STRATEGY.AUTO_SEEK:
64
- this.currentSeekController.reset();
65
81
  this.currentSeekController = this.longPressSeekController;
82
+ log_debug("TVSeekController: setStrategy - AUTO_SEEK");
83
+
66
84
  break;
67
85
  default:
68
86
  }
69
87
  };
70
88
 
71
89
  handleStartSeek = (seekType: SEEK_TYPE) => {
90
+ this.currentSeekType = seekType;
91
+
92
+ if (this.isPlayerInitiallyPaused === null) {
93
+ this.isPlayerInitiallyPaused = this.playerController.isPaused();
94
+ }
95
+
96
+ log_debug(`handleStartSeek: seekType: ${seekType}, initiallyPaused: ${this.isPlayerInitiallyPaused}`);
97
+
98
+ this.clearLongPressThresholdTimeout();
99
+ this.clearSeekCompleteTimeout();
72
100
  this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
73
101
  this.currentSeekController.onStartSeek(seekType);
74
102
 
75
103
  this.longPressThresholdTimeout = setTimeout(() => {
76
104
  this.setStrategy(SEEK_STRATEGY.AUTO_SEEK);
77
105
  this.currentSeekController.onStartSeek(seekType);
78
- }, this.config.LONG_PRESS_THRESHOLD);
106
+ }, LONG_PRESS_THRESHOLD);
79
107
  };
80
108
 
81
109
  onForwardPress = (): boolean => {
@@ -91,8 +119,9 @@ export class TVSeekController implements SeekKeyHandlerListener {
91
119
  };
92
120
 
93
121
  onSeekEnd = () => {
122
+ log_debug("onSeekEnd: Seek ended");
123
+ this.clearLongPressThresholdTimeout();
94
124
  this.currentSeekController.onSeekEnd();
95
- this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
96
125
  };
97
126
 
98
127
  onRewindPressOut = () => {
@@ -103,62 +132,128 @@ export class TVSeekController implements SeekKeyHandlerListener {
103
132
  this.onSeekEnd();
104
133
  };
105
134
 
106
- public onVideoProgress = (position: number) => {
107
- if (this.delayedSeekOffset) {
135
+ onVideoProgress = (position: number) => {
136
+ if (this.delayedSeekPosition !== null) {
108
137
  return;
109
138
  }
110
139
 
111
- // TODO: If 1 second after seek, send
112
-
113
140
  this.delayedSeekProgressSubject.next(position);
114
141
  };
115
142
 
116
- handleDelayedSeek(eventType: SEEK_TYPE, skipTime: number) {
117
- if (this.delayedSeekOffset === null) {
118
- this.pausePlayer();
119
- }
143
+ handleDelayedSeek = (offset: number = 0) => {
144
+ this.handlePlayerPause();
145
+
146
+ const currentPos =
147
+ this.delayedSeekPosition === null
148
+ ? this.playerController.getPosition()
149
+ : this.delayedSeekPosition;
120
150
 
121
- const newOffset = (this.delayedSeekOffset || 0) + skipTime;
122
- const currentPos = this.playerController.getPosition();
123
151
  let targetPos = currentPos;
124
152
 
125
- if (eventType === SEEK_TYPE.FORWARD) {
153
+ if (this.currentSeekType === SEEK_TYPE.FORWARD) {
126
154
  targetPos = Math.min(
127
- currentPos + newOffset,
155
+ currentPos + offset,
128
156
  this.playerController.getSeekableDuration()
129
157
  );
158
+ } else if (this.currentSeekType === SEEK_TYPE.REWIND) {
159
+ targetPos = Math.max(0, currentPos - offset);
130
160
  } else {
131
- targetPos = Math.max(0, currentPos - newOffset);
161
+ log_warning(
162
+ `TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}`
163
+ );
132
164
  }
133
165
 
134
- this.delayedSeekOffset = newOffset;
135
-
136
166
  log_debug(
137
- `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, offset: ${newOffset}, skipTime: ${skipTime}`
167
+ `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}`
138
168
  );
139
169
 
170
+ const prevValue = this.delayedSeekProgressSubject.getValue();
171
+
172
+ if (prevValue !== null) {
173
+ const delta = Math.abs(prevValue - targetPos);
174
+
175
+ if (delta < 1) {
176
+ return;
177
+ }
178
+ }
179
+
180
+ log_debug(`handleDelayedSeek: currentPos: ${currentPos} targetPos:${targetPos}, initiallyPaused: ${this.isPlayerInitiallyPaused}`);
181
+
182
+ this.delayedSeekPosition = targetPos;
140
183
  this.delayedSeekProgressSubject.next(targetPos);
141
- }
184
+ };
185
+
186
+ handleSeekTo = (newPosition: number) => {
187
+ this.playerController.seekTo(newPosition);
188
+
189
+ if (!this.isPlayerInitiallyPaused) {
190
+ this.playerController.play();
191
+ }
192
+ };
142
193
 
143
- seekPlayer(offset: number) {
194
+ handleSeek = (offset: number) => {
144
195
  this.playerController[this.currentSeekType](offset);
145
- this.playerPlay();
146
- }
147
196
 
148
- playerPlay() {
149
- if (this.playerController.isPaused()) {
197
+ if (!this.isPlayerInitiallyPaused) {
150
198
  this.playerController.play();
151
199
  }
152
- }
200
+ };
201
+
202
+ handleSeekComplete = () => {
203
+ this.clearSeekCompleteTimeout();
204
+
205
+ this.seekEndTimeout = setTimeout(() => {
206
+ if (this.delayedSeekPosition !== null) {
207
+ this.handleSeekTo(this.delayedSeekPosition);
208
+ }
209
+
210
+ log_debug(
211
+ `handleSeekComplete: Seek finished, type: ${this.currentSeekController.title}, seekPosition: ${this.delayedSeekPosition}. Reset all states and seek position.`
212
+ );
213
+
214
+ this.resetAll();
215
+ }, SEEK_END_TIMEOUT_DURATION);
216
+ };
217
+
218
+ isSeeking = (): boolean => {
219
+ return this.delayedSeekPosition !== null;
220
+ };
221
+
222
+ handlePlayerPause = () => {
223
+ if (this.playerController.isPaused()) {
224
+ return;
225
+ }
153
226
 
154
- pausePlayer() {
155
227
  this.playerController.pause();
156
- }
228
+ };
229
+
230
+ clearSeekCompleteTimeout = () => {
231
+ if (this.seekEndTimeout === null) {
232
+ return;
233
+ }
234
+
235
+ clearTimeout(this.seekEndTimeout);
236
+ this.seekEndTimeout = null;
237
+ };
238
+
239
+ clearLongPressThresholdTimeout = () => {
240
+ if (this.longPressThresholdTimeout === null) {
241
+ return;
242
+ }
243
+
244
+ clearTimeout(this.longPressThresholdTimeout);
245
+ this.longPressThresholdTimeout = null;
246
+ };
157
247
 
158
248
  resetAll() {
159
249
  log_debug("TVSeekController: Reset all states");
160
250
  this.currentSeekType = null;
161
- this.delayedSeekOffset = null;
251
+
252
+ this.delayedSeekPosition = null;
253
+ this.isPlayerInitiallyPaused = null;
254
+
255
+ this.clearSeekCompleteTimeout();
256
+ this.clearLongPressThresholdTimeout();
162
257
 
163
258
  this.tapSeekController.reset();
164
259
  this.longPressSeekController.reset();
@@ -1,75 +1,53 @@
1
- import { ISeekController } from "./ISeekController";
2
- import { SEEK_TYPE, MAX_SEEK_TIME, SKIP_TIME_BASE } from "../const";
3
- import { log_debug, TVSeekController } from "./TVSeekController";
4
-
5
- const getSkipTime = (multiplier: number) =>
6
- Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
1
+ import {
2
+ ISeekController,
3
+ ISeekControllerDelegate,
4
+ SeekIncrementalBaseStrategy,
5
+ } from "./ISeekController";
6
+ import { SEEK_TYPE } from "../const";
7
+ import { log_debug } from "./TVSeekController";
8
+
9
+ interface TapPressConfig {
10
+ CLICKS_BEFORE_PAUSE: number;
11
+ }
7
12
 
8
13
  export class TapSeekController implements ISeekController {
9
- private tapSequenceTimeout: any | null = null;
10
- private seekMultiplier = 1;
11
- private readonly ON_PRESS_RESET_DURATION = 500; // ms
12
-
13
- constructor(private parent: TVSeekController) {}
14
+ readonly title = "TapSeek";
15
+ constructor(
16
+ private seekControllerDelegate: ISeekControllerDelegate,
17
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
18
+ private config: TapPressConfig
19
+ ) {}
14
20
 
15
21
  onStartSeek = (seekType: SEEK_TYPE) => {
16
- log_debug(`TapSeekController: start tap seek type: ${seekType}.`);
17
-
18
- const prevSeekType = this.parent.currentSeekType;
22
+ log_debug(`${this.title}: start tap seek type: ${seekType}.`);
19
23
 
20
- if (prevSeekType !== seekType) {
21
- this.reset();
22
- }
23
-
24
- this.parent.currentSeekType = seekType;
25
-
26
- if (this.tapSequenceTimeout) {
27
- clearTimeout(this.tapSequenceTimeout);
28
- this.tapSequenceTimeout = null;
29
- }
24
+ const skipTime = this.seekIncrementalStrategy.getSkipTime();
25
+ const seekMultiplier = this.seekIncrementalStrategy.getSkipMultiplier();
30
26
 
31
- const clicksBeforePause = 0;
32
- const skipTime = getSkipTime(this.seekMultiplier);
33
-
34
- if (this.seekMultiplier > clicksBeforePause) {
27
+ if (seekMultiplier > this.config.CLICKS_BEFORE_PAUSE) {
35
28
  log_debug(
36
- `TapSeekController: handle delayed seek type:${seekType} - skipTime: ${skipTime}.`
29
+ `${this.title}: handle delayed seek type:${seekType} - skipTime: ${skipTime}, seekMultiplier: ${seekMultiplier}.`
37
30
  );
38
31
 
39
- this.parent.handleDelayedSeek(seekType, skipTime);
32
+ this.seekControllerDelegate.handleDelayedSeek(skipTime);
40
33
  } else {
41
34
  log_debug(
42
- `TapSeekController: handle seek type:${seekType} - skipTime: ${skipTime}.`
35
+ `${this.title}: handle seek type:${seekType} - skipTime: ${skipTime}.`
43
36
  );
44
37
 
45
- this.parent.seekPlayer(skipTime);
38
+ this.seekControllerDelegate.handleSeek(skipTime);
46
39
  }
47
40
 
48
- this.seekMultiplier++;
41
+ this.seekIncrementalStrategy.increment();
49
42
  };
50
43
 
51
44
  onSeekEnd = () => {
52
- this.tapSequenceTimeout = setTimeout(() => {
53
- if (this.parent.delayedSeekOffset !== null) {
54
- this.parent.seekPlayer(this.parent.delayedSeekOffset);
55
- }
56
-
57
- log_debug(
58
- `TapSeekController: Tap sequence timeout fired, delayedSeekOffset: ${this.parent.delayedSeekOffset}.`
59
- );
60
-
61
- this.parent.resetAll();
62
- }, this.ON_PRESS_RESET_DURATION);
45
+ this.seekControllerDelegate.handleSeekComplete();
63
46
  };
64
47
 
65
48
  reset = () => {
66
- log_debug("TapSeekController: Reset states");
67
-
68
- this.seekMultiplier = 1;
49
+ log_debug(`${this.title}: Reset states`);
69
50
 
70
- if (this.tapSequenceTimeout) {
71
- clearTimeout(this.tapSequenceTimeout);
72
- this.tapSequenceTimeout = null;
73
- }
51
+ this.seekIncrementalStrategy.reset();
74
52
  };
75
53
  }