@applicaster/zapp-react-native-utils 13.0.0-alpha.2635505531 → 13.0.0-alpha.3013227535

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 (114) 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 +68 -0
  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 +14 -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 +28 -3
  83. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +43 -96
  84. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +203 -97
  85. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +35 -55
  86. package/reactHooks/player/__tests__/{useAutoSeek.test.tsx → useAutoSeek._test.tsx} +2 -10
  87. package/reactHooks/player/const.ts +1 -5
  88. package/reactHooks/player/index.ts +0 -6
  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/reactHooks/player/useAutoSeek.tsx +0 -106
  106. package/reactHooks/player/useSeek.tsx +0 -17
  107. package/reactHooks/player/useTapSeek.tsx +0 -85
  108. /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
  109. /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
  110. /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
  111. /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
  112. /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
  113. /package/domUtils/{logger.js → logger.ts} +0 -0
  114. /package/reactHooks/player/__tests__/{useTapSeek.test.ts → useTapSeek._test.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 {
4
- onPressStart(seekType: SEEK_TYPE): void;
5
- onPressEnd?(): void;
11
+ title: string;
12
+ onStartSeek(seekType: SEEK_TYPE): 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,117 +1,64 @@
1
- // LongPressSeekController.ts
2
- import { ISeekController } from "./ISeekController";
3
- import { SEEK_TYPE } from "../const";
4
1
  import {
5
- log_debug,
6
- log_error,
7
- log_verbose,
8
- TVSeekController,
9
- } from "./TVSeekController";
10
- import { timer, interval, Subscription } from "rxjs";
11
- import { switchMap, tap } from "rxjs/operators";
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";
8
+ import { interval, Subscription } from "rxjs";
9
+ import { tap } from "rxjs/operators";
12
10
 
13
- /**
14
- * Example interface for the configuration needed for long-press seeking.
15
- */
16
11
  interface LongPressConfig {
17
- LONG_PRESS_THRESHOLD: number; // ms
18
- TIMER_INTERVAL_MS: number; // ms
19
- SKIP_INCREMENT_STEP: number; // seconds
20
- MAX_SKIP_PER_TICK: number; // seconds
21
- INITIAL_SKIP_AMOUNT: number; // e.g., 10s to start
12
+ TIMER_INTERVAL_MS: number;
22
13
  }
23
14
 
24
15
  export class LongPressSeekController implements ISeekController {
25
- private pressStartTime = 0;
26
- private isLongPressActive = false;
27
- private currentSkipAmount = 0;
28
16
  private subscription: Subscription | null = null;
29
-
17
+ readonly title = "LongPressSeek";
30
18
  constructor(
31
- private parent: TVSeekController,
19
+ private seekControllerDelegate: ISeekControllerDelegate,
20
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
32
21
  private config: LongPressConfig
33
22
  ) {}
34
23
 
35
- onPressStart(seekType: SEEK_TYPE) {
24
+ onStartSeek(seekType: SEEK_TYPE) {
36
25
  this.cleanUpSubscription();
37
26
 
38
- log_debug(
39
- `LongPressSeekController: start long press seek type:${seekType}.`
40
- );
41
-
42
- this.parent.currentSeekType = seekType;
43
- this.parent.pausePlayer();
44
-
45
- this.pressStartTime = Date.now();
46
- this.isLongPressActive = true;
47
- this.currentSkipAmount = this.config.INITIAL_SKIP_AMOUNT;
48
-
49
- /**
50
- * We wait LONG_PRESS_THRESHOLD ms to confirm it's a long press.
51
- * If it still active, we switch to an interval that emits every TIMER_INTERVAL_MS.
52
- */
53
- const threshold$ = timer(this.config.LONG_PRESS_THRESHOLD).pipe(
54
- tap(() =>
55
- log_verbose("LongPressSeekController: Crossed long-press threshold")
56
- ),
57
- switchMap(() => {
58
- return interval(this.config.TIMER_INTERVAL_MS).pipe(
59
- tap(() => {
60
- this.parent.handleDelayedSeek(
61
- this.parent.currentSeekType,
62
- this.currentSkipAmount
63
- );
64
-
65
- log_debug(
66
- `LongPressSeekController: handle delayed seek type: ${seekType} - currentSkipAmount: ${this.currentSkipAmount}.`
67
- );
68
-
69
- this.currentSkipAmount = Math.min(
70
- this.currentSkipAmount + this.config.SKIP_INCREMENT_STEP,
71
- this.config.MAX_SKIP_PER_TICK
72
- );
73
- })
74
- );
75
- })
76
- );
77
-
78
- this.subscription = threshold$.subscribe({
79
- complete: () =>
80
- log_verbose(
81
- "LongPressSeekController: Long press subscription complete"
82
- ),
83
- error: (err) =>
84
- log_error(
85
- "LongPressSeekController: Long press subscription error",
86
- err
87
- ),
88
- });
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
+ });
89
53
  }
90
54
 
91
- onPressEnd() {
92
- const pressDuration = Date.now() - this.pressStartTime;
93
-
94
- if (
95
- this.isLongPressActive &&
96
- pressDuration > this.config.LONG_PRESS_THRESHOLD
97
- ) {
98
- const offset = this.parent.delayedSeekOffset;
99
-
100
- if (offset) {
101
- log_debug(
102
- `LongPressSeekController: onPressEnd for LongPressSeekController, seek offset: ${offset}`
103
- );
104
-
105
- this.parent.seekPlayer(offset);
106
- }
107
- }
108
-
109
- this.parent.resetAll();
55
+ onSeekEnd() {
56
+ this.cleanUpSubscription();
57
+ this.seekControllerDelegate.handleSeekComplete();
110
58
  }
111
59
 
112
60
  reset() {
113
- this.isLongPressActive = false;
114
- this.currentSkipAmount = 0;
61
+ this.seekIncrementalStrategy.reset();
115
62
  this.cleanUpSubscription();
116
63
  }
117
64
 
@@ -1,9 +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";
6
+ import { Player } from "../../../appUtils/playerManager/player";
7
+ import { SeekKeyHandlerListener } from "../../../appUtils/keyInputHandler/KeyInputHandler";
8
+ import {
9
+ ISeekController,
10
+ ISeekControllerDelegate,
11
+ SeekIncrementalBaseStrategy,
12
+ } from "./ISeekController";
7
13
 
8
14
  export const { log_warning, log_debug, log_info, log_error, log_verbose } =
9
15
  createLogger({
@@ -11,142 +17,242 @@ export const { log_warning, log_debug, log_info, log_error, log_verbose } =
11
17
  subsystem: "TVSeekController",
12
18
  });
13
19
 
14
- export class TVSeekController {
15
- private static instance: TVSeekController;
16
- public static getInstance() {
17
- if (!TVSeekController.instance) {
18
- TVSeekController.instance = new TVSeekController();
19
- }
20
+ export enum SEEK_STRATEGY {
21
+ TAP_SEEK = "TAP_SEEK",
22
+ AUTO_SEEK = "AUTO_SEEK",
23
+ }
20
24
 
21
- return TVSeekController.instance;
22
- }
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
23
36
 
24
- // -- SHARED STATE ACROSS STRATEGIES --
25
- public currentSeekType: SEEK_TYPE | null = null;
26
- public delayedSeekOffset: number | null = null;
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
- // -- STRATEGY INSTANCES --
30
- private tapSeekController: TapSeekController;
31
- private longPressSeekController: LongPressSeekController;
45
+ private currentSeekController: ISeekController;
46
+ private readonly tapSeekController: TapSeekController;
47
+ private readonly longPressSeekController: LongPressSeekController;
48
+ private longPressThresholdTimeout: any | null = null;
49
+ private seekEndTimeout: any | null = null;
32
50
 
33
- // Example config for long press
34
- private config = {
35
- LONG_PRESS_THRESHOLD: 500,
36
- TIMER_INTERVAL_MS: 300,
37
- SKIP_INCREMENT_STEP: 5,
38
- MAX_SKIP_PER_TICK: 300,
39
- INITIAL_SKIP_AMOUNT: 10,
40
- };
51
+ constructor(playerController: Player) {
52
+ this.playerController = playerController;
41
53
 
42
- private constructor() {
43
- this.tapSeekController = new TapSeekController(this);
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
- getDelayedSeekProgressObservable() {
69
+ getDelayedSeekProgressObservable = () => {
52
70
  return this.delayedSeekProgressSubject;
53
- }
71
+ };
54
72
 
55
- getSeekMethods() {
56
- return {
57
- onForwardPress: () =>
58
- this.tapSeekController.onPressStart(SEEK_TYPE.FORWARD),
59
- onRewindPress: () =>
60
- this.tapSeekController.onPressStart(SEEK_TYPE.REWIND),
61
-
62
- onForwardLongPress: () => {
63
- this.longPressSeekController.onPressStart(SEEK_TYPE.FORWARD);
64
- this.tapSeekController.reset();
65
- },
66
- onRewindLongPress: () => {
67
- this.longPressSeekController.onPressStart(SEEK_TYPE.REWIND);
68
- this.tapSeekController.reset();
69
- },
70
- onForwardPressOut: () => this.longPressSeekController.onPressEnd(),
71
- onRewindPressOut: () => this.longPressSeekController.onPressEnd(),
72
- };
73
- }
73
+ setStrategy = (seekStrategy: SEEK_STRATEGY) => {
74
+ switch (seekStrategy) {
75
+ case SEEK_STRATEGY.TAP_SEEK:
76
+ this.currentSeekController = this.tapSeekController;
77
+ log_debug("TVSeekController: setStrategy - TAP_SEEK");
78
+
79
+ break;
80
+ case SEEK_STRATEGY.AUTO_SEEK:
81
+ this.currentSeekController = this.longPressSeekController;
82
+ log_debug("TVSeekController: setStrategy - AUTO_SEEK");
74
83
 
75
- // ---------------------------
76
- // SHARED METHODS
77
- // ---------------------------
84
+ break;
85
+ default:
86
+ }
87
+ };
78
88
 
79
- /** Called by either strategy to accumulate offset while paused. */
80
- handleDelayedSeek(eventType: SEEK_TYPE, skipTime: number) {
81
- const player = this.getPlayer();
82
- if (!player) return;
89
+ handleStartSeek = (seekType: SEEK_TYPE) => {
90
+ this.currentSeekType = seekType;
83
91
 
84
- // If not previously delayed, we pause the first time
85
- if (this.delayedSeekOffset === null) {
86
- this.pausePlayer();
92
+ if (this.isPlayerInitiallyPaused === null) {
93
+ this.isPlayerInitiallyPaused = this.playerController.isPaused();
87
94
  }
88
95
 
89
- const newOffset = (this.delayedSeekOffset || 0) + skipTime;
90
- const currentPos = player.getPosition();
96
+ this.clearLongPressThresholdTimeout();
97
+ this.clearSeekCompleteTimeout();
98
+ this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
99
+ this.currentSeekController.onStartSeek(seekType);
100
+
101
+ this.longPressThresholdTimeout = setTimeout(() => {
102
+ this.setStrategy(SEEK_STRATEGY.AUTO_SEEK);
103
+ this.currentSeekController.onStartSeek(seekType);
104
+ }, LONG_PRESS_THRESHOLD);
105
+ };
106
+
107
+ onForwardPress = (): boolean => {
108
+ this.handleStartSeek(SEEK_TYPE.FORWARD);
109
+
110
+ return true;
111
+ };
112
+
113
+ onRewindPress = (): boolean => {
114
+ this.handleStartSeek(SEEK_TYPE.REWIND);
115
+
116
+ return true;
117
+ };
118
+
119
+ onSeekEnd = () => {
120
+ this.clearLongPressThresholdTimeout();
121
+ this.currentSeekController.onSeekEnd();
122
+ };
123
+
124
+ onRewindPressOut = () => {
125
+ this.onSeekEnd();
126
+ };
127
+
128
+ onForwardPressOut = () => {
129
+ this.onSeekEnd();
130
+ };
131
+
132
+ onVideoProgress = (position: number) => {
133
+ if (this.delayedSeekPosition !== null) {
134
+ return;
135
+ }
136
+
137
+ this.delayedSeekProgressSubject.next(position);
138
+ };
139
+
140
+ handleDelayedSeek = (offset: number = 0) => {
141
+ this.handlePlayerPause();
142
+
143
+ const currentPos =
144
+ this.delayedSeekPosition === null
145
+ ? this.playerController.getPosition()
146
+ : this.delayedSeekPosition;
147
+
91
148
  let targetPos = currentPos;
92
149
 
93
- if (eventType === SEEK_TYPE.FORWARD) {
94
- 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);
95
157
  } else {
96
- targetPos = Math.max(0, currentPos - newOffset);
158
+ log_warning(
159
+ `TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}`
160
+ );
97
161
  }
98
162
 
99
- this.delayedSeekOffset = newOffset;
100
-
101
163
  log_debug(
102
- `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, offset: ${newOffset}, skipTime: ${skipTime}`
164
+ `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}`
103
165
  );
104
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;
105
178
  this.delayedSeekProgressSubject.next(targetPos);
106
- }
179
+ };
107
180
 
108
- seekPlayer(offset: number) {
109
- const player = this.getPlayer();
110
- if (!player || !this.currentSeekType) return;
111
- player[this.currentSeekType](offset);
112
- this.playerPlay();
113
- }
181
+ handleSeekTo = (newPosition: number) => {
182
+ this.playerController.seekTo(newPosition);
183
+
184
+ if (!this.isPlayerInitiallyPaused) {
185
+ this.playerController.play();
186
+ }
187
+ };
114
188
 
115
- playerPlay() {
116
- const player = this.getPlayer();
117
- if (!player) return;
189
+ handleSeek = (offset: number) => {
190
+ this.playerController[this.currentSeekType](offset);
118
191
 
119
- if (player.isPaused()) {
120
- player.play();
192
+ if (!this.isPlayerInitiallyPaused) {
193
+ this.playerController.play();
121
194
  }
122
- }
195
+ };
123
196
 
124
- pausePlayer() {
125
- const player = this.getPlayer();
126
- if (!player) return;
127
- player.pause();
128
- }
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
+ };
129
242
 
130
- /**
131
- * Reset *all* relevant state and strategy controllers
132
- */
133
243
  resetAll() {
134
244
  log_debug("TVSeekController: Reset all states");
135
245
  this.currentSeekType = null;
136
- this.delayedSeekOffset = null;
137
- this.delayedSeekProgressSubject.next(null);
138
246
 
139
- this.tapSeekController.reset();
140
- this.longPressSeekController.reset();
141
- }
247
+ this.delayedSeekPosition = null;
248
+ this.isPlayerInitiallyPaused = null;
142
249
 
143
- private getPlayer() {
144
- const player = playerManager.getInstanceController();
250
+ this.clearSeekCompleteTimeout();
251
+ this.clearLongPressThresholdTimeout();
145
252
 
146
- if (!player) {
147
- log_warning("TVSeekController: No valid player controller found");
148
- }
253
+ this.tapSeekController.reset();
254
+ this.longPressSeekController.reset();
149
255
 
150
- return player;
256
+ this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
151
257
  }
152
258
  }