@applicaster/quick-brick-player 15.0.0-rc.5 → 15.0.0-rc.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/package.json +6 -6
  2. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +20 -8
  3. package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +78 -65
  4. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +1 -6
  5. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +27 -25
  6. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +6 -1
  7. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +5 -8
  8. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +1 -6
  9. package/src/Player/AudioLayer/utils.ts +1 -27
  10. package/src/Player/PlayNextOverlay/index.tsx +2 -2
  11. package/src/Player/PlayerModal/PlayerModal.tsx +16 -15
  12. package/src/Player/PlayerModal/VideoPlayerModal.tsx +138 -120
  13. package/src/Player/PlayerModal/consts/index.ts +2 -19
  14. package/src/Player/PlayerModal/hooks/index.ts +115 -139
  15. package/src/Player/PlayerModal/styles.ts +5 -0
  16. package/src/Player/PlayerModal/utils/index.ts +111 -0
  17. package/src/Player/PlayerView/types.ts +1 -2
  18. package/src/Player/Utils/index.tsx +9 -9
  19. package/src/Player/hooks/progressStates/__tests__/utils.test.ts +23 -0
  20. package/src/Player/hooks/progressStates/useLiveProgressState.tsx +78 -0
  21. package/src/Player/hooks/progressStates/useProgressState.tsx +30 -0
  22. package/src/Player/hooks/progressStates/useVodProgressState.tsx +115 -0
  23. package/src/Player/hooks/progressStates/utils.ts +33 -0
  24. package/src/Player/index.tsx +559 -357
  25. package/src/utils/dimensions.ts +29 -0
  26. package/src/utils/index.ts +12 -0
  27. package/src/utils/logger.ts +6 -0
  28. package/src/utils/playerHelpers.ts +11 -0
  29. package/src/utils/playerStyles.ts +50 -0
  30. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +0 -82
  31. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +0 -1
  32. 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
- import { useSafeAreaInsets } from "react-native-safe-area-context";
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 { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
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 { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
18
- import { usePlugins } from "@applicaster/zapp-react-native-redux";
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
- export const useVideoModalState = (
23
- translateYRef: React.MutableRefObject<Animated.Value>
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
- maximiseVideoModal,
29
- minimiseVideoModal,
30
- videoModalState,
31
- currentRoute,
32
- screenData,
33
- } = useNavigation();
34
-
35
- const plugins = usePlugins();
36
-
37
- const menuVisible = isMenuVisible(currentRoute, screenData, plugins);
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, {
35
+ toValue,
36
+ duration,
37
+ useNativeDriver: true,
38
+ }),
39
+ [modalAnimatedValue]
40
+ );
38
41
 
39
- const collapsed = videoModalState.mode === VideoModalMode.MINIMIZED;
42
+ const translateY = yTranslate.current;
40
43
 
41
- const { bottom } = useSafeAreaInsets();
44
+ const { maximiseVideoModal, minimiseVideoModal, videoModalState } =
45
+ useNavigation();
42
46
 
43
- const initialBottomOffset = useRef(bottom).current;
47
+ const videoModalMode = videoModalState.mode;
44
48
 
45
- const collapsedHeight =
46
- minimisedHeight +
47
- initialBottomOffset +
48
- (menuVisible ? bottomTabBarHeight : 0) +
49
- PROGRESS_BAR_HEIGHT;
49
+ const prevModeStateRef = useRef(videoModalMode); // keep track of last non-PIP mode
50
+ const gestureStateRef = useRef<any>(State.UNDETERMINED); // keep track of gesture state
50
51
 
51
- const translateY = translateYRef.current;
52
+ // Track last non-PIP mode
53
+ if (videoModalMode !== prevModeStateRef.current) {
54
+ if (["MAXIMIZED", "MINIMIZED"].includes(videoModalMode)) {
55
+ prevModeStateRef.current = videoModalMode;
56
+ }
57
+ }
52
58
 
53
- const handleExpand = useCallback(
54
- (toValue: number) => {
55
- return Animated.timing(translateYRef.current, {
56
- toValue,
57
- duration: ANIMATION_DURATION,
58
- useNativeDriver: true,
59
- });
60
- },
61
- [translateYRef]
62
- );
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
63
64
 
