@applicaster/zapp-react-native-utils 13.0.0-alpha.7210171818 → 13.0.0-alpha.7846284820

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 (110) hide show
  1. package/actionsExecutor/ActionExecutor.ts +16 -9
  2. package/actionsExecutor/ActionExecutorContext.tsx +74 -9
  3. package/alertUtils/alert.ts +148 -0
  4. package/alertUtils/index.ts +1 -148
  5. package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
  6. package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
  7. package/analyticsUtils/helpers/hooks.ts +1 -1
  8. package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
  9. package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +10 -8
  10. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +693 -0
  11. package/appUtils/HooksManager/__tests__/hook.test.js +196 -184
  12. package/appUtils/HooksManager/__tests__/hooksManager.test.js +96 -99
  13. package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
  14. package/appUtils/HooksManager/{index.js → index.ts} +121 -112
  15. package/appUtils/HooksManager/types.ts +119 -0
  16. package/appUtils/RiverFocusManager/index.js +17 -12
  17. package/appUtils/accessibilityManager/index.ts +42 -1
  18. package/appUtils/contextKeysManager/contextResolver.ts +77 -0
  19. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +6 -6
  20. package/appUtils/focusManager/index.ts +91 -65
  21. package/appUtils/focusManager/treeDataStructure/Tree/index.js +25 -3
  22. package/appUtils/focusManager/treeDataStructure/Utils/index.js +1 -3
  23. package/appUtils/focusManager/utils/enums.ts +4 -0
  24. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  25. package/appUtils/{index.js → index.ts} +1 -1
  26. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
  27. package/appUtils/keyCodes/__tests__/keyCodes.test.js +4 -4
  28. package/appUtils/keyCodes/{index.js → index.ts} +2 -0
  29. package/appUtils/keyCodes/keys/index.ts +3 -0
  30. package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
  31. package/appUtils/keyCodes/keys/keys.android.ts +18 -0
  32. package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
  33. package/appUtils/keyCodes/keys/keys.ts +15 -0
  34. package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +4 -3
  35. package/appUtils/keyCodes/keysByPlatform.ts +8 -0
  36. package/appUtils/keyInputHandler/KeyInputHandler.ts +10 -32
  37. package/appUtils/orientationHelper.ts +1 -1
  38. package/appUtils/platform/const.ts +14 -0
  39. package/appUtils/platform/platformUtils.ts +17 -15
  40. package/appUtils/playerManager/index.ts +6 -2
  41. package/appUtils/playerManager/player.ts +9 -10
  42. package/appUtils/playerManager/playerNative.ts +1 -1
  43. package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -15
  44. package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
  45. package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
  46. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
  47. package/functionUtils/index.ts +15 -11
  48. package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
  49. package/idleUtils/index.ts +1 -1
  50. package/idleUtils/withTimeout.ts +15 -2
  51. package/logger/const.ts +9 -67
  52. package/manifestUtils/__tests__/createConfig.test.js +18 -16
  53. package/modalState/index.ts +1 -5
  54. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +6 -6
  55. package/navigationUtils/__tests__/itemTypes.test.js +2 -2
  56. package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
  57. package/navigationUtils/index.ts +17 -8
  58. package/package.json +3 -2
  59. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  60. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -1
  61. package/reactHooks/cell-click/__tests__/index.test.js +10 -2
  62. package/reactHooks/cell-click/index.ts +7 -1
  63. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
  64. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
  65. package/reactHooks/feed/useFeedLoader.tsx +1 -1
  66. package/reactHooks/feed/usePipesCacheReset.ts +5 -5
  67. package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
  68. package/reactHooks/layout/__tests__/index.test.tsx +10 -10
  69. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
  70. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
  71. package/reactHooks/navigation/__tests__/index.test.tsx +0 -4
  72. package/reactHooks/navigation/index.ts +29 -218
  73. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +12 -0
  74. package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
  75. package/reactHooks/navigation/useGetTabBarHeight.ts +12 -0
  76. package/reactHooks/navigation/useIsScreenActive.ts +17 -0
  77. package/reactHooks/navigation/useNavigationPluginData.ts +15 -0
  78. package/reactHooks/navigation/useNavigationType.ts +15 -0
  79. package/reactHooks/navigation/usePathname.ts +4 -0
  80. package/reactHooks/navigation/useRoute.ts +100 -0
  81. package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
  82. package/reactHooks/player/TVSeekControlller/ISeekController.ts +27 -2
  83. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +39 -74
  84. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +155 -96
  85. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +31 -53
  86. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +115 -0
  87. package/reactHooks/player/__tests__/useTapSeek._test.ts +94 -0
  88. package/reactHooks/player/const.ts +1 -5
  89. package/reactHooks/usePluginConfiguration.ts +1 -1
  90. package/reactHooks/useSubscriberFor.ts +4 -3
  91. package/reactUtils/__tests__/reactUtils.test.js +10 -28
  92. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +19 -19
  93. package/reactUtils/createContext/__tests__/__snapshots__/index.test.js.snap +2 -2
  94. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -3
  95. package/reactUtils/createContext/__tests__/helpers.test.js +8 -8
  96. package/reactUtils/index.ts +0 -14
  97. package/screenState/index.ts +24 -10
  98. package/theme/__mocks__/index.ts +4 -0
  99. package/theme/index.ts +1 -1
  100. package/time/Timer.ts +5 -2
  101. package/types.ts +1 -0
  102. package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
  103. package/appUtils/keyCodes/keys.ios.js +0 -46
  104. package/appUtils/playerManager/DelayedSeekPlayerWrapper.tsx +0 -180
  105. /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
  106. /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
  107. /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
  108. /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
  109. /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
  110. /package/domUtils/{logger.js → logger.ts} +0 -0
