@applicaster/quick-brick-player 14.0.0-rc.85 → 14.0.0-rc.86

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": "14.0.0-rc.85",
3
+ "version": "14.0.0-rc.86",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -37,9 +37,9 @@
37
37
  "dependencies": {
38
38
  "@applicaster/quick-brick-mobile-transport-controls": "14.0.0-rc.83",
39
39
  "@applicaster/quick-brick-tv-transport-controls": "14.0.0-rc.71",
40
- "@applicaster/zapp-react-native-tvos-app": "14.0.0-rc.85",
41
- "@applicaster/zapp-react-native-ui-components": "14.0.0-rc.85",
42
- "@applicaster/zapp-react-native-utils": "14.0.0-rc.85",
40
+ "@applicaster/zapp-react-native-tvos-app": "14.0.0-rc.86",
41
+ "@applicaster/zapp-react-native-ui-components": "14.0.0-rc.86",
42
+ "@applicaster/zapp-react-native-utils": "14.0.0-rc.86",
43
43
  "query-string": "7.1.3",
44
44
  "shaka-player": "4.3.5",
45
45
  "typeface-montserrat": "^0.0.54",
@@ -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;
@@ -76,19 +78,11 @@ export function VideoPlayerModal({
76
78
  const { minimised_height: minimisedHeight } = useConfiguration();
77
79
  const scrollViewRef = useRef<typeof Animated.ScrollView | null>(null);
78
80
 
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);
81
+ const { translateY, collapsed, gestureHandlerProps, expand, offset } =
82
+ useVideoModalState();
90
83
 
91
84
  const frame = useSafeAreaFrame();
85
+ const collapsedHeight = frame.height - offset.current;
92
86
 
93
87
  const heightAboveMinimised = height - collapsedHeight;
94
88
 
@@ -98,9 +92,25 @@ export function VideoPlayerModal({
98
92
 
99
93
  const insets = useSafeAreaInsets();
100
94
 
101
- const isFullScreenOrPIP = fullscreen || pip;
95
+ const [isFullScreenMode, setFullScreenMode] = React.useState(false);
96
+
97
+ React.useEffect(() => {
98
+ const listener = addOrientationChangeListener(({ toOrientation }) => {
99
+ /* ignored in collapsed mode non-PiP player */
100
+ if (collapsed && !pip) return;
101
+ setFullScreenMode(!(toOrientation === 1));
102
+ });
103
+
104
+ return () => {
105
+ listener.remove();
106
+ };
107
+ }, [collapsed, pip]);
108
+
109
+ const isFullScreenOrPIP = isFullScreenMode;
102
110
 
103
- // Interpolated opacities for smooth cross-fade
111
+ const vidHeight = width / aspectRatio;
112
+
113
+ // animated opacity for smooth cross-fade of collapsed content
104
114
  const collapsedOpacity = !isFullScreenOrPIP
105
115
  ? translateY.interpolate({
106
116
  inputRange: [MODAL_COLLAPSE_START, heightAboveMinimised],
@@ -109,6 +119,7 @@ export function VideoPlayerModal({
109
119
  })
110
120
  : new Animated.Value(0);
111
121
 
122
+ // animated opacity for extra content
112
123
  const expandedOpacity = !isFullScreenOrPIP
113
124
  ? translateY.interpolate({
114
125
  inputRange: [0, heightAboveMinimised],
@@ -117,7 +128,7 @@ export function VideoPlayerModal({
117
128
  })
118
129
  : new Animated.Value(1);
119
130
 
120
- // animated position for video content during modal transition
131
+ // animated x position for video content
121
132
  const xPosition = !isFullScreenOrPIP
122
133
  ? translateY.interpolate({
123
134
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -126,9 +137,7 @@ export function VideoPlayerModal({
126
137
  })
127
138
  : new Animated.Value(0);
128
139
 
129
- const vidHeight = width / aspectRatio;
130
-
131
- // animated position for video content during modal transition
140
+ // animated y position for video content
132
141
  const yPosition = !isFullScreenOrPIP
133
142
  ? translateY.interpolate({
134
143
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -144,7 +153,7 @@ export function VideoPlayerModal({
144
153
  })
145
154
  : new Animated.Value(0);
146
155
 
147
- // animated position for video content during modal transition
156
+ // animated position for video content
148
157
  const scalePosition = !isFullScreenOrPIP
149
158
  ? translateY.interpolate({
150
159
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -179,9 +188,11 @@ export function VideoPlayerModal({
179
188
  <PanGestureHandler
180
189
  enabled={enabled}
181
190
  waitFor={scrollViewRef}
191
+ activeOffsetY={[-25, 25]}
192
+ activeOffsetX={[-25, 25]}
182
193
  {...gestureHandlerProps}
183
194
  >
184
- <SafeAreaView pointerEvents="box-none" edges={["bottom"]}>
195
+ <AnimatedSafeAreaView pointerEvents="box-none" edges={["bottom"]}>
185
196
  <Animated.View
186
197
  pointerEvents={"auto"}
187
198
  style={[
@@ -266,7 +277,7 @@ export function VideoPlayerModal({
266
277
  ) : null}
267
278
  </Animated.View>
268
279
  </Animated.View>
269
- </SafeAreaView>
280
+ </AnimatedSafeAreaView>
270
281
  </PanGestureHandler>
271
282
  );
272
283
  }
@@ -1,159 +1,125 @@
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";
12
6
  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
7
  import {
15
8
  ANIMATION_DURATION,
16
9
  DRAG_TO_COLLAPSE,
17
10
  DRAG_TO_EXPAND,
18
11
  } 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();
12
+ import { Animated } from "react-native";
13
+ import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
24
14
 
25
- export const useVideoModalState = (
26
- translateYRef: React.MutableRefObject<Animated.Value>
27
- ) => {
28
- const { minimised_height: minimisedHeight } = useConfiguration();
15
+ const getAnimatedValue = (animatedValue: Animated.Value): number => {
16
+ return (animatedValue as any).__getValue() as number;
17
+ };
29
18
 
19
+ export const useVideoModalState = () => {
30
20
  const {
31
- maximiseVideoModal,
32
- minimiseVideoModal,
33
- videoModalState,
34
- currentRoute,
35
- screenData,
36
- } = useNavigation();
37
-
38
- const plugins = usePlugins();
39
-
40
- const menuVisible = isMenuVisible(currentRoute, screenData, plugins);
41
-
42
- const collapsed =
43
- videoModalState.visible &&
44
- videoModalState.mode === VideoModalMode.MINIMIZED;
45
-
46
- const frame = useSafeAreaFrame();
47
- const insets = useSafeAreaInsets();
48
-
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;
54
-
55
- const heightAboveMinimised = frame.height - collapsedHeight;
21
+ yTranslate,
22
+ offset,
23
+ heightAboveMinimised,
24
+ gestureTranslationRef,
25
+ offsetAnimatedValueRef,
26
+ } = useModalAnimationContext();
56
27
 
57
- const translateY = translateYRef.current;
28
+ const modalAnimatedValue = offsetAnimatedValueRef.current;
29
+ const gestureTranslation = gestureTranslationRef.current;
58
30
 
59
- const handleExpand = useCallback(
60
- (toValue: number) => {
61
- return Animated.timing(translateYRef.current, {
31
+ const updateModalTranslation = useCallback(
32
+ (toValue: number, duration: number = ANIMATION_DURATION) =>
33
+ Animated.timing(modalAnimatedValue, {
62
34
  toValue,
63
- duration: ANIMATION_DURATION,
35
+ duration,
64
36
  useNativeDriver: true,
65
- });
66
- },
67
- [translateYRef]
37
+ }),
38
+ [modalAnimatedValue]
68
39
  );
69
40
 
70
- const handleCollapse = useCallback(
71
- (toValue: number) => {
72
- return Animated.timing(translateYRef.current, {
73
- toValue,
74
- duration: ANIMATION_DURATION,
75
- useNativeDriver: true,
76
- });
77
- },
78
- [translateYRef]
79
- );
41
+ const translateY = yTranslate.current;
80
42
 
81
- const offset = useRef(heightAboveMinimised);
43
+ const { maximiseVideoModal, minimiseVideoModal, videoModalState } =
44
+ useNavigation();
82
45
 
83
- // Gesture handler for modal
84
- const onGestureEvent = useCallback(
85
- (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;
46
+ const videoModalMode = videoModalState.mode;
90
47
 
91
- if (newY > heightAboveMinimised) {
92
- newY = heightAboveMinimised;
93
- }
48
+ const prevModeStateRef = useRef(videoModalMode); // keep track of last non-PIP mode
49
+ const gestureStateRef = useRef<any>(State.UNDETERMINED); // keep track of gesture state
94
50
 
95
- translateY.setValue(newY);
96
- }
97
- },
98
- [collapsedHeight, translateY]
51
+ // Track last non-PIP mode
52
+ if (videoModalMode !== prevModeStateRef.current) {
53
+ if (["MAXIMIZED", "MINIMIZED"].includes(videoModalMode)) {
54
+ prevModeStateRef.current = videoModalMode;
55
+ }
56
+ }
57
+
58
+ const collapsed =
59
+ videoModalState.visible &&
60
+ ((prevModeStateRef.current === "MINIMIZED" &&
61
+ videoModalState.mode === "PIP") || // If STILL in PIP mode, check if last non-PIP mode was MINIMIZED
62
+ videoModalState.mode === VideoModalMode.MINIMIZED); // Or if currently in MINIMIZED mode
63
+
64
+ // Gesture handler for modal
65
+ const onGestureEvent = useMemo(
66
+ () =>
67
+ Animated.event([{ nativeEvent: { translationY: gestureTranslation } }], {
68
+ useNativeDriver: true,
69
+ }),
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ []
99
72
  );
100
73
 
101
74
  const onHandlerStateChange = useCallback(
102
75
  (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;
107
-
108
- if (newY > heightAboveMinimised) {
109
- newY = heightAboveMinimised;
110
- }
111
-
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
- });
76
+ const { state: newState, translationY } = event.nativeEvent;
77
+
78
+ // Update gesture state tracking
79
+ gestureStateRef.current = newState;
80
+
81
+ if (translationY !== 0) {
82
+ if (newState === State.BEGAN) {
83
+ // clean up gesture translation
84
+ gestureTranslation.setValue(0);
85
+ } else if (
86
+ newState === State.END ||
87
+ newState === State.FAILED ||
88
+ newState === State.CANCELLED
89
+ ) {
90
+ const newY = getAnimatedValue(gestureTranslation);
91
+
92
+ // clean up gesture translation
93
+ gestureTranslation.setValue(0);
94
+
95
+ const v = getAnimatedValue(modalAnimatedValue);
96
+
97
+ // store current offset
98
+ modalAnimatedValue.setValue(v + newY);
99
+
100
+ const shouldCollapse = newY > offset.current * DRAG_TO_COLLAPSE;
101
+
102
+ const shouldExpand = newY < offset.current * DRAG_TO_EXPAND;
103
+
104
+ if (!collapsed && shouldCollapse) {
105
+ // Collapse
106
+ updateModalTranslation(offset.current).start(() => {
107
+ minimiseVideoModal();
108
+ });
109
+ } else if (collapsed && shouldExpand) {
110
+ // Expand
111
+ updateModalTranslation(0).start(() => {
112
+ maximiseVideoModal();
113
+ });
114
+ } else {
115
+ // Snap back to current state
116
+ updateModalTranslation(collapsed ? offset.current : 0).start();
117
+ }
148
118
  }
149
- } else if (event.nativeEvent.state === State.BEGAN) {
150
- // Gesture started, set offset
151
- translateY.stopAnimation((value: number) => {
152
- offset.current = value;
153
- });
154
119
  }
155
120
  },
156
- [collapsedHeight, translateY, collapsed, videoModalState]
121
+ // eslint-disable-next-line react-hooks/exhaustive-deps
122
+ [collapsed]
157
123
  );
158
124
 
159
125
  const gestureHandlerProps = useMemo(() => {
@@ -163,44 +129,34 @@ export const useVideoModalState = (
163
129
  };
164
130
  }, [onGestureEvent, onHandlerStateChange]);
165
131
 
132
+ // Track video modal state changes & calling updateAnimatedPosition
166
133
  useEffect(() => {
167
134
  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
- });
135
+ const mode =
136
+ videoModalMode === "PIP" ? prevModeStateRef.current : videoModalMode;
137
+
138
+ // Prevent changes during active gesture states to avoid interference
139
+ if ([State.ACTIVE].includes(gestureStateRef.current)) {
140
+ return;
141
+ }
142
+
143
+ if (mode === VideoModalMode.MAXIMIZED) {
144
+ updateModalTranslation(0).start();
145
+ } else if (mode === VideoModalMode.MINIMIZED) {
146
+ updateModalTranslation(heightAboveMinimised).start();
180
147
  }
181
148
  }
182
- }, [
183
- videoModalState.visible,
184
- videoModalState.mode,
185
- collapsedHeight,
186
- heightAboveMinimised,
187
- ]);
149
+ // eslint-disable-next-line react-hooks/exhaustive-deps
150
+ }, [videoModalState.visible, videoModalState.mode, heightAboveMinimised]);
188
151
 
189
152
  return useMemo(
190
153
  () => ({
191
154
  collapsed,
192
- collapsedHeight,
193
155
  offset,
194
156
  gestureHandlerProps,
195
157
  translateY,
196
158
  expand: maximiseVideoModal,
197
159
  }),
198
- [
199
- collapsed,
200
- collapsedHeight,
201
- gestureHandlerProps,
202
- translateY,
203
- maximiseVideoModal,
204
- ]
160
+ [collapsed, offset, gestureHandlerProps, translateY, maximiseVideoModal]
205
161
  );
206
162
  };
@@ -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
 
@@ -35,7 +32,7 @@ export const getWindowWidth = (): number => {
35
32
  };
36
33
 
37
34
  const getIsTabletLandscape = (isTabletPortrait: boolean): boolean => {
38
- return !isTV() && isTablet() && !isTabletPortrait;
35
+ return !isTV() && getIsTablet() && !isTabletPortrait;
39
36
  };
40
37
 
41
38
  export const getInsetsOffset = (
@@ -44,7 +41,7 @@ export const getInsetsOffset = (
44
41
  ): number => {
45
42
  const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
46
43
 
47
- return isTablet() && isTabletLandscape
44
+ return getIsTablet() && isTabletLandscape
48
45
  ? isOldAndroidDevice
49
46
  ? insets.top / 2
50
47
  : 0