@applicaster/zapp-react-native-utils 13.0.0-rc.2 → 13.0.0-rc.21
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 +73 -0
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +6 -6
- package/appUtils/focusManager/index.ts +90 -63
- 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} +4 -0
- package/appUtils/keyCodes/keysByPlatform.ts +8 -0
- package/appUtils/keyInputHandler/KeyInputHandler.ts +68 -0
- package/appUtils/orientationHelper.ts +1 -1
- package/appUtils/platform/const.ts +14 -0
- package/appUtils/platform/platformUtils.ts +17 -15
- package/appUtils/playerManager/index.ts +6 -2
- package/appUtils/playerManager/player.ts +17 -5
- package/appUtils/playerManager/playerFactory.ts +5 -4
- package/appUtils/playerManager/playerNative.ts +3 -2
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +3 -3
- package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
- package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
- 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 +18 -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 +14 -5
- 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 +20 -13
- package/reactHooks/feed/useFeedLoader.tsx +12 -8
- 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/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 +12 -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 +32 -0
- package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +71 -0
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +258 -0
- package/reactHooks/player/TVSeekControlller/TapSeekController.ts +53 -0
- package/reactHooks/player/__tests__/{useAutoSeek.test.tsx → useAutoSeek._test.tsx} +2 -3
- package/reactHooks/player/const.ts +0 -4
- package/reactHooks/player/index.ts +0 -6
- package/reactHooks/utils/index.ts +13 -0
- 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/theme/__mocks__/index.ts +4 -0
- package/time/Timer.ts +1 -1
- package/types.ts +1 -0
- package/appUtils/keyCodes/keys.ios.js +0 -46
- package/reactHooks/player/useAutoSeek.tsx +0 -87
- package/reactHooks/player/useSeek.tsx +0 -66
- package/reactHooks/player/useTapSeek.tsx +0 -58
- /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
- /package/reactHooks/player/__tests__/{useTapSeek.test.ts → useTapSeek._test.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
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ISeekController {
|
|
11
|
+
title: string;
|
|
12
|
+
onStartSeek(seekType: SEEK_TYPE): void;
|
|
13
|
+
onSeekEnd(): void;
|
|
14
|
+
reset(): void;
|
|
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
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
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";
|
|
10
|
+
|
|
11
|
+
interface LongPressConfig {
|
|
12
|
+
TIMER_INTERVAL_MS: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class LongPressSeekController implements ISeekController {
|
|
16
|
+
private subscription: Subscription | null = null;
|
|
17
|
+
readonly title = "LongPressSeek";
|
|
18
|
+
constructor(
|
|
19
|
+
private seekControllerDelegate: ISeekControllerDelegate,
|
|
20
|
+
private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
|
|
21
|
+
private config: LongPressConfig
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
onStartSeek(seekType: SEEK_TYPE) {
|
|
25
|
+
this.cleanUpSubscription();
|
|
26
|
+
|
|
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
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onSeekEnd() {
|
|
56
|
+
this.cleanUpSubscription();
|
|
57
|
+
this.seekControllerDelegate.handleSeekComplete();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
reset() {
|
|
61
|
+
this.seekIncrementalStrategy.reset();
|
|
62
|
+
this.cleanUpSubscription();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private cleanUpSubscription() {
|
|
66
|
+
if (this.subscription) {
|
|
67
|
+
this.subscription.unsubscribe();
|
|
68
|
+
this.subscription = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { TapSeekController } from "./TapSeekController";
|
|
3
|
+
import { LongPressSeekController } from "./LongPressSeekController";
|
|
4
|
+
import { SEEK_TYPE } from "../const";
|
|
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";
|
|
13
|
+
|
|
14
|
+
export const { log_warning, log_debug, log_info, log_error, log_verbose } =
|
|
15
|
+
createLogger({
|
|
16
|
+
category: "General",
|
|
17
|
+
subsystem: "TVSeekController",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export enum SEEK_STRATEGY {
|
|
21
|
+
TAP_SEEK = "TAP_SEEK",
|
|
22
|
+
AUTO_SEEK = "AUTO_SEEK",
|
|
23
|
+
}
|
|
24
|
+
|
|
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;
|
|
41
|
+
private delayedSeekProgressSubject = new BehaviorSubject<number | null>(null);
|
|
42
|
+
isPlayerInitiallyPaused: null | boolean = null;
|
|
43
|
+
currentSeekType: SEEK_TYPE | null = null;
|
|
44
|
+
|
|
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;
|
|
50
|
+
|
|
51
|
+
constructor(playerController: Player) {
|
|
52
|
+
this.playerController = playerController;
|
|
53
|
+
|
|
54
|
+
this.tapSeekController = new TapSeekController(
|
|
55
|
+
this,
|
|
56
|
+
new SeekIncrementalBaseStrategy(),
|
|
57
|
+
CONFIG_TAP_SEEK
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
this.longPressSeekController = new LongPressSeekController(
|
|
61
|
+
this,
|
|
62
|
+
new SeekIncrementalBaseStrategy(),
|
|
63
|
+
CONFIG_LONG_PRESS
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
this.currentSeekController = this.tapSeekController;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getDelayedSeekProgressObservable = () => {
|
|
70
|
+
return this.delayedSeekProgressSubject;
|
|
71
|
+
};
|
|
72
|
+
|
|
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");
|
|
83
|
+
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
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();
|
|
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
|
+
|
|
148
|
+
let targetPos = currentPos;
|
|
149
|
+
|
|
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);
|
|
157
|
+
} else {
|
|
158
|
+
log_warning(
|
|
159
|
+
`TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
log_debug(
|
|
164
|
+
`TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}`
|
|
165
|
+
);
|
|
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;
|
|
178
|
+
this.delayedSeekProgressSubject.next(targetPos);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
handleSeekTo = (newPosition: number) => {
|
|
182
|
+
this.playerController.seekTo(newPosition);
|
|
183
|
+
|
|
184
|
+
if (!this.isPlayerInitiallyPaused) {
|
|
185
|
+
this.playerController.play();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
handleSeek = (offset: number) => {
|
|
190
|
+
this.playerController[this.currentSeekType](offset);
|
|
191
|
+
|
|
192
|
+
if (!this.isPlayerInitiallyPaused) {
|
|
193
|
+
this.playerController.play();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
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
|
+
};
|
|
242
|
+
|
|
243
|
+
resetAll() {
|
|
244
|
+
log_debug("TVSeekController: Reset all states");
|
|
245
|
+
this.currentSeekType = null;
|
|
246
|
+
|
|
247
|
+
this.delayedSeekPosition = null;
|
|
248
|
+
this.isPlayerInitiallyPaused = null;
|
|
249
|
+
|
|
250
|
+
this.clearSeekCompleteTimeout();
|
|
251
|
+
this.clearLongPressThresholdTimeout();
|
|
252
|
+
|
|
253
|
+
this.tapSeekController.reset();
|
|
254
|
+
this.longPressSeekController.reset();
|
|
255
|
+
|
|
256
|
+
this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TapSeekController implements ISeekController {
|
|
14
|
+
readonly title = "TapSeek";
|
|
15
|
+
constructor(
|
|
16
|
+
private seekControllerDelegate: ISeekControllerDelegate,
|
|
17
|
+
private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
|
|
18
|
+
private config: TapPressConfig
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
onStartSeek = (seekType: SEEK_TYPE) => {
|
|
22
|
+
log_debug(`${this.title}: start tap seek type: ${seekType}.`);
|
|
23
|
+
|
|
24
|
+
const skipTime = this.seekIncrementalStrategy.getSkipTime();
|
|
25
|
+
const seekMultiplier = this.seekIncrementalStrategy.getSkipMultiplier();
|
|
26
|
+
|
|
27
|
+
if (seekMultiplier > this.config.CLICKS_BEFORE_PAUSE) {
|
|
28
|
+
log_debug(
|
|
29
|
+
`${this.title}: handle delayed seek type:${seekType} - skipTime: ${skipTime}, seekMultiplier: ${seekMultiplier}.`
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
this.seekControllerDelegate.handleDelayedSeek(skipTime);
|
|
33
|
+
} else {
|
|
34
|
+
log_debug(
|
|
35
|
+
`${this.title}: handle seek type:${seekType} - skipTime: ${skipTime}.`
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
this.seekControllerDelegate.handleSeek(skipTime);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.seekIncrementalStrategy.increment();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
onSeekEnd = () => {
|
|
45
|
+
this.seekControllerDelegate.handleSeekComplete();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
reset = () => {
|
|
49
|
+
log_debug("{this.title}: Reset states");
|
|
50
|
+
|
|
51
|
+
this.seekIncrementalStrategy.reset();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -5,15 +5,14 @@ import { ON_HOLD_INTERVAL, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
|
|
|
5
5
|
|
|
6
6
|
import { useAutoSeek } from "../useAutoSeek";
|
|
7
7
|
|
|
8
|
-
jest.useFakeTimers();
|
|
8
|
+
jest.useFakeTimers({ legacyFakeTimers: true });
|
|
9
9
|
|
|
10
10
|
const rewind = jest.fn();
|
|
11
11
|
const forward = jest.fn();
|
|
12
12
|
|
|
13
13
|
jest
|
|
14
14
|
.spyOn(playerManager, "getInstanceController")
|
|
15
|
-
|
|
16
|
-
.mockImplementation(() => ({ rewind, forward }));
|
|
15
|
+
.mockReturnValue({ rewind, forward });
|
|
17
16
|
|
|
18
17
|
describe("useAutoSeek", () => {
|
|
19
18
|
beforeEach(() => {
|
|
@@ -30,3 +30,16 @@ export const useIsInitialRender = (): boolean => {
|
|
|
30
30
|
|
|
31
31
|
return isInitialRenderRef.current;
|
|
32
32
|
};
|
|
33
|
+
|
|
34
|
+
export const checkIsLocalFeed = (feedUrl) => feedUrl?.startsWith("pipesv2://");
|
|
35
|
+
|
|
36
|
+
export const shouldDispatchData = (
|
|
37
|
+
url: string,
|
|
38
|
+
feed: ZappPipesData | null,
|
|
39
|
+
clearCache: boolean
|
|
40
|
+
) => {
|
|
41
|
+
const currentFeedHasData = feed?.data;
|
|
42
|
+
const isLocalFeed = checkIsLocalFeed(url);
|
|
43
|
+
|
|
44
|
+
return !currentFeedHasData || clearCache || isLocalFeed;
|
|
45
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
jest.mock("create-react-ref/lib/createRef", () => jest.fn());
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import * as helpers from "../helpers";
|
|
4
3
|
|
|
@@ -8,13 +7,7 @@ import {
|
|
|
8
7
|
componentDidMount,
|
|
9
8
|
} from "./fixtures";
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
attachLifeCycleMethods,
|
|
13
|
-
createReactRef,
|
|
14
|
-
mergeChildrenProps,
|
|
15
|
-
} = require("../index");
|
|
16
|
-
|
|
17
|
-
const createRef = require("create-react-ref/lib/createRef");
|
|
10
|
+
import { attachLifeCycleMethods, createReactRef, mergeChildrenProps } from "..";
|
|
18
11
|
|
|
19
12
|
describe("attachLifeCycleMethods", () => {
|
|
20
13
|
let addLifeCycleMethodsSpy;
|
|
@@ -50,30 +43,19 @@ describe("attachLifeCycleMethods", () => {
|
|
|
50
43
|
|
|
51
44
|
describe("createReactRef", () => {
|
|
52
45
|
it("calls the React.createRef function if it exists", () => {
|
|
53
|
-
jest.
|
|
54
|
-
|
|
55
|
-
jest.doMock("react", () => ({
|
|
56
|
-
...jest.requireActual("react"),
|
|
57
|
-
createRef: jest.fn(),
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
const React = require("react");
|
|
46
|
+
jest.clearAllMocks();
|
|
61
47
|
|
|
62
|
-
createReactRef();
|
|
63
|
-
expect(
|
|
64
|
-
expect(createRef).not.toHaveBeenCalled();
|
|
48
|
+
const ref = createReactRef();
|
|
49
|
+
expect(ref).toHaveProperty("current");
|
|
65
50
|
});
|
|
66
51
|
|
|
67
52
|
it("calls the polyfill if React.createRef doesn't exist", () => {
|
|
68
|
-
jest.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
createReactRef();
|
|
76
|
-
expect(createRef).toHaveBeenCalled();
|
|
53
|
+
jest.clearAllMocks();
|
|
54
|
+
const originalCreateRef = React.createRef;
|
|
55
|
+
React.createRef = undefined;
|
|
56
|
+
const ref = createReactRef();
|
|
57
|
+
expect(ref).toHaveProperty("current");
|
|
58
|
+
React.createRef = originalCreateRef;
|
|
77
59
|
});
|
|
78
60
|
});
|
|
79
61
|
|