@applicaster/quick-brick-player 15.0.0-rc.4 → 15.0.0-rc.41
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/package.json +6 -6
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +20 -8
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +71 -63
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +1 -6
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +27 -25
- package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +6 -1
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +5 -8
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +1 -6
- package/src/Player/AudioLayer/utils.ts +1 -27
- package/src/Player/PlayerModal/PlayerModal.tsx +16 -15
- package/src/Player/PlayerModal/VideoPlayerModal.tsx +138 -120
- package/src/Player/PlayerModal/consts/index.ts +2 -19
- package/src/Player/PlayerModal/hooks/index.ts +117 -141
- package/src/Player/PlayerModal/styles.ts +5 -0
- package/src/Player/PlayerModal/utils/index.ts +111 -0
- package/src/Player/PlayerView/types.ts +1 -2
- package/src/Player/hooks/progressStates/__tests__/utils.test.ts +23 -0
- package/src/Player/hooks/progressStates/useLiveProgressState.tsx +78 -0
- package/src/Player/hooks/progressStates/useProgressState.tsx +30 -0
- package/src/Player/hooks/progressStates/useVodProgressState.tsx +115 -0
- package/src/Player/hooks/progressStates/utils.ts +33 -0
- package/src/Player/index.tsx +559 -357
- package/src/utils/dimensions.ts +29 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/logger.ts +6 -0
- package/src/utils/playerHelpers.ts +11 -0
- package/src/utils/playerStyles.ts +50 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +0 -82
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +0 -1
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +0 -99
|
@@ -1,153 +1,132 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { State } from "react-native-gesture-handler";
|
|
4
|
-
import { Animated } from "react-native";
|
|
5
4
|
|
|
6
5
|
import { VideoModalMode } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
7
|
-
import {
|
|
8
|
-
import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
|
|
6
|
+
import { useAnimationStateStore } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
|
|
9
7
|
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
|
|
10
|
-
import { isMenuVisible } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
|
|
11
8
|
import {
|
|
12
9
|
ANIMATION_DURATION,
|
|
13
10
|
DRAG_TO_COLLAPSE,
|
|
14
11
|
DRAG_TO_EXPAND,
|
|
15
|
-
SCREEN_HEIGHT,
|
|
16
12
|
} from "../consts";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
const bottomTabBarHeight = getTabBarHeight();
|
|
13
|
+
import { Animated } from "react-native";
|
|
14
|
+
import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const { minimised_height: minimisedHeight } = useConfiguration();
|
|
16
|
+
const getAnimatedValue = (animatedValue: Animated.Value): number => {
|
|
17
|
+
return (animatedValue as any).__getValue() as number;
|
|
18
|
+
};
|
|
26
19
|
|
|
20
|
+
export const useVideoModalState = () => {
|
|
27
21
|
const {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} =
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const { bottom } = useSafeAreaInsets();
|
|
42
|
-
|
|
43
|
-
const initialBottomOffset = useRef(bottom).current;
|
|
44
|
-
|
|
45
|
-
const collapsedHeight =
|
|
46
|
-
minimisedHeight +
|
|
47
|
-
initialBottomOffset +
|
|
48
|
-
(menuVisible ? bottomTabBarHeight : 0) +
|
|
49
|
-
PROGRESS_BAR_HEIGHT;
|
|
50
|
-
|
|
51
|
-
const translateY = translateYRef.current;
|
|
52
|
-
|
|
53
|
-
const handleExpand = useCallback(
|
|
54
|
-
(toValue: number) => {
|
|
55
|
-
return Animated.timing(translateYRef.current, {
|
|
22
|
+
yTranslate,
|
|
23
|
+
offset,
|
|
24
|
+
heightAboveMinimised,
|
|
25
|
+
gestureTranslationRef,
|
|
26
|
+
offsetAnimatedValueRef,
|
|
27
|
+
} = useModalAnimationContext();
|
|
28
|
+
|
|
29
|
+
const modalAnimatedValue = offsetAnimatedValueRef.current;
|
|
30
|
+
const gestureTranslation = gestureTranslationRef.current;
|
|
31
|
+
|
|
32
|
+
const updateModalTranslation = useCallback(
|
|
33
|
+
(toValue: number, duration: number = ANIMATION_DURATION) =>
|
|
34
|
+
Animated.timing(modalAnimatedValue, {
|
|
56
35
|
toValue,
|
|
57
|
-
duration
|
|
36
|
+
duration,
|
|
58
37
|
useNativeDriver: true,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
[translateYRef]
|
|
38
|
+
}),
|
|
39
|
+
[modalAnimatedValue]
|
|
62
40
|
);
|
|
63
41
|
|
|
64
|
-
const
|
|
65
|
-
(toValue: number) => {
|
|
66
|
-
return Animated.timing(translateYRef.current, {
|
|
67
|
-
toValue,
|
|
68
|
-
duration: ANIMATION_DURATION,
|
|
69
|
-
useNativeDriver: true,
|
|
70
|
-
});
|
|
71
|
-
},
|
|
72
|
-
[translateYRef]
|
|
73
|
-
);
|
|
42
|
+
const translateY = yTranslate.current;
|
|
74
43
|
|
|
75
|
-
const
|
|
44
|
+
const { maximiseVideoModal, minimiseVideoModal, videoModalState } =
|
|
45
|
+
useNavigation();
|
|
76
46
|
|
|
77
|
-
|
|
78
|
-
const onGestureEvent = useCallback(
|
|
79
|
-
(event: any) => {
|
|
80
|
-
if (event.nativeEvent.state === State.ACTIVE) {
|
|
81
|
-
const y = event.nativeEvent.translationY;
|
|
82
|
-
let newY = offset.current + y;
|
|
83
|
-
if (newY < 0) newY = 0;
|
|
47
|
+
const videoModalMode = videoModalState.mode;
|
|
84
48
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
49
|
+
const prevModeStateRef = useRef(videoModalMode); // keep track of last non-PIP mode
|
|
50
|
+
const gestureStateRef = useRef<any>(State.UNDETERMINED); // keep track of gesture state
|
|
88
51
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
52
|
+
// Track last non-PIP mode
|
|
53
|
+
if (videoModalMode !== prevModeStateRef.current) {
|
|
54
|
+
if (["MAXIMIZED", "MINIMIZED"].includes(videoModalMode)) {
|
|
55
|
+
prevModeStateRef.current = videoModalMode;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const collapsed =
|
|
60
|
+
videoModalState.visible &&
|
|
61
|
+
((prevModeStateRef.current === "MINIMIZED" &&
|
|
62
|
+
videoModalState.mode === "PIP") || // If STILL in PIP mode, check if last non-PIP mode was MINIMIZED
|
|
63
|
+
videoModalState.mode === VideoModalMode.MINIMIZED); // Or if currently in MINIMIZED mode
|
|
64
|
+
|
|
65
|
+
// Gesture handler for modal
|
|
66
|
+
const onGestureEvent = useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
Animated.event([{ nativeEvent: { translationY: gestureTranslation } }], {
|
|
69
|
+
useNativeDriver: true,
|
|
70
|
+
}),
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
[]
|
|
93
73
|
);
|
|
94
74
|
|
|
95
75
|
const onHandlerStateChange = useCallback(
|
|
96
76
|
(event: any) => {
|
|
97
|
-
|
|
98
|
-
const { translationY } = event.nativeEvent;
|
|
99
|
-
let newY = offset.current + translationY;
|
|
100
|
-
if (newY < 0) newY = 0;
|
|
77
|
+
const { state: newState, oldState, translationY } = event.nativeEvent;
|
|
101
78
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
79
|
+
if (newState === State.ACTIVE || oldState === State.ACTIVE) {
|
|
80
|
+
useAnimationStateStore.setState({
|
|
81
|
+
isAnimationInProgress: newState === State.ACTIVE,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
105
84
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
85
|
+
// Update gesture state tracking
|
|
86
|
+
gestureStateRef.current = newState;
|
|
87
|
+
|
|
88
|
+
if (translationY !== 0) {
|
|
89
|
+
if (newState === State.BEGAN) {
|
|
90
|
+
// clean up gesture translation
|
|
91
|
+
gestureTranslation.setValue(0);
|
|
92
|
+
} else if (
|
|
93
|
+
newState === State.END ||
|
|
94
|
+
newState === State.FAILED ||
|
|
95
|
+
newState === State.CANCELLED
|
|
96
|
+
) {
|
|
97
|
+
const newY = getAnimatedValue(gestureTranslation);
|
|
98
|
+
|
|
99
|
+
// clean up gesture translation
|
|
100
|
+
gestureTranslation.setValue(0);
|
|
101
|
+
|
|
102
|
+
const v = getAnimatedValue(modalAnimatedValue);
|
|
103
|
+
|
|
104
|
+
// store current offset
|
|
105
|
+
modalAnimatedValue.setValue(v + newY);
|
|
106
|
+
|
|
107
|
+
const shouldCollapse = newY > offset.current * DRAG_TO_COLLAPSE;
|
|
108
|
+
|
|
109
|
+
const shouldExpand = newY < offset.current * DRAG_TO_EXPAND;
|
|
110
|
+
|
|
111
|
+
if (!collapsed && shouldCollapse) {
|
|
112
|
+
// Collapse
|
|
113
|
+
updateModalTranslation(offset.current).start(() => {
|
|
114
|
+
minimiseVideoModal();
|
|
115
|
+
});
|
|
116
|
+
} else if (collapsed && shouldExpand) {
|
|
117
|
+
// Expand
|
|
118
|
+
updateModalTranslation(0).start(() => {
|
|
119
|
+
maximiseVideoModal();
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
// Snap back to current state
|
|
123
|
+
updateModalTranslation(collapsed ? offset.current : 0).start();
|
|
124
|
+
}
|
|
142
125
|
}
|
|
143
|
-
} else if (event.nativeEvent.state === State.BEGAN) {
|
|
144
|
-
// Gesture started, set offset
|
|
145
|
-
translateY.stopAnimation((value: number) => {
|
|
146
|
-
offset.current = value;
|
|
147
|
-
});
|
|
148
126
|
}
|
|
149
127
|
},
|
|
150
|
-
|
|
128
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
129
|
+
[collapsed]
|
|
151
130
|
);
|
|
152
131
|
|
|
153
132
|
const gestureHandlerProps = useMemo(() => {
|
|
@@ -157,37 +136,34 @@ export const useVideoModalState = (
|
|
|
157
136
|
};
|
|
158
137
|
}, [onGestureEvent, onHandlerStateChange]);
|
|
159
138
|
|
|
139
|
+
// Track video modal state changes & calling updateAnimatedPosition
|
|
160
140
|
useEffect(() => {
|
|
161
|
-
if (videoModalState.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
141
|
+
if (videoModalState.visible) {
|
|
142
|
+
const mode =
|
|
143
|
+
videoModalMode === "PIP" ? prevModeStateRef.current : videoModalMode;
|
|
144
|
+
|
|
145
|
+
// Prevent changes during active gesture states to avoid interference
|
|
146
|
+
if ([State.ACTIVE].includes(gestureStateRef.current)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (mode === VideoModalMode.MAXIMIZED) {
|
|
151
|
+
updateModalTranslation(0).start();
|
|
152
|
+
} else if (mode === VideoModalMode.MINIMIZED) {
|
|
153
|
+
updateModalTranslation(heightAboveMinimised).start();
|
|
154
|
+
}
|
|
173
155
|
}
|
|
174
|
-
|
|
156
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
157
|
+
}, [videoModalState.visible, videoModalState.mode, heightAboveMinimised]);
|
|
175
158
|
|
|
176
159
|
return useMemo(
|
|
177
160
|
() => ({
|
|
178
161
|
collapsed,
|
|
179
|
-
collapsedHeight,
|
|
180
162
|
offset,
|
|
181
163
|
gestureHandlerProps,
|
|
182
164
|
translateY,
|
|
183
165
|
expand: maximiseVideoModal,
|
|
184
166
|
}),
|
|
185
|
-
[
|
|
186
|
-
collapsed,
|
|
187
|
-
collapsedHeight,
|
|
188
|
-
gestureHandlerProps,
|
|
189
|
-
translateY,
|
|
190
|
-
maximiseVideoModal,
|
|
191
|
-
]
|
|
167
|
+
[collapsed, offset, gestureHandlerProps, translateY, maximiseVideoModal]
|
|
192
168
|
);
|
|
193
169
|
};
|
|
@@ -63,6 +63,7 @@ export const useStyles = ({ height }: { height: number }) => {
|
|
|
63
63
|
backgroundColor,
|
|
64
64
|
elevation: 10,
|
|
65
65
|
zIndex: 100,
|
|
66
|
+
height: "100%",
|
|
66
67
|
overflow: "hidden",
|
|
67
68
|
},
|
|
68
69
|
modalWrapper: {
|
|
@@ -75,6 +76,10 @@ export const useStyles = ({ height }: { height: number }) => {
|
|
|
75
76
|
flex: 1,
|
|
76
77
|
alignItems: "center",
|
|
77
78
|
},
|
|
79
|
+
modalContentPiP: {
|
|
80
|
+
flex: 1,
|
|
81
|
+
alignItems: "flex-start",
|
|
82
|
+
},
|
|
78
83
|
expandedContentContainer: {
|
|
79
84
|
justifyContent: "flex-start",
|
|
80
85
|
backgroundColor: BG_COLOR_DEFAULT_TRANSPARENT,
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { StyleSheet, Dimensions, ViewStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isAndroidPlatform,
|
|
5
|
+
isAndroidVersionAtLeast,
|
|
6
|
+
isTV,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
8
|
+
import { useIsTablet as getIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
9
|
+
|
|
10
|
+
const SAFE_AREA_BREAKING_API_VERSION = 35;
|
|
11
|
+
|
|
12
|
+
export const isOldAndroidDevice =
|
|
13
|
+
isAndroidPlatform() &&
|
|
14
|
+
!isAndroidVersionAtLeast(SAFE_AREA_BREAKING_API_VERSION);
|
|
15
|
+
|
|
16
|
+
export const getWindowHeight = (
|
|
17
|
+
isTablet: boolean,
|
|
18
|
+
landscape: boolean
|
|
19
|
+
): number => {
|
|
20
|
+
const windowDimensions = Dimensions.get("window");
|
|
21
|
+
|
|
22
|
+
if (isTablet && landscape) {
|
|
23
|
+
return windowDimensions.height;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (windowDimensions.width > windowDimensions.height) {
|
|
27
|
+
return windowDimensions.width;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return windowDimensions.height;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getScreenHeight = (): number => {
|
|
34
|
+
const screenDimensions = Dimensions.get("screen");
|
|
35
|
+
|
|
36
|
+
return screenDimensions.height;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const getWindowWidth = (
|
|
40
|
+
isTablet: boolean,
|
|
41
|
+
landscape: boolean
|
|
42
|
+
): number => {
|
|
43
|
+
const windowDimensions = Dimensions.get("window");
|
|
44
|
+
|
|
45
|
+
if (isTablet && landscape) {
|
|
46
|
+
return windowDimensions.width;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (windowDimensions.width > windowDimensions.height) {
|
|
50
|
+
return windowDimensions.height;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return windowDimensions.width;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const getIsTabletLandscape = (isTabletPortrait: boolean): boolean => {
|
|
57
|
+
return !isTV() && getIsTablet() && !isTabletPortrait;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const getInsetsOffset = (
|
|
61
|
+
insets: { top: number; bottom: number },
|
|
62
|
+
isTabletPortrait: boolean
|
|
63
|
+
): number => {
|
|
64
|
+
const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
|
|
65
|
+
|
|
66
|
+
return getIsTablet() && isTabletLandscape
|
|
67
|
+
? isOldAndroidDevice
|
|
68
|
+
? insets.top / 2
|
|
69
|
+
: 0
|
|
70
|
+
: insets.top / 2;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const orientationStyles = StyleSheet.create({
|
|
74
|
+
landscape: { flexDirection: "row" },
|
|
75
|
+
portrait: { flexDirection: "column" },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const directionStyles = (isTabletPortrait: boolean): ViewStyle => {
|
|
79
|
+
const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
|
|
80
|
+
|
|
81
|
+
return isTabletLandscape
|
|
82
|
+
? orientationStyles.landscape
|
|
83
|
+
: orientationStyles.portrait;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const getScaledPos = (
|
|
87
|
+
height,
|
|
88
|
+
vidHeight,
|
|
89
|
+
minimisedHeight,
|
|
90
|
+
isTabletPortrait
|
|
91
|
+
) => {
|
|
92
|
+
const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
|
|
93
|
+
|
|
94
|
+
const SCALE_FACTOR = minimisedHeight / vidHeight; // Scale factor for expanded content
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
((isTabletLandscape ? height : vidHeight) - vidHeight * SCALE_FACTOR) / 2
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const getExtraContentPadding = (insets: {
|
|
102
|
+
top: number;
|
|
103
|
+
bottom: number;
|
|
104
|
+
}): ViewStyle => {
|
|
105
|
+
const isTablet = getIsTablet(); // not a hook, just a utility function
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
paddingTop: isTablet ? insets.top : 0,
|
|
109
|
+
paddingBottom: insets.bottom,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { calculateLiveProgressBarTime } from "../utils";
|
|
2
|
+
|
|
3
|
+
describe("Utils", () => {
|
|
4
|
+
describe("calculateLiveProgressBarTime", () => {
|
|
5
|
+
it("Should return 0", () => {
|
|
6
|
+
expect(calculateLiveProgressBarTime(68, 1)).toBe(0);
|
|
7
|
+
|
|
8
|
+
expect(calculateLiveProgressBarTime(68, 0)).toBe(0);
|
|
9
|
+
|
|
10
|
+
expect(calculateLiveProgressBarTime(68, -1)).toBe(0);
|
|
11
|
+
|
|
12
|
+
expect(calculateLiveProgressBarTime(68, -5)).toBe(0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("Should return equal duration", () => {
|
|
16
|
+
expect(calculateLiveProgressBarTime(68, -100)).toBe(-68);
|
|
17
|
+
|
|
18
|
+
expect(calculateLiveProgressBarTime(68, -68)).toBe(-68);
|
|
19
|
+
|
|
20
|
+
expect(calculateLiveProgressBarTime(68, -105)).toBe(-68);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { calculateLiveProgressBarTime, formatTimeDisplay } from "./utils";
|
|
4
|
+
import { ProgressState } from "./useProgressState";
|
|
5
|
+
|
|
6
|
+
// TODO: Must Add check RTL support
|
|
7
|
+
const getSliderValues = (seekableDuration: number, time: number) => {
|
|
8
|
+
const value = calculateLiveProgressBarTime(seekableDuration, time);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
minimumValue: -seekableDuration,
|
|
12
|
+
maximumValue: 0,
|
|
13
|
+
value: Math.min(0, value),
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const prepareNewState = ({ seekableDuration, time }) => {
|
|
18
|
+
const sliderValues = getSliderValues(seekableDuration, time);
|
|
19
|
+
|
|
20
|
+
const getProgress = (
|
|
21
|
+
seekableDuration: number,
|
|
22
|
+
currentTime: number
|
|
23
|
+
): number => {
|
|
24
|
+
return Math.round((currentTime / seekableDuration) * 100);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getElapsedTime = (currentTime: number): string => {
|
|
28
|
+
return currentTime === 0
|
|
29
|
+
? ""
|
|
30
|
+
: `-${formatTimeDisplay(Math.abs(currentTime))}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getLiveOffsetPercent = (): number =>
|
|
34
|
+
(100 * (sliderValues.value - sliderValues.minimumValue)) /
|
|
35
|
+
(sliderValues.maximumValue - sliderValues.minimumValue);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
progress: getProgress(seekableDuration, sliderValues.value),
|
|
39
|
+
liveOffsetPercent: getLiveOffsetPercent(),
|
|
40
|
+
time: [getElapsedTime(sliderValues.value), null],
|
|
41
|
+
currentTime: sliderValues.value,
|
|
42
|
+
duration: Math.max(0, seekableDuration),
|
|
43
|
+
sliderValues,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// TODO: Add for `NOW` localization
|
|
48
|
+
export const useLiveProgressState = ({
|
|
49
|
+
playerState: { currentTime, seekableDuration },
|
|
50
|
+
}): [ProgressState, (seek: number) => void] => {
|
|
51
|
+
const stateRef = React.useRef<ProgressState>({
|
|
52
|
+
progress: 0,
|
|
53
|
+
liveOffsetPercent: 0,
|
|
54
|
+
time: ["--:--", "--:--"],
|
|
55
|
+
currentTime: 0,
|
|
56
|
+
duration: 0,
|
|
57
|
+
sliderValues: {
|
|
58
|
+
minimumValue: 0,
|
|
59
|
+
maximumValue: 0,
|
|
60
|
+
value: 0,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const updateState = React.useCallback(
|
|
65
|
+
(seekTime: number): void => {
|
|
66
|
+
if (seekTime == null || seekableDuration == null) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
stateRef.current = prepareNewState({ seekableDuration, time: -seekTime });
|
|
71
|
+
},
|
|
72
|
+
[seekableDuration]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
updateState(currentTime);
|
|
76
|
+
|
|
77
|
+
return [stateRef.current, updateState];
|
|
78
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useLiveProgressState } from "./useLiveProgressState";
|
|
2
|
+
import { useVodProgressState } from "./useVodProgressState";
|
|
3
|
+
|
|
4
|
+
export type ProgressState = {
|
|
5
|
+
liveOffsetPercent?: number;
|
|
6
|
+
progress: number;
|
|
7
|
+
time: string[];
|
|
8
|
+
currentTime: number;
|
|
9
|
+
duration: number;
|
|
10
|
+
sliderValues: { minimumValue: number; maximumValue: number; value: number };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// TODO: Try refactor to remove code duplication
|
|
14
|
+
export const useProgressState = ({
|
|
15
|
+
playerState: { seekableDuration, duration, isLive },
|
|
16
|
+
remainingTimeDisplay = false,
|
|
17
|
+
currentTime,
|
|
18
|
+
}): [ProgressState, (seek) => void] => {
|
|
19
|
+
const liveData: [ProgressState, (seek: number) => void] =
|
|
20
|
+
useLiveProgressState({
|
|
21
|
+
playerState: { currentTime, seekableDuration },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const vodData: [ProgressState, (seek) => void] = useVodProgressState({
|
|
25
|
+
playerState: { currentTime, duration },
|
|
26
|
+
remainingTimeDisplay,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return isLive ? liveData : vodData;
|
|
30
|
+
};
|