@applicaster/zapp-react-native-utils 13.0.0-alpha.4515349201 → 13.0.0-alpha.4528460103
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.
- package/actionsExecutor/ActionExecutor.ts +16 -9
- package/actionsExecutor/ActionExecutorContext.tsx +74 -9
- package/alertUtils/alert.ts +148 -0
- package/alertUtils/index.ts +1 -148
- package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
- package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
- package/analyticsUtils/helpers/hooks.ts +1 -1
- package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
- package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +10 -8
- package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +693 -0
- package/appUtils/HooksManager/__tests__/hook.test.js +196 -184
- package/appUtils/HooksManager/__tests__/hooksManager.test.js +96 -99
- package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
- package/appUtils/HooksManager/{index.js → index.ts} +121 -112
- package/appUtils/HooksManager/types.ts +119 -0
- package/appUtils/RiverFocusManager/index.js +17 -12
- package/appUtils/accessibilityManager/index.ts +42 -1
- package/appUtils/contextKeysManager/contextResolver.ts +77 -0
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -6
- package/appUtils/focusManager/index.ts +100 -65
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +25 -3
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +1 -3
- package/appUtils/focusManager/utils/enums.ts +4 -0
- package/appUtils/focusManagerAux/utils/index.ts +4 -3
- package/appUtils/{index.js → index.ts} +1 -1
- package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
- package/appUtils/keyCodes/__tests__/keyCodes.test.js +4 -4
- package/appUtils/keyCodes/{index.js → index.ts} +2 -0
- package/appUtils/keyCodes/keys/index.ts +3 -0
- package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
- package/appUtils/keyCodes/keys/keys.android.ts +18 -0
- package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
- package/appUtils/keyCodes/keys/keys.ts +15 -0
- package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +3 -3
- package/appUtils/keyCodes/keysByPlatform.ts +8 -0
- package/appUtils/orientationHelper.ts +1 -1
- package/appUtils/platform/const.ts +14 -0
- package/appUtils/platform/platformUtils.ts +47 -18
- package/appUtils/playerManager/player.ts +2 -2
- package/appUtils/playerManager/playerNative.ts +1 -1
- package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
- package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
- package/equalUtils/index.ts +10 -3
- package/focusManager/FocusManager.ts +11 -1
- package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
- package/functionUtils/index.ts +15 -11
- package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
- package/idleUtils/index.ts +1 -1
- package/idleUtils/withTimeout.ts +15 -2
- package/logger/const.ts +9 -67
- package/manifestUtils/__tests__/createConfig.test.js +16 -16
- package/modalState/index.ts +1 -5
- package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +6 -6
- package/navigationUtils/__tests__/itemTypes.test.js +2 -2
- package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
- package/navigationUtils/index.ts +17 -8
- package/package.json +3 -2
- package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -1
- package/reactHooks/cell-click/__tests__/index.test.js +10 -2
- package/reactHooks/cell-click/index.ts +7 -1
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
- package/reactHooks/feed/useFeedLoader.tsx +1 -1
- package/reactHooks/feed/usePipesCacheReset.ts +5 -5
- package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
- package/reactHooks/layout/__tests__/index.test.tsx +10 -10
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
- package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +0 -4
- package/reactHooks/navigation/index.ts +29 -218
- package/reactHooks/navigation/useGetBottomTabBarHeight.ts +12 -0
- package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
- package/reactHooks/navigation/useGetTabBarHeight.ts +10 -0
- package/reactHooks/navigation/useIsScreenActive.ts +17 -0
- package/reactHooks/navigation/useNavigationPluginData.ts +15 -0
- package/reactHooks/navigation/useNavigationType.ts +15 -0
- package/reactHooks/navigation/usePathname.ts +4 -0
- package/reactHooks/navigation/useRoute.ts +100 -0
- package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
- package/reactHooks/player/TVSeekControlller/ISeekController.ts +19 -2
- package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +20 -41
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +80 -31
- package/reactHooks/player/TVSeekControlller/TapSeekController.ts +18 -40
- package/reactHooks/player/__tests__/useAutoSeek._test.tsx +115 -0
- package/reactHooks/player/__tests__/useTapSeek._test.ts +94 -0
- package/reactHooks/player/const.ts +1 -1
- package/reactHooks/state/useRefWithInitialValue.ts +14 -0
- package/reactHooks/state/useStateFromSubscribe.ts +23 -0
- package/reactHooks/usePluginConfiguration.ts +1 -1
- package/reactHooks/useSubscriberFor.ts +4 -3
- package/reactUtils/__tests__/reactUtils.test.js +10 -28
- package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +19 -19
- package/reactUtils/createContext/__tests__/__snapshots__/index.test.js.snap +2 -2
- package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -3
- package/reactUtils/createContext/__tests__/helpers.test.js +8 -8
- package/reactUtils/index.ts +0 -14
- package/screenState/index.ts +24 -10
- package/theme/__mocks__/index.ts +4 -0
- package/theme/index.ts +1 -1
- package/time/Timer.ts +5 -2
- package/types.ts +1 -0
- package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
- package/appUtils/keyCodes/keys.ios.js +0 -46
- /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
- /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
- /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
- /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
- /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
- /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,15 +1,32 @@
|
|
|
1
|
-
import { SEEK_TYPE } from "../const";
|
|
1
|
+
import { MAX_SEEK_TIME, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
|
|
2
2
|
|
|
3
3
|
export interface ISeekControllerDelegate {
|
|
4
4
|
handleDelayedSeek(offset: number): void;
|
|
5
5
|
handleSeek(offset: number): void;
|
|
6
6
|
handleSeekComplete(): void;
|
|
7
7
|
handlePlayerPause(): void;
|
|
8
|
-
delayedSeekOffset: number | null;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export interface ISeekController {
|
|
11
|
+
title: string;
|
|
12
12
|
onStartSeek(seekType: SEEK_TYPE): void;
|
|
13
13
|
onSeekEnd(): void;
|
|
14
14
|
reset(): void;
|
|
15
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,85 +1,64 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ISeekController,
|
|
3
|
+
ISeekControllerDelegate,
|
|
4
|
+
SeekIncrementalBaseStrategy,
|
|
5
|
+
} from "./ISeekController";
|
|
2
6
|
import { SEEK_TYPE } from "../const";
|
|
3
7
|
import { log_debug, log_error, log_verbose } from "./TVSeekController";
|
|
4
8
|
import { interval, Subscription } from "rxjs";
|
|
5
9
|
import { tap } from "rxjs/operators";
|
|
6
10
|
|
|
7
11
|
interface LongPressConfig {
|
|
8
|
-
TIMER_INTERVAL_MS: number;
|
|
9
|
-
SKIP_INCREMENT_STEP: number; // seconds
|
|
10
|
-
MAX_SKIP_PER_TICK: number; // seconds
|
|
11
|
-
INITIAL_SKIP_AMOUNT: number; // e.g., 10s to start
|
|
12
|
+
TIMER_INTERVAL_MS: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export class LongPressSeekController implements ISeekController {
|
|
15
|
-
isLongPressActive = false;
|
|
16
|
-
private currentSkipAmount = 0;
|
|
17
16
|
private subscription: Subscription | null = null;
|
|
18
|
-
|
|
17
|
+
readonly title = "LongPressSeek";
|
|
19
18
|
constructor(
|
|
20
19
|
private seekControllerDelegate: ISeekControllerDelegate,
|
|
20
|
+
private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
|
|
21
21
|
private config: LongPressConfig
|
|
22
22
|
) {}
|
|
23
23
|
|
|
24
24
|
onStartSeek(seekType: SEEK_TYPE) {
|
|
25
25
|
this.cleanUpSubscription();
|
|
26
26
|
|
|
27
|
-
log_debug(
|
|
28
|
-
`LongPressSeekController: start long press seek type:${seekType}.`
|
|
29
|
-
);
|
|
27
|
+
log_debug(`${this.title}: start long press seek type: ${seekType}.`);
|
|
30
28
|
|
|
31
29
|
this.seekControllerDelegate.handlePlayerPause();
|
|
32
30
|
|
|
33
|
-
this.isLongPressActive = true;
|
|
34
|
-
this.currentSkipAmount = this.config.INITIAL_SKIP_AMOUNT;
|
|
35
|
-
|
|
36
31
|
this.subscription = interval(this.config.TIMER_INTERVAL_MS)
|
|
37
32
|
.pipe(
|
|
38
33
|
tap(() => {
|
|
39
|
-
this.seekControllerDelegate.handleDelayedSeek(
|
|
34
|
+
this.seekControllerDelegate.handleDelayedSeek(
|
|
35
|
+
this.seekIncrementalStrategy.getSkipTime()
|
|
36
|
+
);
|
|
40
37
|
|
|
41
38
|
log_debug(
|
|
42
|
-
|
|
39
|
+
`${
|
|
40
|
+
this.title
|
|
41
|
+
}: handle delayed seek type: ${seekType} - multiplier: ${this.seekIncrementalStrategy.getSkipMultiplier()}, currentSkipAmount: ${this.seekIncrementalStrategy.getSkipTime()}.`
|
|
43
42
|
);
|
|
44
43
|
|
|
45
|
-
this.
|
|
46
|
-
this.currentSkipAmount + this.config.SKIP_INCREMENT_STEP,
|
|
47
|
-
this.config.MAX_SKIP_PER_TICK
|
|
48
|
-
);
|
|
44
|
+
this.seekIncrementalStrategy.increment();
|
|
49
45
|
})
|
|
50
46
|
)
|
|
51
47
|
.subscribe({
|
|
52
48
|
complete: () =>
|
|
53
|
-
log_verbose(
|
|
54
|
-
"LongPressSeekController: Long press subscription complete"
|
|
55
|
-
),
|
|
49
|
+
log_verbose(`${this.title}: Long press subscription complete`),
|
|
56
50
|
error: (err) =>
|
|
57
|
-
log_error(
|
|
58
|
-
"LongPressSeekController: Long press subscription error",
|
|
59
|
-
err
|
|
60
|
-
),
|
|
51
|
+
log_error(`${this.title}: Long press subscription error`, err),
|
|
61
52
|
});
|
|
62
53
|
}
|
|
63
54
|
|
|
64
55
|
onSeekEnd() {
|
|
65
|
-
|
|
66
|
-
const offset = this.seekControllerDelegate.delayedSeekOffset;
|
|
67
|
-
|
|
68
|
-
if (offset !== null) {
|
|
69
|
-
log_debug(
|
|
70
|
-
`LongPressSeekController: onPressEnd for LongPressSeekController, seek offset: ${offset}`
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
this.seekControllerDelegate.handleSeek(offset);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
56
|
+
this.cleanUpSubscription();
|
|
77
57
|
this.seekControllerDelegate.handleSeekComplete();
|
|
78
58
|
}
|
|
79
59
|
|
|
80
60
|
reset() {
|
|
81
|
-
this.
|
|
82
|
-
this.currentSkipAmount = 0;
|
|
61
|
+
this.seekIncrementalStrategy.reset();
|
|
83
62
|
this.cleanUpSubscription();
|
|
84
63
|
}
|
|
85
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 {
|
|
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({
|
|
@@ -23,17 +27,16 @@ const CONFIG_TAP_SEEK = {
|
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
const CONFIG_LONG_PRESS = {
|
|
26
|
-
TIMER_INTERVAL_MS:
|
|
27
|
-
SKIP_INCREMENT_STEP: 5,
|
|
28
|
-
MAX_SKIP_PER_TICK: 300,
|
|
29
|
-
INITIAL_SKIP_AMOUNT: 10,
|
|
30
|
+
TIMER_INTERVAL_MS: 200,
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
const LONG_PRESS_THRESHOLD = 500;
|
|
34
|
+
const SEEK_END_TIMEOUT_DURATION = 700;
|
|
35
|
+
// prettier-ignore
|
|
33
36
|
|
|
34
37
|
export class TVSeekController
|
|
35
38
|
implements SeekKeyHandlerListener, ISeekControllerDelegate {
|
|
36
|
-
public
|
|
39
|
+
public delayedSeekPosition: number | null = null;
|
|
37
40
|
private readonly playerController: Player;
|
|
38
41
|
private delayedSeekProgressSubject = new BehaviorSubject<number | null>(null);
|
|
39
42
|
isPlayerInitiallyPaused: null | boolean = null;
|
|
@@ -43,14 +46,20 @@ export class TVSeekController
|
|
|
43
46
|
private readonly tapSeekController: TapSeekController;
|
|
44
47
|
private readonly longPressSeekController: LongPressSeekController;
|
|
45
48
|
private longPressThresholdTimeout: any | null = null;
|
|
49
|
+
private seekEndTimeout: any | null = null;
|
|
46
50
|
|
|
47
51
|
constructor(playerController: Player) {
|
|
48
52
|
this.playerController = playerController;
|
|
49
53
|
|
|
50
|
-
this.tapSeekController = new TapSeekController(
|
|
54
|
+
this.tapSeekController = new TapSeekController(
|
|
55
|
+
this,
|
|
56
|
+
new SeekIncrementalBaseStrategy(),
|
|
57
|
+
CONFIG_TAP_SEEK
|
|
58
|
+
);
|
|
51
59
|
|
|
52
60
|
this.longPressSeekController = new LongPressSeekController(
|
|
53
61
|
this,
|
|
62
|
+
new SeekIncrementalBaseStrategy(),
|
|
54
63
|
CONFIG_LONG_PRESS
|
|
55
64
|
);
|
|
56
65
|
|
|
@@ -64,30 +73,28 @@ export class TVSeekController
|
|
|
64
73
|
setStrategy = (seekStrategy: SEEK_STRATEGY) => {
|
|
65
74
|
switch (seekStrategy) {
|
|
66
75
|
case SEEK_STRATEGY.TAP_SEEK:
|
|
67
|
-
clearTimeout(this.longPressThresholdTimeout);
|
|
68
|
-
this.longPressThresholdTimeout = null;
|
|
69
76
|
this.currentSeekController = this.tapSeekController;
|
|
77
|
+
log_debug("TVSeekController: setStrategy - TAP_SEEK");
|
|
70
78
|
|
|
71
79
|
break;
|
|
72
80
|
case SEEK_STRATEGY.AUTO_SEEK:
|
|
73
|
-
this.currentSeekController.reset();
|
|
74
81
|
this.currentSeekController = this.longPressSeekController;
|
|
82
|
+
log_debug("TVSeekController: setStrategy - AUTO_SEEK");
|
|
83
|
+
|
|
75
84
|
break;
|
|
76
85
|
default:
|
|
77
86
|
}
|
|
78
87
|
};
|
|
79
88
|
|
|
80
89
|
handleStartSeek = (seekType: SEEK_TYPE) => {
|
|
81
|
-
if (this.currentSeekType !== seekType) {
|
|
82
|
-
this.resetAll();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
90
|
this.currentSeekType = seekType;
|
|
86
91
|
|
|
87
92
|
if (this.isPlayerInitiallyPaused === null) {
|
|
88
93
|
this.isPlayerInitiallyPaused = this.playerController.isPaused();
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
this.clearLongPressThresholdTimeout();
|
|
97
|
+
this.clearSeekCompleteTimeout();
|
|
91
98
|
this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
|
|
92
99
|
this.currentSeekController.onStartSeek(seekType);
|
|
93
100
|
|
|
@@ -110,8 +117,8 @@ export class TVSeekController
|
|
|
110
117
|
};
|
|
111
118
|
|
|
112
119
|
onSeekEnd = () => {
|
|
120
|
+
this.clearLongPressThresholdTimeout();
|
|
113
121
|
this.currentSeekController.onSeekEnd();
|
|
114
|
-
this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
|
|
115
122
|
};
|
|
116
123
|
|
|
117
124
|
onRewindPressOut = () => {
|
|
@@ -122,40 +129,39 @@ export class TVSeekController
|
|
|
122
129
|
this.onSeekEnd();
|
|
123
130
|
};
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
if (this.
|
|
132
|
+
onVideoProgress = (position: number) => {
|
|
133
|
+
if (this.delayedSeekPosition !== null) {
|
|
127
134
|
return;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
// TODO: If 1 second after seek, send
|
|
131
|
-
|
|
132
137
|
this.delayedSeekProgressSubject.next(position);
|
|
133
138
|
};
|
|
134
139
|
|
|
135
|
-
handleDelayedSeek = (offset: number) => {
|
|
140
|
+
handleDelayedSeek = (offset: number = 0) => {
|
|
136
141
|
this.handlePlayerPause();
|
|
137
142
|
|
|
138
|
-
const
|
|
139
|
-
|
|
143
|
+
const currentPos =
|
|
144
|
+
this.delayedSeekPosition === null
|
|
145
|
+
? this.playerController.getPosition()
|
|
146
|
+
: this.delayedSeekPosition;
|
|
147
|
+
|
|
140
148
|
let targetPos = currentPos;
|
|
141
149
|
|
|
142
150
|
if (this.currentSeekType === SEEK_TYPE.FORWARD) {
|
|
143
151
|
targetPos = Math.min(
|
|
144
|
-
currentPos +
|
|
152
|
+
currentPos + offset,
|
|
145
153
|
this.playerController.getSeekableDuration()
|
|
146
154
|
);
|
|
147
155
|
} else if (this.currentSeekType === SEEK_TYPE.REWIND) {
|
|
148
|
-
targetPos = Math.max(0, currentPos -
|
|
156
|
+
targetPos = Math.max(0, currentPos - offset);
|
|
149
157
|
} else {
|
|
150
158
|
log_warning(
|
|
151
159
|
`TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}`
|
|
152
160
|
);
|
|
153
161
|
}
|
|
154
162
|
|
|
155
|
-
this.delayedSeekOffset = newOffset;
|
|
156
|
-
|
|
157
163
|
log_debug(
|
|
158
|
-
`TVSeekController: handleDelayedSeek - targetPos: ${targetPos},
|
|
164
|
+
`TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}`
|
|
159
165
|
);
|
|
160
166
|
|
|
161
167
|
const prevValue = this.delayedSeekProgressSubject.getValue();
|
|
@@ -168,9 +174,18 @@ export class TVSeekController
|
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
176
|
|
|
177
|
+
this.delayedSeekPosition = targetPos;
|
|
171
178
|
this.delayedSeekProgressSubject.next(targetPos);
|
|
172
179
|
};
|
|
173
180
|
|
|
181
|
+
handleSeekTo = (newPosition: number) => {
|
|
182
|
+
this.playerController.seekTo(newPosition);
|
|
183
|
+
|
|
184
|
+
if (!this.isPlayerInitiallyPaused) {
|
|
185
|
+
this.playerController.play();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
174
189
|
handleSeek = (offset: number) => {
|
|
175
190
|
this.playerController[this.currentSeekType](offset);
|
|
176
191
|
|
|
@@ -180,11 +195,23 @@ export class TVSeekController
|
|
|
180
195
|
};
|
|
181
196
|
|
|
182
197
|
handleSeekComplete = () => {
|
|
183
|
-
this.
|
|
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);
|
|
184
211
|
};
|
|
185
212
|
|
|
186
|
-
|
|
187
|
-
return this.
|
|
213
|
+
isSeeking = (): boolean => {
|
|
214
|
+
return this.delayedSeekPosition !== null;
|
|
188
215
|
};
|
|
189
216
|
|
|
190
217
|
handlePlayerPause = () => {
|
|
@@ -195,12 +222,34 @@ export class TVSeekController
|
|
|
195
222
|
this.playerController.pause();
|
|
196
223
|
};
|
|
197
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
|
+
};
|
|
242
|
+
|
|
198
243
|
resetAll() {
|
|
199
244
|
log_debug("TVSeekController: Reset all states");
|
|
200
245
|
this.currentSeekType = null;
|
|
201
|
-
|
|
246
|
+
|
|
247
|
+
this.delayedSeekPosition = null;
|
|
202
248
|
this.isPlayerInitiallyPaused = null;
|
|
203
249
|
|
|
250
|
+
this.clearSeekCompleteTimeout();
|
|
251
|
+
this.clearLongPressThresholdTimeout();
|
|
252
|
+
|
|
204
253
|
this.tapSeekController.reset();
|
|
205
254
|
this.longPressSeekController.reset();
|
|
206
255
|
|
|
@@ -1,75 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ISeekController,
|
|
3
|
+
ISeekControllerDelegate,
|
|
4
|
+
SeekIncrementalBaseStrategy,
|
|
5
|
+
} from "./ISeekController";
|
|
6
|
+
import { SEEK_TYPE } from "../const";
|
|
3
7
|
import { log_debug } from "./TVSeekController";
|
|
4
8
|
|
|
5
9
|
interface TapPressConfig {
|
|
6
10
|
CLICKS_BEFORE_PAUSE: number;
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
const getSkipTime = (multiplier: number) =>
|
|
10
|
-
Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
|
|
11
|
-
|
|
12
13
|
export class TapSeekController implements ISeekController {
|
|
13
|
-
|
|
14
|
-
private seekMultiplier = 1;
|
|
15
|
-
private readonly ON_PRESS_RESET_DURATION = 500; // ms
|
|
16
|
-
|
|
14
|
+
readonly title = "TapSeek";
|
|
17
15
|
constructor(
|
|
18
16
|
private seekControllerDelegate: ISeekControllerDelegate,
|
|
17
|
+
private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
|
|
19
18
|
private config: TapPressConfig
|
|
20
19
|
) {}
|
|
21
20
|
|
|
22
21
|
onStartSeek = (seekType: SEEK_TYPE) => {
|
|
23
|
-
log_debug(
|
|
22
|
+
log_debug(`${this.title}: start tap seek type: ${seekType}.`);
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.tapSequenceTimeout = null;
|
|
28
|
-
}
|
|
24
|
+
const skipTime = this.seekIncrementalStrategy.getSkipTime();
|
|
25
|
+
const seekMultiplier = this.seekIncrementalStrategy.getSkipMultiplier();
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (this.seekMultiplier > this.config.CLICKS_BEFORE_PAUSE) {
|
|
27
|
+
if (seekMultiplier > this.config.CLICKS_BEFORE_PAUSE) {
|
|
33
28
|
log_debug(
|
|
34
|
-
|
|
29
|
+
`${this.title}: handle delayed seek type:${seekType} - skipTime: ${skipTime}, seekMultiplier: ${seekMultiplier}.`
|
|
35
30
|
);
|
|
36
31
|
|
|
37
32
|
this.seekControllerDelegate.handleDelayedSeek(skipTime);
|
|
38
33
|
} else {
|
|
39
34
|
log_debug(
|
|
40
|
-
|
|
35
|
+
`${this.title}: handle seek type:${seekType} - skipTime: ${skipTime}.`
|
|
41
36
|
);
|
|
42
37
|
|
|
43
38
|
this.seekControllerDelegate.handleSeek(skipTime);
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
this.
|
|
41
|
+
this.seekIncrementalStrategy.increment();
|
|
47
42
|
};
|
|
48
43
|
|
|
49
44
|
onSeekEnd = () => {
|
|
50
|
-
this.
|
|
51
|
-
if (this.seekControllerDelegate.delayedSeekOffset !== null) {
|
|
52
|
-
this.seekControllerDelegate.handleSeek(
|
|
53
|
-
this.seekControllerDelegate.delayedSeekOffset
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
log_debug(
|
|
58
|
-
`TapSeekController: Tap sequence timeout fired, delayedSeekOffset: ${this.seekControllerDelegate.delayedSeekOffset}.`
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
this.seekControllerDelegate.handleSeekComplete();
|
|
62
|
-
}, this.ON_PRESS_RESET_DURATION);
|
|
45
|
+
this.seekControllerDelegate.handleSeekComplete();
|
|
63
46
|
};
|
|
64
47
|
|
|
65
48
|
reset = () => {
|
|
66
|
-
log_debug("
|
|
49
|
+
log_debug("{this.title}: Reset states");
|
|
67
50
|
|
|
68
|
-
this.
|
|
69
|
-
|
|
70
|
-
if (this.tapSequenceTimeout) {
|
|
71
|
-
clearTimeout(this.tapSequenceTimeout);
|
|
72
|
-
this.tapSequenceTimeout = null;
|
|
73
|
-
}
|
|
51
|
+
this.seekIncrementalStrategy.reset();
|
|
74
52
|
};
|
|
75
53
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
|
|
3
|
+
|
|
4
|
+
import { ON_HOLD_INTERVAL, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
|
|
5
|
+
|
|
6
|
+
import { useAutoSeek } from "../useAutoSeek";
|
|
7
|
+
|
|
8
|
+
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
9
|
+
|
|
10
|
+
const rewind = jest.fn();
|
|
11
|
+
const forward = jest.fn();
|
|
12
|
+
|
|
13
|
+
jest
|
|
14
|
+
.spyOn(playerManager, "getInstanceController")
|
|
15
|
+
.mockReturnValue({ rewind, forward });
|
|
16
|
+
|
|
17
|
+
describe("useAutoSeek", () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
rewind.mockReset();
|
|
20
|
+
forward.mockReset();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("autoSeekStart", () => {
|
|
24
|
+
it("should call forward method on playerInstance", () => {
|
|
25
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
26
|
+
|
|
27
|
+
act(() => {
|
|
28
|
+
result.current.autoSeekStart(SEEK_TYPE.FORWARD);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(forward).toBeCalled();
|
|
32
|
+
result.current.autoSeekStop();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should call rewind method on playerManager", () => {
|
|
36
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
37
|
+
|
|
38
|
+
act(() => {
|
|
39
|
+
result.current.autoSeekStart(SEEK_TYPE.REWIND);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(rewind).toBeCalled();
|
|
43
|
+
result.current.autoSeekStop();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should call rewind with default skip time", () => {
|
|
47
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
48
|
+
|
|
49
|
+
act(() => {
|
|
50
|
+
result.current.autoSeekStart(SEEK_TYPE.REWIND);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(rewind).toBeCalledWith(SKIP_TIME_BASE);
|
|
54
|
+
result.current.autoSeekStop();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should call forward with default skip time", () => {
|
|
58
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
59
|
+
|
|
60
|
+
act(() => {
|
|
61
|
+
result.current.autoSeekStart(SEEK_TYPE.FORWARD);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(forward).toBeCalledWith(SKIP_TIME_BASE);
|
|
65
|
+
result.current.autoSeekStop();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should call rewind in intervals set in configuration", () => {
|
|
69
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
70
|
+
|
|
71
|
+
act(() => {
|
|
72
|
+
result.current.autoSeekStart(SEEK_TYPE.REWIND);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(rewind).toBeCalled();
|
|
76
|
+
jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
|
|
77
|
+
expect(rewind).toBeCalledTimes(4);
|
|
78
|
+
result.current.autoSeekStop();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should call forward in intervals set in configuration", () => {
|
|
82
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
83
|
+
|
|
84
|
+
act(() => {
|
|
85
|
+
result.current.autoSeekStart(SEEK_TYPE.FORWARD);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(forward).toBeCalled();
|
|
89
|
+
jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
|
|
90
|
+
expect(forward).toBeCalledTimes(4);
|
|
91
|
+
result.current.autoSeekStop();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("autoSeekStop", () => {
|
|
96
|
+
it("should stop calling playerManager method after autoSeekStop is called", () => {
|
|
97
|
+
const { result } = renderHook(() => useAutoSeek());
|
|
98
|
+
|
|
99
|
+
act(() => {
|
|
100
|
+
result.current.autoSeekStart(SEEK_TYPE.FORWARD);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(forward).toBeCalled();
|
|
104
|
+
jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
|
|
105
|
+
expect(forward).toBeCalledTimes(4);
|
|
106
|
+
|
|
107
|
+
act(() => {
|
|
108
|
+
result.current.autoSeekStop();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
|
|
112
|
+
expect(forward).toBeCalledTimes(4);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|