64
- const handleCollapse = useCallback(
65
- (toValue: number) => {
66
- return Animated.timing(translateYRef.current, {
67
- toValue,
68
- duration: ANIMATION_DURATION,
65
+ // Gesture handler for modal
66
+ const onGestureEvent = useMemo(
67
+ () =>
68
+ Animated.event([{ nativeEvent: { translationY: gestureTranslation } }], {
69
69
  useNativeDriver: true,
70
- });
71
- },
72
- [translateYRef]
70
+ }),
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
+ []
73
73
  );
74
74
 
75
- const offset = useRef(SCREEN_HEIGHT - collapsedHeight);
76
-
77
- // Gesture handler for modal
78
- const onGestureEvent = useCallback(
75
+ const onHandlerStateChange = useCallback(
79
76
  (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;
77
+ const { state: newState, oldState, translationY } = event.nativeEvent;
84
78
 
85
- if (newY > SCREEN_HEIGHT - collapsedHeight) {
86
- newY = SCREEN_HEIGHT - collapsedHeight;
87
- }
88
-
89
- translateY.setValue(newY);
79
+ if (newState === State.ACTIVE || oldState === State.ACTIVE) {
80
+ useAnimationStateStore.setState({
81
+ isAnimationInProgress: newState === State.ACTIVE,
82
+ });
90
83
  }
91
- },
92
- [collapsedHeight, translateY]
93
- );
94
84
 
95
- const onHandlerStateChange = useCallback(
96
- (event: any) => {
97
- if (event.nativeEvent.oldState === State.ACTIVE) {
98
- const { translationY } = event.nativeEvent;
99
- let newY = offset.current + translationY;
100
- if (newY < 0) newY = 0;
85
+ // Update gesture state tracking
86
+ gestureStateRef.current = newState;
101
87
 
102
- if (newY > SCREEN_HEIGHT - collapsedHeight) {
103
- newY = SCREEN_HEIGHT - collapsedHeight;
104
- }
88
+ if (newState === State.FAILED) {
89
+ return;
90
+ }
105
91
 
106
- const shouldCollapse =
107
- translationY > (SCREEN_HEIGHT - collapsedHeight) * DRAG_TO_COLLAPSE;
108
-
109
- const shouldExpand =
110
- translationY < -(SCREEN_HEIGHT - collapsedHeight) * DRAG_TO_EXPAND;
111
-
112
- if (!collapsed && shouldCollapse) {
113
- // Collapse
114
- Animated.timing(translateY, {
115
- toValue: SCREEN_HEIGHT - collapsedHeight,
116
- duration: ANIMATION_DURATION,
117
- useNativeDriver: true,
118
- }).start(() => {
119
- minimiseVideoModal();
120
- offset.current = SCREEN_HEIGHT - collapsedHeight;
121
- });
122
- } else if (collapsed && shouldExpand) {
123
- // Expand
124
- Animated.timing(translateY, {
125
- toValue: 0,
126
- duration: ANIMATION_DURATION,
127
- useNativeDriver: true,
128
- }).start(() => {
129
- maximiseVideoModal();
130
- offset.current = 0;
131
- translateY.setValue(0);
132
- });
133
- } else {
134
- // Snap back to current state
135
- Animated.spring(translateY, {
136
- toValue: collapsed ? SCREEN_HEIGHT - collapsedHeight : 0,
137
- useNativeDriver: true,
138
- }).start(() => {
139
- offset.current = collapsed ? SCREEN_HEIGHT - collapsedHeight : 0;
140
- translateY.setValue(offset.current);
141
- });
92
+ if (translationY !== 0) {
93
+ if (newState === State.BEGAN) {
94
+ // clean up gesture translation
95
+ gestureTranslation.setValue(0);
96
+ } else if (newState === State.END || newState === State.CANCELLED) {
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
- [collapsedHeight, translateY, collapsed, videoModalState]
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.mode === VideoModalMode.MAXIMIZED) {
162
- handleExpand(0).start(() => {
163
- maximiseVideoModal();
164
- offset.current = 0;
165
- translateY.setValue(0);
166
- });
167
- } else if (videoModalState.mode === VideoModalMode.MINIMIZED) {
168
- handleCollapse(SCREEN_HEIGHT - collapsedHeight).start(() => {
169
- minimiseVideoModal();
170
- offset.current = SCREEN_HEIGHT - collapsedHeight;
171
- translateY.setValue(SCREEN_HEIGHT - collapsedHeight);
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
- }, [videoModalState.visible, videoModalState.mode, collapsedHeight]);
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
+ };
@@ -10,9 +10,8 @@ export interface PlayNextOverlayProps {
10
10
  }
11
11
 
12
12
  export interface PlayerComponentProps {
13
- ref: React.RefObject<any>;
13
+ ref: React.Ref<any>;
14
14
  listener: Record<string, any>;
15
- configuration: Record<string, any>;
16
15
  style: ViewStyle;
17
16
  }
18
17
 
@@ -3,13 +3,13 @@ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils"
3
3
  export * from "./Utils";
4
4
 
5
5
  export const getBottomSheetModalIconProps = (theme) => ({
6
- height: theme["audio_modal_bottom_sheet_item_icon_height"],
7
- width: theme["audio_modal_bottom_sheet_item_icon_width"],
8
- marginBottom: theme["audio_modal_bottom_sheet_item_icon_margin_bottom"],
9
- marginLeft: theme["audio_modal_bottom_sheet_item_icon_margin_left"],
10
- marginRight: theme["audio_modal_bottom_sheet_item_icon_margin_right"],
11
- marginTop: theme["audio_modal_bottom_sheet_item_icon_margin_top"],
12
- flavour: theme["modal_bottom_sheet_item_icon_flavour"],
6
+ height: theme.audio_modal_bottom_sheet_item_icon_height,
7
+ width: theme.audio_modal_bottom_sheet_item_icon_width,
8
+ marginBottom: theme.audio_modal_bottom_sheet_item_icon_margin_bottom,
9
+ marginLeft: theme.audio_modal_bottom_sheet_item_icon_margin_left,
10
+ marginRight: theme.audio_modal_bottom_sheet_item_icon_margin_right,
11
+ marginTop: theme.audio_modal_bottom_sheet_item_icon_margin_top,
12
+ flavour: theme.modal_bottom_sheet_item_icon_flavour,
13
13
  });
14
14
 
15
15
  export const getBottomSheetModalLabelProps = (theme, width: number) => {
@@ -112,10 +112,10 @@ export const getBottomSheetModalItemProps = (theme, width: number) => {
112
112
  };
113
113
 
114
114
  export const getBottomSheetModalSelectedItemIcon = (theme) =>
115
- theme["audio_modal_bottom_sheet_item_selected_icon"] || null;
115
+ theme.audio_modal_bottom_sheet_item_selected_icon || null;
116
116
 
117
117
  export const getBottomSheetModalDefaultItemIcon = (theme) =>
118
- theme["audio_modal_bottom_sheet_item_default_icon"] || null;
118
+ theme.audio_modal_bottom_sheet_item_default_icon || null;
119
119
 
120
120
  export const selectSleepTitle = (configuration) =>
121
121
  configuration.playback_sleep_title;
@@ -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
+ };