@applicaster/quick-brick-player 15.0.0-alpha.9667030680 → 15.0.0-alpha.9980835537

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "15.0.0-alpha.9667030680",
3
+ "version": "15.0.0-alpha.9980835537",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -35,11 +35,11 @@
35
35
  },
36
36
  "homepage": "https://github.com/applicaster/quickbrick#readme",
37
37
  "dependencies": {
38
- "@applicaster/quick-brick-mobile-transport-controls": "15.0.0-rc.27",
39
- "@applicaster/quick-brick-tv-transport-controls": "15.0.0-rc.15",
40
- "@applicaster/zapp-react-native-tvos-app": "15.0.0-alpha.9667030680",
41
- "@applicaster/zapp-react-native-ui-components": "15.0.0-alpha.9667030680",
42
- "@applicaster/zapp-react-native-utils": "15.0.0-alpha.9667030680",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "15.0.0-rc.143",
39
+ "@applicaster/quick-brick-tv-transport-controls": "15.0.0-rc.142",
40
+ "@applicaster/zapp-react-native-tvos-app": "15.0.0-alpha.9980835537",
41
+ "@applicaster/zapp-react-native-ui-components": "15.0.0-alpha.9980835537",
42
+ "@applicaster/zapp-react-native-utils": "15.0.0-alpha.9980835537",
43
43
  "query-string": "7.1.3",
44
44
  "shaka-player": "4.3.5",
45
45
  "typeface-montserrat": "^0.0.54",
@@ -23,7 +23,7 @@ import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/pl
23
23
  import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
24
24
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
25
25
  import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
26
- import { isMenuVisible } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
26
+ import { isBottomTabVisible } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
27
27
  import { usePlugins } from "@applicaster/zapp-react-native-redux";
28
28
  import { partial } from "@applicaster/zapp-react-native-utils/utils";
29
29
 
@@ -167,7 +167,12 @@ export const DockedControls = (props: Props) => {
167
167
  const { currentRoute, screenData } = useNavigation();
168
168
 
169
169
  const plugins = usePlugins();
170
- const menuVisible = isMenuVisible(currentRoute, screenData, plugins);
170
+
171
+ const bottomTabsVisible = isBottomTabVisible(
172
+ currentRoute,
173
+ screenData,
174
+ plugins
175
+ );
171
176
 
172
177
  const { minimised_height: minimisedHeight } = useConfiguration();
173
178
 
@@ -248,13 +253,13 @@ export const DockedControls = (props: Props) => {
248
253
  );
249
254
 
250
255
  const bottomSpacerStyle = React.useMemo(() => {
251
- const tabBarHeight = menuVisible ? bottomTabBarHeight : 0;
256
+ const tabBarHeight = bottomTabsVisible ? bottomTabBarHeight : 0;
252
257
  const safeAreaBottomInset = insets.bottom || 0;
253
258
 
254
259
  return {
255
260
  height: safeAreaBottomInset + tabBarHeight,
256
261
  };
257
- }, [menuVisible, insets.bottom]);
262
+ }, [bottomTabsVisible, insets.bottom]);
258
263
 