@@ -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,99 +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
- /**
13
- * Example interface for the configuration needed for long-press seeking.
14
- */
15
11
  interface LongPressConfig {
16
- LONG_PRESS_THRESHOLD: number; // ms
17
- TIMER_INTERVAL_MS: number; // ms
18
- SKIP_INCREMENT_STEP: number; // seconds
19
- MAX_SKIP_PER_TICK: number; // seconds
20
- INITIAL_SKIP_AMOUNT: number; // e.g., 10s to start
12
+ TIMER_INTERVAL_MS: number;
21
13
  }
22
14
 
23
15
  export class LongPressSeekController implements ISeekController {
24
- private pressStartTime = 0;
25
- private isLongPressActive = false;
26
- private currentSkipAmount = 0;
27
16
  private subscription: Subscription | null = null;
28
-
17
+ readonly title = "LongPressSeek";
29
18
  constructor(
30
- private parent: TVSeekController,
19
+ private seekControllerDelegate: ISeekControllerDelegate,
20
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
31
21
  private config: LongPressConfig
32
22
  ) {}
33
23
 
34
24
  onStartSeek(seekType: SEEK_TYPE) {
35
25
  this.cleanUpSubscription();
36
26
 
37
- log_debug(
38
- `LongPressSeekController: start long press seek type:${seekType}.`
39
- );
40
-
41
- this.parent.currentSeekType = seekType;
42
- this.parent.pausePlayer();
43
-
44
- this.pressStartTime = Date.now();
45
- this.isLongPressActive = true;
46
- this.currentSkipAmount = this.config.INITIAL_SKIP_AMOUNT;
47
-
48
- // Start the interval immediately without waiting for a threshold.
49
- this.subscription = interval(this.config.TIMER_INTERVAL_MS).pipe(
50
- tap(() => {
51
- this.parent.handleDelayedSeek(
52
- this.parent.currentSeekType,
53
- this.currentSkipAmount
54
- );
55
-
56
- log_debug(
57
- `LongPressSeekController: handle delayed seek type: ${seekType} - currentSkipAmount: ${this.currentSkipAmount}.`
58
- );
59
-
60
- this.currentSkipAmount = Math.min(
61
- this.currentSkipAmount + this.config.SKIP_INCREMENT_STEP,
62
- this.config.MAX_SKIP_PER_TICK
63
- );
64
- })
65
- ).subscribe({
66
- complete: () =>
67
- log_verbose("LongPressSeekController: Long press subscription complete"),
68
- error: (err) =>
69
- log_error("LongPressSeekController: Long press subscription error", err),
70
- });
27
+ log_debug(`${this.title}: start long press seek type: ${seekType}.`);
28
+
29
+ this.seekControllerDelegate.handlePlayerPause();
30
+
31
+ this.subscription = interval(this.config.TIMER_INTERVAL_MS)
32
+ .pipe(
33
+ tap(() => {
34
+ this.seekControllerDelegate.handleDelayedSeek(
35
+ this.seekIncrementalStrategy.getSkipTime()
36
+ );
37
+
38
+ log_debug(
39
+ `${
40
+ this.title
41
+ }: handle delayed seek type: ${seekType} - multiplier: ${this.seekIncrementalStrategy.getSkipMultiplier()}, currentSkipAmount: ${this.seekIncrementalStrategy.getSkipTime()}.`
42
+ );
43
+
44
+ this.seekIncrementalStrategy.increment();
45
+ })
46
+ )
47
+ .subscribe({
48
+ complete: () =>
49
+ log_verbose(`${this.title}: Long press subscription complete`),
50
+ error: (err) =>
51
+ log_error(`${this.title}: Long press subscription error`, err),
52
+ });
71
53
  }
72
54
 
73
55
  onSeekEnd() {
74
- const pressDuration = Date.now() - this.pressStartTime;
75
-
76
- if (
77
- this.isLongPressActive &&
78
- pressDuration > this.config.LONG_PRESS_THRESHOLD
79
- ) {
80
- const offset = this.parent.delayedSeekOffset;
81
-
82
- if (offset) {
83
- log_debug(
84
- `LongPressSeekController: onPressEnd for LongPressSeekController, seek offset: ${offset}`
85
- );
86
-
87
- this.parent.seekPlayer(offset);
88
- }
89
- }
90
-
91
- this.parent.resetAll();
56
+ this.cleanUpSubscription();
57
+ this.seekControllerDelegate.handleSeekComplete();
92
58
  }
93
59
 
94
60
  reset() {
95
- this.isLongPressActive = false;
96
- this.currentSkipAmount = 0;
61
+ this.seekIncrementalStrategy.reset();
97
62
  this.cleanUpSubscription();
98
63
  }
99
64
 
@@ -1,12 +1,15 @@
1
1
  import { BehaviorSubject } from "rxjs";
2
2
  import { TapSeekController } from "./TapSeekController";
3
3
  import { LongPressSeekController } from "./LongPressSeekController";
4
- import { playerManager } from "../../../appUtils";
5
4
  import { SEEK_TYPE } from "../const";
6
5
  import { createLogger } from "../../../logger";
7
6
  import { Player } from "../../../appUtils/playerManager/player";
8
7
  import { SeekKeyHandlerListener } from "../../../appUtils/keyInputHandler/KeyInputHandler";
9
- import { ISeekController } from "./ISeekController";
8
+ import {
9
+ ISeekController,
10
+ ISeekControllerDelegate,
11
+ SeekIncrementalBaseStrategy,
12
+ } from "./ISeekController";
10
13
 
11
14
  export const { log_warning, log_debug, log_info, log_error, log_verbose } =
12
15
  createLogger({
@@ -19,70 +22,87 @@ export enum SEEK_STRATEGY {
19
22
  AUTO_SEEK = "AUTO_SEEK",
20
23
  }
21
24
 
22
- export class TVSeekController implements SeekKeyHandlerListener {
23
- // -- SHARED STATE ACROSS STRATEGIES --
24
- public currentSeekType: SEEK_TYPE | null = null;
25
- public delayedSeekOffset: number | null = null;
26
- private controller: Player;
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;
40
+ private readonly playerController: Player;
27
41
  private delayedSeekProgressSubject = new BehaviorSubject<number | null>(null);
42
+ isPlayerInitiallyPaused: null | boolean = null;
43
+ currentSeekType: SEEK_TYPE | null = null;
28
44
 
29
45
  private currentSeekController: ISeekController;
30
-
31
- // -- STRATEGY INSTANCES --
32
- private tapSeekController: TapSeekController;
33
- private longPressSeekController: LongPressSeekController;
46
+ private readonly tapSeekController: TapSeekController;
47
+ private readonly longPressSeekController: LongPressSeekController;
34
48
  private longPressThresholdTimeout: any | null = null;
49
+ private seekEndTimeout: any | null = null;
35
50
 
36
- // Example config for long press
37
- private config = {
38
- LONG_PRESS_THRESHOLD: 500,
39
- TIMER_INTERVAL_MS: 300,
40
- SKIP_INCREMENT_STEP: 5,
41
- MAX_SKIP_PER_TICK: 300,
42
- INITIAL_SKIP_AMOUNT: 10,
43
- };
51
+ constructor(playerController: Player) {
52
+ this.playerController = playerController;
44
53
 
45
- constructor(controller: Player) {
46
- this.controller = controller;
47
- this.tapSeekController = new TapSeekController(this);
54
+ this.tapSeekController = new TapSeekController(
55
+ this,
56
+ new SeekIncrementalBaseStrategy(),
57
+ CONFIG_TAP_SEEK
58
+ );
48
59
 
49
60
  this.longPressSeekController = new LongPressSeekController(
50
61
  this,
51
- this.config
62
+ new SeekIncrementalBaseStrategy(),
63
+ CONFIG_LONG_PRESS
52
64
  );
65
+
66
+ this.currentSeekController = this.tapSeekController;
53
67
  }
54
68
 
55
69
  getDelayedSeekProgressObservable = () => {
56
70
  return this.delayedSeekProgressSubject;
57
- }
71
+ };
58
72
 
59
73
  setStrategy = (seekStrategy: SEEK_STRATEGY) => {
60
74
  switch (seekStrategy) {
61
75
  case SEEK_STRATEGY.TAP_SEEK:
62
- clearTimeout(this.longPressThresholdTimeout);
63
- this.longPressThresholdTimeout = null;
64
76
  this.currentSeekController = this.tapSeekController;
77
+ log_debug("TVSeekController: setStrategy - TAP_SEEK");
65
78
 
66
79
  break;
67
80
  case SEEK_STRATEGY.AUTO_SEEK:
68
- this.currentSeekController.reset();
69
81
  this.currentSeekController = this.longPressSeekController;
82
+ log_debug("TVSeekController: setStrategy - AUTO_SEEK");
83
+
70
84
  break;
71
85
  default:
72
86
  }
73
- }
87
+ };
74
88
 
75
89
  handleStartSeek = (seekType: SEEK_TYPE) => {
90
+ this.currentSeekType = seekType;
91
+
92
+ if (this.isPlayerInitiallyPaused === null) {
93
+ this.isPlayerInitiallyPaused = this.playerController.isPaused();
94
+ }
95
+
96
+ this.clearLongPressThresholdTimeout();
97
+ this.clearSeekCompleteTimeout();
76
98
  this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
77
99
  this.currentSeekController.onStartSeek(seekType);
78
- log_verbose(`handleStartSeek: TAP seekType: ${seekType}`);
79
100
 
80
101
  this.longPressThresholdTimeout = setTimeout(() => {
81
- log_verbose(`handleStartSeek: longPressThresholdTimeout: TIMEOUT, START AUTO_SEEK seekType: ${seekType}`);
82
- this.setStrategy(SEEK_STRATEGY.AUTO_SEEK);
102
+ this.setStrategy(SEEK_STRATEGY.AUTO_SEEK);
83
103
  this.currentSeekController.onStartSeek(seekType);
84
- }, this.config.LONG_PRESS_THRESHOLD);
85
- }
104
+ }, LONG_PRESS_THRESHOLD);
105
+ };
86
106
 
