@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.
- package/README.md +0 -10
- 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/contextKeysManager/index.ts +2 -2
- package/appUtils/contextKeysManager/utils/index.ts +2 -1
- 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 +5 -4
- 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 +35 -236
- 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/useProfilerLogging.ts +15 -0
- package/reactHooks/navigation/useRoute.ts +100 -0
- package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
- package/reactHooks/player/TVSeekControlller/ISeekController.ts +27 -2
- package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +23 -61
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +143 -48
- package/reactHooks/player/TVSeekControlller/TapSeekController.ts +29 -51
- package/reactHooks/player/__tests__/useAutoSeek._test.tsx +115 -0
- package/reactHooks/player/__tests__/useTapSeek._test.ts +94 -0
- package/reactHooks/player/const.ts +1 -5
- 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__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
- package/reactUtils/__tests__/reactUtils.test.js +10 -28
- package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
- package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +13 -79
- package/reactUtils/createContext/__tests__/__snapshots__/index.test.js.snap +2 -2
- package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
- package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
- package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
- package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
- package/reactUtils/createContext/__tests__/provider.test.js +4 -6
- package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
- package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
- package/reactUtils/index.ts +0 -14
- package/screenState/index.ts +24 -10
- package/testUtils/index.tsx +0 -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,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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
49
|
-
this.
|
|
50
|
-
this.currentSkipAmount
|
|
34
|
+
this.seekControllerDelegate.handleDelayedSeek(
|
|
35
|
+
this.seekIncrementalStrategy.getSkipTime()
|
|
51
36
|
);
|
|
52
37
|
|
|
53
38
|
log_debug(
|
|
54
|
-
|
|
39
|
+
`${
|
|
40
|
+
this.title
|
|
41
|
+
}: handle delayed seek type: ${seekType} - multiplier: ${this.seekIncrementalStrategy.getSkipMultiplier()}, currentSkipAmount: ${this.seekIncrementalStrategy.getSkipTime()}.`
|
|
55
42
|
);
|
|
56
43
|
|
|
57
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
107
|
-
if (this.
|
|
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(
|
|
117
|
-
|
|
118
|
-
|
|
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 (
|
|
153
|
+
if (this.currentSeekType === SEEK_TYPE.FORWARD) {
|
|
126
154
|
targetPos = Math.min(
|
|
127
|
-
currentPos +
|
|
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
|
-
|
|
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},
|
|
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
|
-
|
|
194
|
+
handleSeek = (offset: number) => {
|
|
144
195
|
this.playerController[this.currentSeekType](offset);
|
|
145
|
-
this.playerPlay();
|
|
146
|
-
}
|
|
147
196
|
|
|
148
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
17
|
-
|
|
18
|
-
const prevSeekType = this.parent.currentSeekType;
|
|
22
|
+
log_debug(`${this.title}: start tap seek type: ${seekType}.`);
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
`${this.title}: handle delayed seek type:${seekType} - skipTime: ${skipTime}, seekMultiplier: ${seekMultiplier}.`
|
|
37
30
|
);
|
|
38
31
|
|
|
39
|
-
this.
|
|
32
|
+
this.seekControllerDelegate.handleDelayedSeek(skipTime);
|
|
40
33
|
} else {
|
|
41
34
|
log_debug(
|
|
42
|
-
|
|
35
|
+
`${this.title}: handle seek type:${seekType} - skipTime: ${skipTime}.`
|
|
43
36
|
);
|
|
44
37
|
|
|
45
|
-
this.
|
|
38
|
+
this.seekControllerDelegate.handleSeek(skipTime);
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
this.
|
|
41
|
+
this.seekIncrementalStrategy.increment();
|
|
49
42
|
};
|
|
50
43
|
|
|
51
44
|
onSeekEnd = () => {
|
|
52
|
-
this.
|
|
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(
|
|
67
|
-
|
|
68
|
-
this.seekMultiplier = 1;
|
|
49
|
+
log_debug(`${this.title}: Reset states`);
|
|
69
50
|
|
|
70
|
-
|
|
71
|
-
clearTimeout(this.tapSequenceTimeout);
|
|
72
|
-
this.tapSequenceTimeout = null;
|
|
73
|
-
}
|
|
51
|
+
this.seekIncrementalStrategy.reset();
|
|
74
52
|
};
|
|
75
53
|
}
|