259
264
  const contentInfoContent = React.useMemo(
260
265
  () => ({ ...entry, title, summary }),
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { LayoutChangeEvent, View } from "react-native";
2
+ import { Animated, LayoutChangeEvent, View } from "react-native";
3
3
 
4
4
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
5
5
  import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
@@ -8,6 +8,7 @@ import { FlexImage } from "./FlexImage";
8
8
 
9
9
  import { styles } from "./styles";
10
10
  import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
11
+ import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
11
12
 
12
13
  type Props = {
13
14
  entry: ZappEntry;
@@ -29,15 +30,34 @@ export const PlayerImage = (props: Props) => {
29
30
  configuration.audio_player_artwork_border_radius
30
31
  );
31
32
 
33
+ const { yTranslate } = useModalAnimationContext();
34
+
35
+ const isModalExpanded = yTranslate.current.interpolate({
36
+ inputRange: [0, 1], // this should be end with `1` only when it's in initial state ( 0pos), otherwise `0`
37
+ outputRange: [1, 0],
38
+ });
39
+
40
+ // do not use styles.shadow when isModalExpanded is 0
41
+
42
+ const animatedStyle = {
43
+ opacity: isModalExpanded,
44
+ };
45
+
32
46
  return (
33
47
  <View style={styles.alignItemsCenter}>
48
+ {/* Using an Animated.View to apply shadow to the image */}
49
+ <Animated.View
50
+ style={[
51
+ animatedStyle,
52
+ shouldShowShadow ? styles.shadow : undefined,
53
+ { borderRadius },
54
+ ]}
55
+ />
34
56
  <View
35
57
  onLayout={onLayoutImage}
36
- style={[shouldShowShadow ? styles.shadow : undefined, { borderRadius }]}
58
+ style={[styles.defaultImageWrapperView, { borderRadius }]}
37
59
  >
38
- <View style={[styles.defaultImageWrapperView, { borderRadius }]}>
39
- <FlexImage entry={entry} style={styles.backgroundImageContainer} />
40
- </View>
60
+ <FlexImage entry={entry} style={styles.backgroundImageContainer} />
41
61
  </View>
42
62
  </View>
43
63
  );
@@ -23,7 +23,12 @@ export const styles = StyleSheet.create({
23
23
  overflow: "hidden",
24
24
  },
25
25
  shadow: {
26
- borderColor: "transparent",
26
+ zIndex: 0,
27
+ position: "absolute",
28
+ top: 0,
29
+ left: 0,
30
+ right: 0,
31
+ bottom: 0,
27
32
  shadowColor: SHADOW_COLOR,
28
33
  shadowOffset: { width: 0, height: 24 },
29
34
  shadowOpacity: platformSelect({
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
3
 
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
+ import { usePlugins } from "@applicaster/zapp-react-native-redux";
5
5
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
6
6
 
7
7
  type Props = {
@@ -32,7 +32,7 @@ export const PlayNextOverlay = ({
32
32
  playNextData,
33
33
  setNextVideoPreloadThresholdPercentage,
34
34
  }: Props) => {
35
- const { plugins } = usePickFromState(["plugins"]);
35
+ const plugins = usePlugins();
36
36
 
37
37
  const OverlayPlugin = getOverlayPlugin(plugins);
38
38
 
@@ -5,8 +5,8 @@ import { PanGestureHandler } from "react-native-gesture-handler";
5
5
  import { useStyles } from "./styles";
6
6
  import { useVideoModalState } from "./hooks";
7
7
  import { MODAL_COLLAPSE_RATIO, MODAL_RADIUS } from "./consts";
8
- import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
9
8
  import { getWindowHeight } from "./utils";
9
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
10
10
 
11
11
  type AnimatedModalProps = {
12
12
  content?: ReactElement;
@@ -17,21 +17,17 @@ export function AnimatedModal({
17
17
  content,
18
18
  collapsedContent,
19
19
  }: AnimatedModalProps) {
20
- const translateYRef = useModalAnimationContext().yTranslate;
20
+ const { translateY, collapsed, gestureHandlerProps, expand, offset } =
21
+ useVideoModalState();
21
22
 
22
- const {
23
- translateY,
24
- collapsed,
25
- collapsedHeight,
26
- gestureHandlerProps,
27
- expand,
28
- } = useVideoModalState(translateYRef);
29
-
30
- const styles = useStyles({ height: collapsedHeight, type: "audio" });
23
+ const frame = useSafeAreaFrame();
24
+ const collapsedHeight = frame.height - offset.current;
31
25
 
32
26
  const height = getWindowHeight();
33
27
  const heightAboveMinimised = height - collapsedHeight;
34
28
 
29
+ const styles = useStyles({ height: collapsedHeight, type: "audio" });
30
+
35
31
  const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
36
32
 
37
33
  // Interpolated opacities for smooth cross-fade
@@ -12,7 +12,6 @@ import {
12
12
  VIDEO_TRANSITION_THRESHOLD,
13
13
  } from "./consts";
14
14
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
15
- import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/useModalAnimationContext";
16
15
  import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
17
16
  import {
18
17
  SafeAreaView,
@@ -29,6 +28,9 @@ import {
29
28
  getScaledPos,
30
29
  getExtraContentPadding,
31
30
  } from "./utils";
31
+ import { addOrientationChangeListener } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
32
+
33
+ const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
32
34
 
33
35
  type AnimatedModalProps = {
34
36
  pip?: boolean;
@@ -65,30 +67,26 @@ export function VideoPlayerModal({
65
67
  () =>
66
68
  isTablet && !isTabletPortrait
67
69
  ? style.tabletLandscapeWidth
68
- : getWindowWidth(),
70
+ : getWindowWidth(isTablet, !isTabletPortrait),
69
71
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
72
  []
71
73
  );
72
74
 
73
- const height = useMemo(() => getWindowHeight(), []); // remember initial height, ignore rotation changes
75
+ const height = useMemo(
76
+ () => getWindowHeight(isTablet, !isTabletPortrait),
77
+ []
78
+ ); // remember initial height, ignore rotation changes
79
+
74
80
  const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
75
81
 
76
82
  const { minimised_height: minimisedHeight } = useConfiguration();
77
83
  const scrollViewRef = useRef<typeof Animated.ScrollView | null>(null);
78
84
 
79
- const translateYRef = useModalAnimationContext().yTranslate;
80
- // used when modal is false, should not be used in animation
81
- const dummyRef = useRef(new Animated.Value(0));
82
-
83
- const {
84
- translateY,
85
- collapsed,
86
- collapsedHeight,
87
- gestureHandlerProps,
88
- expand,
89
- } = useVideoModalState(modal ? translateYRef : dummyRef);
85
+ const { translateY, collapsed, gestureHandlerProps, expand, offset } =
86
+ useVideoModalState();
90
87
 
91
88
  const frame = useSafeAreaFrame();
89
+ const collapsedHeight = frame.height - offset.current;
92
90
 
93
91
  const heightAboveMinimised = height - collapsedHeight;
94
92
 
@@ -98,9 +96,27 @@ export function VideoPlayerModal({
98
96
 
99
97
  const insets = useSafeAreaInsets();
100
98
 
101
- const isFullScreenOrPIP = fullscreen || pip;
99
+ const [isFullScreenMode, setFullScreenMode] = React.useState(
100
+ fullscreen || pip
101
+ );
102
+
103
+ React.useEffect(() => {
104
+ const listener = addOrientationChangeListener(({ toOrientation }) => {
105
+ /* ignored in collapsed mode non-PiP player */
106
+ if (collapsed && !pip) return;
107
+ setFullScreenMode(!(toOrientation === 1));
108
+ });
102
109
 
103
- // Interpolated opacities for smooth cross-fade
110
+ return () => {
111
+ listener.remove();
112
+ };
113
+ }, [collapsed, pip]);
114
+
115
+ const isFullScreenOrPIP = isFullScreenMode;
116
+
117
+ const vidHeight = width / aspectRatio;
118
+
119
+ // animated opacity for smooth cross-fade of collapsed content
104
120
  const collapsedOpacity = !isFullScreenOrPIP
105
121
  ? translateY.interpolate({
106
122
  inputRange: [MODAL_COLLAPSE_START, heightAboveMinimised],
@@ -109,6 +125,7 @@ export function VideoPlayerModal({
109
125
  })
110
126
  : new Animated.Value(0);
111
127
 
128
+ // animated opacity for extra content
112
129
  const expandedOpacity = !isFullScreenOrPIP
113
130
  ? translateY.interpolate({
114
131
  inputRange: [0, heightAboveMinimised],
@@ -117,7 +134,7 @@ export function VideoPlayerModal({
117
134
  })
118
135
  : new Animated.Value(1);
119
136
 
120
- // animated position for video content during modal transition
137
+ // animated x position for video content
121
138
  const xPosition = !isFullScreenOrPIP
122
139
  ? translateY.interpolate({
123
140
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -126,9 +143,7 @@ export function VideoPlayerModal({
126
143
  })
127
144
  : new Animated.Value(0);
128
145
 
129
- const vidHeight = width / aspectRatio;
130
-
131
- // animated position for video content during modal transition
146
+ // animated y position for video content
132
147
  const yPosition = !isFullScreenOrPIP
133
148
  ? translateY.interpolate({
134
149
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -144,7 +159,7 @@ export function VideoPlayerModal({
144
159
  })
145
160
  : new Animated.Value(0);
146
161
 
147
- // animated position for video content during modal transition
162
+ // animated position for video content
148
163
  const scalePosition = !isFullScreenOrPIP
149
164
  ? translateY.interpolate({
150
165
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -179,9 +194,11 @@ export function VideoPlayerModal({
179
194
  <PanGestureHandler
180
195
  enabled={enabled}
181
196
  waitFor={scrollViewRef}
197
+ activeOffsetY={[-25, 25]}
198
+ activeOffsetX={[-25, 25]}
182
199
  {...gestureHandlerProps}
183
200
  >
184
- <SafeAreaView pointerEvents="box-none" edges={["bottom"]}>
201
+ <AnimatedSafeAreaView pointerEvents="box-none" edges={["bottom"]}>
185
202
  <Animated.View
186
203
  pointerEvents={"auto"}
187
204
  style={[
@@ -266,7 +283,7 @@ export function VideoPlayerModal({
266
283
  ) : null}
267
284
  </Animated.View>
268
285
  </Animated.View>
269
- </SafeAreaView>
286
+ </AnimatedSafeAreaView>
270
287
  </PanGestureHandler>
271
288
  );
272
289
  }
@@ -1,159 +1,132 @@
1
1
  import { useCallback, useEffect, useMemo, useRef } from "react";
2
- import {
3
- useSafeAreaFrame,
4
- useSafeAreaInsets,
5
- } from "react-native-safe-area-context";
2
+
6
3
  import { State } from "react-native-gesture-handler";
7
- import { Animated } from "react-native";
8
4
 
9
5
  import { VideoModalMode } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
10
- import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
11
- import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
6
+ import { useAnimationStateStore } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
12
7
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
13
- import { isMenuVisible } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
14
8
  import {
15
9
  ANIMATION_DURATION,
16
10
  DRAG_TO_COLLAPSE,
17
11
  DRAG_TO_EXPAND,
18
12
  } from "../consts";
19
- import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
20
- import { usePlugins } from "@applicaster/zapp-react-native-redux";
21
- import { isOldAndroidDevice } from "../utils";
22
-
23
- const bottomTabBarHeight = getTabBarHeight();
13
+ import { Animated } from "react-native";
14
+ import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
24
15
 
25
- export const useVideoModalState = (
26
- translateYRef: React.MutableRefObject<Animated.Value>
27
- ) => {
28
- const { minimised_height: minimisedHeight } = useConfiguration();
16
+ const getAnimatedValue = (animatedValue: Animated.Value): number => {
17
+ return (animatedValue as any).__getValue() as number;
18
+ };
29
19
 
20
+ export const useVideoModalState = () => {
30
21
  const {
31
- maximiseVideoModal,
32
- minimiseVideoModal,
33
- videoModalState,
34
- currentRoute,
35
- screenData,
36
- } = useNavigation();
22
+ yTranslate,
23
+ offset,
24
+ heightAboveMinimised,
25
+ gestureTranslationRef,
26
+ offsetAnimatedValueRef,
27
+ } = useModalAnimationContext();
37
28
 
38
- const plugins = usePlugins();
29
+ const modalAnimatedValue = offsetAnimatedValueRef.current;
30
+ const gestureTranslation = gestureTranslationRef.current;
39
31
 
40
- const menuVisible = isMenuVisible(currentRoute, screenData, plugins);
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
+ );
41
41
 
42
- const collapsed =
43
- videoModalState.visible &&
44
- videoModalState.mode === VideoModalMode.MINIMIZED;
42
+ const translateY = yTranslate.current;
45
43
 
46
- const frame = useSafeAreaFrame();
47
- const insets = useSafeAreaInsets();
44
+ const { maximiseVideoModal, minimiseVideoModal, videoModalState } =
45
+ useNavigation();
48
46
 
49
- const collapsedHeight =
50
- minimisedHeight +
51
- (menuVisible ? bottomTabBarHeight : 0) +
52
- (isOldAndroidDevice ? 0 : insets.bottom) + // insets.bottom is added to properly display docked modal
53
- PROGRESS_BAR_HEIGHT;
47
+ const videoModalMode = videoModalState.mode;
54
48
 
55
- const heightAboveMinimised = frame.height - collapsedHeight;
49
+ const prevModeStateRef = useRef(videoModalMode); // keep track of last non-PIP mode
50
+ const gestureStateRef = useRef<any>(State.UNDETERMINED); // keep track of gesture state
56
51
 
57
- 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
+ }
58
58
 
59
- const handleExpand = useCallback(
60
- (toValue: number) => {
61
- return Animated.timing(translateYRef.current, {
62
- toValue,
63
- duration: ANIMATION_DURATION,
64
- useNativeDriver: true,
65
- });
66
- },
67
- [translateYRef]
68
- );
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
69
64
 
70
- const handleCollapse = useCallback(
71
- (toValue: number) => {
72
- return Animated.timing(translateYRef.current, {
73
- toValue,
74
- duration: ANIMATION_DURATION,
65
+ // Gesture handler for modal
66
+ const onGestureEvent = useMemo(
67
+ () =>
68
+ Animated.event([{ nativeEvent: { translationY: gestureTranslation } }], {
75
69
  useNativeDriver: true,
76
- });
77
- },
78
- [translateYRef]
70
+ }),
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
+ []
79
73
  );
80
74
 
81
- const offset = useRef(heightAboveMinimised);
82
-
83
- // Gesture handler for modal
84
- const onGestureEvent = useCallback(
75
+ const onHandlerStateChange = useCallback(
85
76
  (event: any) => {
86
- if (event.nativeEvent.state === State.ACTIVE) {
87
- const y = event.nativeEvent.translationY;
88
- let newY = offset.current + y;
89
- if (newY < 0) newY = 0;
77
+ const { state: newState, oldState, translationY } = event.nativeEvent;
90
78
 
91
- if (newY > heightAboveMinimised) {
92
- newY = heightAboveMinimised;
93
- }
94
-
95
- translateY.setValue(newY);
79
+ if (newState === State.ACTIVE || oldState === State.ACTIVE) {
80
+ useAnimationStateStore.setState({
81
+ isAnimationInProgress: newState === State.ACTIVE,
82
+ });
96
83
  }
97
- },
98
- [collapsedHeight, translateY]
99
- );
100
84
 
101
- const onHandlerStateChange = useCallback(
102
- (event: any) => {
103
- if (event.nativeEvent.oldState === State.ACTIVE) {
104
- const { translationY } = event.nativeEvent;
105
- let newY = offset.current + translationY;
106
- if (newY < 0) newY = 0;
85
+ // Update gesture state tracking
86
+ gestureStateRef.current = newState;
107
87
 
108
- if (newY > heightAboveMinimised) {
109
- newY = heightAboveMinimised;
110
- }
88
+ if (newState === State.FAILED) {
89
+ return;
90
+ }
111
91
 
112
- const shouldCollapse =
113
- translationY > heightAboveMinimised * DRAG_TO_COLLAPSE;
114
-
115
- const shouldExpand =
116
- translationY < -heightAboveMinimised * DRAG_TO_EXPAND;
117
-
118
- if (!collapsed && shouldCollapse) {
119
- // Collapse
120
- Animated.timing(translateY, {
121
- toValue: heightAboveMinimised,
122
- duration: ANIMATION_DURATION,
123
- useNativeDriver: true,
124
- }).start(() => {
125
- minimiseVideoModal();
126
- offset.current = heightAboveMinimised;
127
- });
128
- } else if (collapsed && shouldExpand) {
129
- // Expand
130
- Animated.timing(translateY, {
131
- toValue: 0,
132
- duration: ANIMATION_DURATION,
133
- useNativeDriver: true,
134
- }).start(() => {
135
- maximiseVideoModal();
136
- offset.current = 0;
137
- translateY.setValue(0);
138
- });
139
- } else {
140
- // Snap back to current state
141
- Animated.spring(translateY, {
142
- toValue: collapsed ? heightAboveMinimised : 0,
143
- useNativeDriver: true,
144
- }).start(() => {
145
- offset.current = collapsed ? heightAboveMinimised : 0;
146
- translateY.setValue(offset.current);
147
- });
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
+ }
148
125
  }
149
- } else if (event.nativeEvent.state === State.BEGAN) {
150
- // Gesture started, set offset
151
- translateY.stopAnimation((value: number) => {
152
- offset.current = value;
153
- });
154
126
  }
155
127
  },
156
- [collapsedHeight, translateY, collapsed, videoModalState]
128
+ // eslint-disable-next-line react-hooks/exhaustive-deps
129
+ [collapsed]
157
130
  );
158
131
 
159
132
  const gestureHandlerProps = useMemo(() => {
@@ -163,44 +136,34 @@ export const useVideoModalState = (
163
136
  };
164
137
  }, [onGestureEvent, onHandlerStateChange]);
165
138
 
139
+ // Track video modal state changes & calling updateAnimatedPosition
166
140
  useEffect(() => {
167
141
  if (videoModalState.visible) {
168
- if (videoModalState.mode === VideoModalMode.MAXIMIZED) {
169
- handleExpand(0).start(() => {
170
- maximiseVideoModal();
171
- offset.current = 0;
172
- translateY.setValue(0);
173
- });
174
- } else if (videoModalState.mode === VideoModalMode.MINIMIZED) {
175
- handleCollapse(heightAboveMinimised).start(() => {
176
- minimiseVideoModal();
177
- offset.current = heightAboveMinimised;
178
- translateY.setValue(heightAboveMinimised);
179
- });
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();
180
154
  }
181
155
  }
182
- }, [
183
- videoModalState.visible,
184
- videoModalState.mode,
185
- collapsedHeight,
186
- heightAboveMinimised,
187
- ]);
156
+ // eslint-disable-next-line react-hooks/exhaustive-deps
157
+ }, [videoModalState.visible, videoModalState.mode, heightAboveMinimised]);
188
158
 
189
159
  return useMemo(
190
160
  () => ({
191
161
  collapsed,
192
- collapsedHeight,
193
162
  offset,
194
163
  gestureHandlerProps,
195
164
  translateY,
196
165
  expand: maximiseVideoModal,
197
166
  }),
198
- [
199
- collapsed,
200
- collapsedHeight,
201
- gestureHandlerProps,
202
- translateY,
203
- maximiseVideoModal,
204
- ]
167
+ [collapsed, offset, gestureHandlerProps, translateY, maximiseVideoModal]
205
168
  );
206
169
  };
@@ -5,10 +5,7 @@ import {
5
5
  isAndroidVersionAtLeast,
6
6
  isTV,
7
7
  } from "@applicaster/zapp-react-native-utils/reactUtils";
8
- import {
9
- isTablet,
10
- useIsTablet as getIsTablet,
11
- } from "@applicaster/zapp-react-native-utils/reactHooks";
8
+ import { useIsTablet as getIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
12
9
 
13
10
  const SAFE_AREA_BREAKING_API_VERSION = 35;
14
11
 
@@ -16,9 +13,20 @@ export const isOldAndroidDevice =
16
13
  isAndroidPlatform() &&
17
14
  !isAndroidVersionAtLeast(SAFE_AREA_BREAKING_API_VERSION);
18
15
 
19
- export const getWindowHeight = (): number => {
16
+ export const getWindowHeight = (
17
+ isTablet: boolean,
18
+ landscape: boolean
19
+ ): number => {
20
20
  const windowDimensions = Dimensions.get("window");
21
21
 
22
+ if (isTablet && landscape) {
23
+ return windowDimensions.height;
24
+ }
25
+
26
+ if (windowDimensions.width > windowDimensions.height) {
27
+ return windowDimensions.width;
28
+ }
29
+
22
30
  return windowDimensions.height;
23
31
  };
24
32
 
@@ -28,14 +36,25 @@ export const getScreenHeight = (): number => {
28
36
  return screenDimensions.height;
29
37
  };
30
38
 
31
- export const getWindowWidth = (): number => {
39
+ export const getWindowWidth = (
40
+ isTablet: boolean,
41
+ landscape: boolean
42
+ ): number => {
32
43
  const windowDimensions = Dimensions.get("window");
33
44
 
45
+ if (isTablet && landscape) {
46
+ return windowDimensions.width;
47
+ }
48
+
49
+ if (windowDimensions.width > windowDimensions.height) {
50
+ return windowDimensions.height;
51
+ }
52
+
34
53
  return windowDimensions.width;
35
54
  };
36
55
 
37
56
  const getIsTabletLandscape = (isTabletPortrait: boolean): boolean => {
38
- return !isTV() && isTablet() && !isTabletPortrait;
57
+ return !isTV() && getIsTablet() && !isTabletPortrait;
39
58
  };
40
59
 
41
60
  export const getInsetsOffset = (
@@ -44,7 +63,7 @@ export const getInsetsOffset = (
44
63
  ): number => {
45
64
  const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
46
65
 
47
- return isTablet() && isTabletLandscape
66
+ return getIsTablet() && isTabletLandscape
48
67
  ? isOldAndroidDevice
49
68
  ? insets.top / 2
50
69
  : 0
@@ -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
+ };
@@ -0,0 +1,115 @@
1
+ import * as React from "react";
2
+
3
+ import { formatTimeDisplay } from "./utils";
4
+ import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
5
+ import { ProgressState } from "./useProgressState";
6
+
7
+ // TODO: Refactor if needed, code was aligned to new structure only
8
+
9
+ const getSliderValues = (duration: number, time: number, isRTL: boolean) => {
10
+ const minimumValue = 0;
11
+
12
+ // Default max value should be greater than duration due to RTL thumb animation
13
+ const maximumValue = duration || 9999;
14
+ const initValue = isRTL ? maximumValue : minimumValue;
15
+ const currentValue = isRTL ? duration - time : time;
16
+ const progressValue = !duration ? initValue : currentValue;
17
+ const timeValue = !duration ? minimumValue : time;
18
+
19
+ return {
20
+ minimumValue,
21
+ maximumValue,
22
+ value: Math.max(0, progressValue),
23
+ timeValue: Math.max(0, timeValue),
24
+ };
25
+ };
26
+
27
+ const prepareNewState = ({ duration, time, remainingTimeDisplay, isRTL }) => {
28
+ const sliderValues = getSliderValues(duration, time, isRTL);
29
+
30
+ const getProgress = (duration, currentTime): number => {
31
+ return Math.round((currentTime / duration) * 100);
32
+ };
33
+
34
+ const getElapsedTime = (currentTime): string => {
35
+ return formatTimeDisplay(currentTime);
36
+ };
37
+
38
+ const getRemainingTime = (duration, currentTime): string => {
39
+ if (isNaN(duration)) {
40
+ return "--:--";
41
+ }
42
+
43
+ return formatTimeDisplay(duration - currentTime);
44
+ };
45
+
46
+ const remaining = getRemainingTime(duration, sliderValues.value);
47
+
48
+ const remainingTimeState = remainingTimeDisplay
49
+ ? `-${remaining}`
50
+ : formatTimeDisplay(duration);
51
+
52
+ return {
53
+ progress: getProgress(duration, sliderValues.value),
54
+ time: [getElapsedTime(sliderValues.timeValue), remainingTimeState],
55
+ currentTime: sliderValues.timeValue,
56
+ duration: Math.max(0, duration),
57
+ sliderValues,
58
+ };
59
+ };
60
+
61
+ export const useVodProgressState = ({
62
+ remainingTimeDisplay = false,
63
+ playerState: { currentTime, duration },
64
+ }): [ProgressState, (seek) => void] => {
65
+ const [state, setState] = React.useState<ProgressState>({
66
+ progress: 0,
67
+ time: ["--:--", "--:--"],
68
+ currentTime: 0,
69
+ duration: 0,
70
+ sliderValues: {
71
+ minimumValue: 0,
72
+ maximumValue: 0,
73
+ value: 0,
74
+ },
75
+ });
76
+
77
+ const isRTL = useIsRTL();
78
+
79
+ React.useEffect(() => {
80
+ if (currentTime == null || duration == null) {
81
+ return;
82
+ }
83
+
84
+ setState(
85
+ prepareNewState({
86
+ duration,
87
+ time: currentTime,
88
+ remainingTimeDisplay,
89
+ isRTL,
90
+ })
91
+ );
92
+ }, [currentTime, duration, isRTL, remainingTimeDisplay]);
93
+
94
+ const updateState = React.useCallback(
95
+ (seekTime?: number): void => {
96
+ if (seekTime == null || duration == null) {
97
+ return;
98
+ }
99
+
100
+ const time =
101
+ (seekTime || seekTime === 0) && Math.abs(seekTime - currentTime) > 1
102
+ ? seekTime
103
+ : currentTime;
104
+
105
+ if (duration && (time || time === 0)) {
106
+ setState(
107
+ prepareNewState({ duration, time, remainingTimeDisplay, isRTL })
108
+ );
109
+ }
110
+ },
111
+ [duration, currentTime, remainingTimeDisplay, isRTL]
112
+ );
113
+
114
+ return [state, updateState];
115
+ };
@@ -0,0 +1,33 @@
1
+ const prependZeroIfNeeded = (value) => {
2
+ const s = typeof value === "number" ? String(value) : String(value ?? "");
3
+
4
+ return `0${s}`.slice(-2);
5
+ };
6
+
7
+ export const calculateLiveProgressBarTime = (duration, time) => {
8
+ const minimumLiveOffsetStart = -5;
9
+
10
+ if (time < minimumLiveOffsetStart && time > -duration) {
11
+ // -67 < -5 && -67 > -68
12
+ return time;
13
+ } else if (time <= -duration) {
14
+ return -duration;
15
+ }
16
+
17
+ return 0;
18
+ };
19
+
20
+ export function formatTimeDisplay(durationInSeconds) {
21
+ const hours = Math.floor(durationInSeconds / 60 / 60);
22
+ const minutes = Math.floor((durationInSeconds - hours * 60 * 60) / 60);
23
+
24
+ const seconds = Math.floor(
25
+ durationInSeconds - hours * 60 * 60 - minutes * 60
26
+ );
27
+
28
+ const hr = prependZeroIfNeeded(hours);
29
+ const mn = prependZeroIfNeeded(minutes);
30
+ const sc = prependZeroIfNeeded(seconds);
31
+
32
+ return `${hours ? `${hr}:` : ""}${mn || "00"}:${sc}`;
33
+ }
@@ -7,8 +7,12 @@ import * as R from "ramda";
7
7
 
8
8
  import { findNodeHandle, StyleSheet, View, ViewStyle } from "react-native";
9
9
 
10
- import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
11
- import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
10
+ import TransportControlsMobile, {
11
+ ErrorOverlay,
12
+ } from "@applicaster/quick-brick-mobile-transport-controls";
13
+ import TransportControlsTV, {
14
+ ErrorOverlay as TVErrorOverlay,
15
+ } from "@applicaster/quick-brick-tv-transport-controls";
12
16
 
13
17
  import { AudioPlayer } from "@applicaster/zapp-react-native-ui-components/Components/AudioPlayer";
14
18
  import {
@@ -102,6 +106,7 @@ export default class VideoPlayer extends React.Component<
102
106
  isLive?: boolean;
103
107
  seekableDuration?: number;
104
108
  displayOverlay?: boolean;
109
+ error: PlayerError | null;
105
110
  }
106
111
  > {
107
112
  public static propTypes = {};
@@ -121,6 +126,7 @@ export default class VideoPlayer extends React.Component<
121
126
  isLive?: boolean;
122
127
  seekableDuration?: number;
123
128
  displayOverlay?: boolean;
129
+ error: PlayerError | null;
124
130
  } = {
125
131
  // Base required properties from NativePlayerState
126
132
  buffering: false,
@@ -131,7 +137,7 @@ export default class VideoPlayer extends React.Component<
131
137
  isTabletPortrait: false,
132
138
  muted: false,
133
139
  showPoster: false,
134
-
140
+ error: null,
135
141
  // Extended properties
136
142
  audioTrackId: null,
137
143
  textTrackId: null,
@@ -462,6 +468,8 @@ export default class VideoPlayer extends React.Component<
462
468
 
463
469
  const newError = new PlayerError(error, description);
464
470
 
471
+ this.setState({ error: newError });
472
+
465
473
  return this.props?.listener?.onError(newError);
466
474
  };
467
475
 
@@ -749,7 +757,7 @@ export default class VideoPlayer extends React.Component<
749
757
  }
750
758
 
751
759
  renderPlayerContent(playerDimensions: ViewStyle) {
752
- const { entry, docked } = this.props;
760
+ const { entry, docked, fullscreen } = this.props;
753
761
  const { isAd, piped } = this.state;
754
762
 
755
763
  const pluginConfiguration = this.getPluginConfiguration();
@@ -812,6 +820,20 @@ export default class VideoPlayer extends React.Component<
812
820
  pluginConfiguration
813
821
  );
814
822
 
823
+ if (this.state.error) {
824
+ return (
825
+ <View style={styles.container}>
826
+ <ErrorOverlay
827
+ error={this.state.error}
828
+ onClose={() => playerManager.close()}
829
+ configuration={pluginConfiguration}
830
+ docked={docked}
831
+ fullscreen={fullscreen}
832
+ />
833
+ </View>
834
+ );
835
+ }
836
+
815
837
  return (
816
838
  <View style={styles.container}>
817
839
  <View style={[playerContainerStyles, playerDimensions]}>
@@ -1346,6 +1368,18 @@ export default class VideoPlayer extends React.Component<
1346
1368
  style: { ...styles.audio, ...this.takeParentStyle },
1347
1369
  };
1348
1370
 
1371
+ if (this.state.error) {
1372
+ return (
1373
+ <View style={this.takeParentStyle} testID={"player-view"}>
1374
+ <TVErrorOverlay
1375
+ error={this.state.error}
1376
+ onClose={() => playerManager.close()}
1377
+ configuration={this.getPluginConfiguration()}
1378
+ />
1379
+ </View>
1380
+ );
1381
+ }
1382
+
1349
1383
  return (
1350
1384
  <View style={this.takeParentStyle} testID={"player-view"}>
1351
1385
  <TVPlayerView
@@ -7,7 +7,7 @@ import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/acti
7
7
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
8
8
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
9
9
  import { usePlayerTTS } from "@applicaster/zapp-react-native-utils/playerUtils/usePlayerTTS";
10
- import { useIsCasting } from "./Player/hooks/useIsCasting";
10
+ import { useIsCasting } from "@applicaster/zapp-react-native-utils/reactHooks/casting";
11
11
 
12
12
  const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
13
13
 
@@ -1,57 +0,0 @@
1
- import React from "react";
2
- import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
3
- import { useAppState } from "@applicaster/zapp-react-native-utils/reactHooks/app";
4
- import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
5
-
6
- const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
7
-
8
- const fakeEntry: any = {
9
- title: "Chromecast use is casting fake entry",
10
- id: "quick-brick-use-is-casting-fake-entry",
11
- };
12
-
13
- /**
14
- * Returns the cast action state connection status, does not update when app is in background on Apple platforms
15
- * On other platforms, it continues to check the cast action state even when the app is in background
16
- */
17
- export const useIsCasting = () => {
18
- const actionContext = useActions(CHROMECAST_IDENTIFIER);
19
- const appState = useAppState();
20
- const isAppInForeground = appState === "active";
21
- const shouldOnlyUpdateInForeground = isApplePlatform();
22
-
23
- const [castActionState, setCastActionState] = React.useState(
24
- !actionContext ? null : actionContext.initialEntryState(fakeEntry)
25
- );
26
-
27
- React.useEffect(() => {
28
- if (!shouldOnlyUpdateInForeground || isAppInForeground) {
29
- if (actionContext && castActionState === null) {
30
- setCastActionState(actionContext.initialEntryState(fakeEntry));
31
- }
32
- }
33
- }, [
34
- actionContext,
35
- setCastActionState,
36
- isAppInForeground,
37
- shouldOnlyUpdateInForeground,
38
- ]);
39
-
40
- React.useEffect(() => {
41
- if (typeof actionContext?.addListener === "function") {
42
- return actionContext?.addListener(String(fakeEntry.id), (state) => {
43
- // Only check foreground state on Apple platforms
44
- if (!shouldOnlyUpdateInForeground || isAppInForeground) {
45
- setCastActionState(state);
46
- }
47
- });
48
- }
49
- }, [
50
- setCastActionState,
51
- actionContext,
52
- isAppInForeground,
53
- shouldOnlyUpdateInForeground,
54
- ]);
55
-
56
- return !!castActionState?.connected;
57
- };