87
107
  onForwardPress = (): boolean => {
88
108
  this.handleStartSeek(SEEK_TYPE.FORWARD);
@@ -97,103 +117,142 @@ export class TVSeekController implements SeekKeyHandlerListener {
97
117
  };
98
118
 
99
119
  onSeekEnd = () => {
120
+ this.clearLongPressThresholdTimeout();
100
121
  this.currentSeekController.onSeekEnd();
101
- this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
102
- }
122
+ };
103
123
 
104
124
  onRewindPressOut = () => {
105
- const isAutoSeek = this.currentSeekController === this.longPressSeekController;
106
-
107
- log_verbose(`handleStartSeek: onRewindPressOut, isAutoSeek: ${isAutoSeek}`);
108
-
109
125
  this.onSeekEnd();
110
- }
126
+ };
111
127
 
112
128
  onForwardPressOut = () => {
113
- const isAutoSeek = this.currentSeekController === this.longPressSeekController;
114
-
115
- log_verbose(`handleStartSeek: onForwardPressOut, isAutoSeek: ${isAutoSeek}`);
116
129
  this.onSeekEnd();
117
- }
130
+ };
118
131
 
119
- // ---------------------------
120
- // SHARED METHODS
121
- // ---------------------------
132
+ onVideoProgress = (position: number) => {
133
+ if (this.delayedSeekPosition !== null) {
134
+ return;
135
+ }
122
136
 
123
- /** Called by either strategy to accumulate offset while paused. */
124
- handleDelayedSeek(eventType: SEEK_TYPE, skipTime: number) {
125
- const player = this.getPlayer();
126
- if (!player) return;
137
+ this.delayedSeekProgressSubject.next(position);
138
+ };
127
139
 
128
- // If not previously delayed, we pause the first time
129
- if (this.delayedSeekOffset === null) {
130
- this.pausePlayer();
131
- }
140
+ handleDelayedSeek = (offset: number = 0) => {
141
+ this.handlePlayerPause();
142
+
143
+ const currentPos =
144
+ this.delayedSeekPosition === null
145
+ ? this.playerController.getPosition()
146
+ : this.delayedSeekPosition;
132
147
 
133
- const newOffset = (this.delayedSeekOffset || 0) + skipTime;
134
- const currentPos = player.getPosition();
135
148
  let targetPos = currentPos;
136
149
 
137
- if (eventType === SEEK_TYPE.FORWARD) {
138
- targetPos = Math.min(currentPos + newOffset, player.getDuration());
150
+ if (this.currentSeekType === SEEK_TYPE.FORWARD) {
151
+ targetPos = Math.min(
152
+ currentPos + offset,
153
+ this.playerController.getSeekableDuration()
154
+ );
155
+ } else if (this.currentSeekType === SEEK_TYPE.REWIND) {
156
+ targetPos = Math.max(0, currentPos - offset);
139
157
  } else {
140
- targetPos = Math.max(0, currentPos - newOffset);
158
+ log_warning(
159
+ `TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}`
160
+ );
141
161
  }
142
162
 
143
- this.delayedSeekOffset = newOffset;
144
-
145
163
  log_debug(
146
- `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, offset: ${newOffset}, skipTime: ${skipTime}`
164
+ `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}`
147
165
  );
148
166
 
167
+ const prevValue = this.delayedSeekProgressSubject.getValue();
168
+
169
+ if (prevValue !== null) {
170
+ const delta = Math.abs(prevValue - targetPos);
171
+
172
+ if (delta < 1) {
173
+ return;
174
+ }
175
+ }
176
+
177
+ this.delayedSeekPosition = targetPos;
149
178
  this.delayedSeekProgressSubject.next(targetPos);
150
- }
179
+ };
151
180
 
152
- seekPlayer(offset: number) {
153
- const player = this.getPlayer();
154
- if (!player || !this.currentSeekType) return;
155
- player[this.currentSeekType](offset);
156
- this.playerPlay();
157
- }
181
+ handleSeekTo = (newPosition: number) => {
182
+ this.playerController.seekTo(newPosition);
158
183
 
159
- playerPlay() {
160
- const player = this.getPlayer();
161
- if (!player) return;
184
+ if (!this.isPlayerInitiallyPaused) {
185
+ this.playerController.play();
186
+ }
187
+ };
162
188
 
163
- if (player.isPaused()) {
164
- player.play();
189
+ handleSeek = (offset: number) => {
190
+ this.playerController[this.currentSeekType](offset);
191
+
192
+ if (!this.isPlayerInitiallyPaused) {
193
+ this.playerController.play();
165
194
  }
166
- }
195
+ };
167
196
 
168
- pausePlayer() {
169
- const player = this.getPlayer();
170
- if (!player) return;
171
- player.pause();
172
- }
197
+ handleSeekComplete = () => {
198
+ this.clearSeekCompleteTimeout();
199
+
200
+ this.seekEndTimeout = setTimeout(() => {
201
+ if (this.delayedSeekPosition !== null) {
202
+ this.handleSeekTo(this.delayedSeekPosition);
203
+ }
204
+
205
+ log_debug(
206
+ `handleSeekComplete: Seek finished, type: ${this.currentSeekController.title}, seekPosition: ${this.delayedSeekPosition}. Reset all states and seek position.`
207
+ );
208
+
209
+ this.resetAll();
210
+ }, SEEK_END_TIMEOUT_DURATION);
211
+ };
212
+
213
+ isSeeking = (): boolean => {
214
+ return this.delayedSeekPosition !== null;
215
+ };
216
+
217
+ handlePlayerPause = () => {
218
+ if (this.playerController.isPaused()) {
219
+ return;
220
+ }
221
+
222
+ this.playerController.pause();
223
+ };
224
+
225
+ clearSeekCompleteTimeout = () => {
226
+ if (this.seekEndTimeout === null) {
227
+ return;
228
+ }
229
+
230
+ clearTimeout(this.seekEndTimeout);
231
+ this.seekEndTimeout = null;
232
+ };
233
+
234
+ clearLongPressThresholdTimeout = () => {
235
+ if (this.longPressThresholdTimeout === null) {
236
+ return;
237
+ }
238
+
239
+ clearTimeout(this.longPressThresholdTimeout);
240
+ this.longPressThresholdTimeout = null;
241
+ };
173
242
 
174
- /**
175
- * Reset *all* relevant state and strategy controllers
176
- */
177
243
  resetAll() {
178
244
  log_debug("TVSeekController: Reset all states");
179
245
  this.currentSeekType = null;
180
- this.delayedSeekOffset = null;
181
246
 
182
- this.delayedSeekProgressSubject.next(null);
247
+ this.delayedSeekPosition = null;
248
+ this.isPlayerInitiallyPaused = null;
249
+
250
+ this.clearSeekCompleteTimeout();
251
+ this.clearLongPressThresholdTimeout();
183
252
 
184
253
  this.tapSeekController.reset();
185
254
  this.longPressSeekController.reset();
186
255
 
187
256
  this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
188
257
  }
189
-
190
- private getPlayer() {
191
- const player = playerManager.getInstanceController();
192
-
193
- if (!player) {
194
- log_warning("TVSeekController: No valid player controller found");
195
- }
196
-
197
- return player;
198
- }
199
258
  }
@@ -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;
19
-
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
- }
22
+ log_debug(`${this.title}: start tap seek type: ${seekType}.`);
30
23
 
31
- const clicksBeforePause = 0;
32
- const skipTime = getSkipTime(this.seekMultiplier);
24
+ const skipTime = this.seekIncrementalStrategy.getSkipTime();
25
+ const seekMultiplier = this.seekIncrementalStrategy.getSkipMultiplier();
33
26
 
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++;
49
- }
41
+ this.seekIncrementalStrategy.increment();
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
- }
74
- }
51
+ this.seekIncrementalStrategy.reset();
52
+ };
75
53
  }