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

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.83",
3
+ "version": "14.0.0-rc.85",
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.83",
41
- "@applicaster/zapp-react-native-ui-components": "14.0.0-rc.83",
42
- "@applicaster/zapp-react-native-utils": "14.0.0-rc.83",
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",
43
43
  "query-string": "7.1.3",
44
44
  "shaka-player": "4.3.5",
45
45
  "typeface-montserrat": "^0.0.54",
@@ -6,7 +6,7 @@ import { useStyles } from "./styles";
6
6
  import { useVideoModalState } from "./hooks";
7
7
  import { MODAL_COLLAPSE_RATIO, MODAL_RADIUS } from "./consts";
8
8
  import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
9
- import { getScreenHeight, getWindowHeight } from "./utils";
9
+ import { getWindowHeight } from "./utils";
10
10
 
11
11
  type AnimatedModalProps = {
12
12
  content?: ReactElement;
@@ -29,11 +29,10 @@ export function AnimatedModal({
29
29
 
30
30
  const styles = useStyles({ height: collapsedHeight, type: "audio" });
31
31
 
32
- const WINDOW_HEIGHT = getWindowHeight();
33
- const heightAboveMinimised = WINDOW_HEIGHT - collapsedHeight;
34
- const screenHeight = getScreenHeight();
32
+ const height = getWindowHeight();
33
+ const heightAboveMinimised = height - collapsedHeight;
35
34
 
36
- const MODAL_COLLAPSE_START = WINDOW_HEIGHT * MODAL_COLLAPSE_RATIO;
35
+ const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
37
36
 
38
37
  // Interpolated opacities for smooth cross-fade
39
38
  const collapsedOpacity = translateY.interpolate({
@@ -64,7 +63,7 @@ export function AnimatedModal({
64
63
  borderTopLeftRadius: borderTopRadiusAnimated,
65
64
  borderTopRightRadius: borderTopRadiusAnimated,
66
65
  transform: [{ translateY }],
67
- height: screenHeight,
66
+ height: height,
68
67
  },
69
68
  ]}
70
69
  >
@@ -74,7 +73,6 @@ export function AnimatedModal({
74
73
  {
75
74
  borderTopLeftRadius: borderTopRadiusAnimated,
76
75
  borderTopRightRadius: borderTopRadiusAnimated,
77
- height: screenHeight,
78
76
  },
79
77
  ]}
80
78
  >
@@ -1,6 +1,6 @@
1
1
  import React, { ReactElement, useMemo, useRef } from "react";
2
2
 
3
- import { Animated, Pressable, StyleSheet, ViewStyle } from "react-native";
3
+ import { Animated, Pressable } from "react-native";
4
4
  import { PanGestureHandler } from "react-native-gesture-handler";
5
5
  import { useStyles } from "./styles";
6
6
  import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
@@ -12,22 +12,23 @@ import {
12
12
  VIDEO_TRANSITION_THRESHOLD,
13
13
  } from "./consts";
14
14
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
15
- import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
16
15
  import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/useModalAnimationContext";
17
16
  import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
18
- import { useSafeAreaInsets } from "react-native-safe-area-context";
17
+ import {
18
+ SafeAreaView,
19
+ useSafeAreaFrame,
20
+ useSafeAreaInsets,
21
+ } from "react-native-safe-area-context";
19
22
  import { GestureAnimatedScrollView } from "./GestureAnimatedScrollView";
20
23
  import { PlayerDetailsWrapperHeightContext } from "./context";
21
- import { getScreenHeight, getWindowHeight, getWindowWidth } from "./utils";
22
-
23
- const orientationStyles = StyleSheet.create({
24
- landscape: { flexDirection: "row" },
25
- portrait: { flexDirection: "column" },
26
- });
27
-
28
- const directionStyles = (isLandscape: boolean): ViewStyle => {
29
- return isLandscape ? orientationStyles.landscape : orientationStyles.portrait;
30
- };
24
+ import {
25
+ getInsetsOffset,
26
+ getWindowHeight,
27
+ getWindowWidth,
28
+ directionStyles,
29
+ getScaledPos,
30
+ getExtraContentPadding,
31
+ } from "./utils";
31
32
 
32
33
  type AnimatedModalProps = {
33
34
  pip?: boolean;
@@ -59,17 +60,19 @@ export function VideoPlayerModal({
59
60
  const enabled = !!modal && !fullscreen;
60
61
  const isTablet = useIsTablet();
61
62
 
62
- const WINDOW_WIDTH = useMemo(() => getWindowWidth(), []); // remember initial width, ignore rotation changes
63
+ // remember initial width, ignore rotation changes
64
+ const width = useMemo(
65
+ () =>
66
+ isTablet && !isTabletPortrait
67
+ ? style.tabletLandscapeWidth
68
+ : getWindowWidth(),
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps
70
+ []
71
+ );
72
+
63
73
  const height = useMemo(() => getWindowHeight(), []); // remember initial height, ignore rotation changes
64
- const screenHeight = useMemo(() => getScreenHeight(), []); // remember initial height, ignore rotation changes
65
74
  const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
66
75
 
67
- let width = WINDOW_WIDTH;
68
-
69
- if (isTablet && !isTabletPortrait) {
70
- width = style?.tabletLandscapeWidth || width;
71
- }
72
-
73
76
  const { minimised_height: minimisedHeight } = useConfiguration();
74
77
  const scrollViewRef = useRef<typeof Animated.ScrollView | null>(null);
75
78
 
@@ -85,9 +88,9 @@ export function VideoPlayerModal({
85
88
  expand,
86
89
  } = useVideoModalState(modal ? translateYRef : dummyRef);
87
90
 
88
- const heightAboveMinimised = height - collapsedHeight;
91
+ const frame = useSafeAreaFrame();
89
92
 
90
- const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
93
+ const heightAboveMinimised = height - collapsedHeight;
91
94
 
92
95
  const styles = useStyles({ height: collapsedHeight });
93
96
 
@@ -114,6 +117,7 @@ export function VideoPlayerModal({
114
117
  })
115
118
  : new Animated.Value(1);
116
119
 
120
+ // animated position for video content during modal transition
117
121
  const xPosition = !isFullScreenOrPIP
118
122
  ? translateY.interpolate({
119
123
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -124,30 +128,23 @@ export function VideoPlayerModal({
124
128
 
125
129
  const vidHeight = width / aspectRatio;
126
130
 
131
+ // animated position for video content during modal transition
127
132
  const yPosition = !isFullScreenOrPIP
128
133
  ? translateY.interpolate({
129
134
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
130
135
  outputRange: [
131
136
  0,
132
- isTablet && isTabletLandscape
133
- ? -(
134
- (-PROGRESS_BAR_HEIGHT +
135
- screenHeight -
136
- vidHeight * SCALE_FACTOR) /
137
- 2
138
- )
139
- : -(
140
- PROGRESS_BAR_HEIGHT +
141
- insets.top / 2 +
142
- (WINDOW_WIDTH / aspectRatio -
143
- (WINDOW_WIDTH / aspectRatio) * SCALE_FACTOR) /
144
- 2
145
- ),
137
+ -(
138
+ PROGRESS_BAR_HEIGHT +
139
+ getInsetsOffset(insets, isTabletPortrait) +
140
+ getScaledPos(height, vidHeight, minimisedHeight, isTabletPortrait)
141
+ ),
146
142
  ],
147
143
  extrapolate: "clamp",
148
144
  })
149
145
  : new Animated.Value(0);
150
146
 
147
+ // animated position for video content during modal transition
151
148
  const scalePosition = !isFullScreenOrPIP
152
149
  ? translateY.interpolate({
153
150
  inputRange: [height * VIDEO_TRANSITION_THRESHOLD, MODAL_COLLAPSE_START],
@@ -158,7 +155,7 @@ export function VideoPlayerModal({
158
155
 
159
156
  const borderTopRadiusAnimated = !isFullScreenOrPIP
160
157
  ? translateY.interpolate({
161
- inputRange: [0, height - collapsedHeight],
158
+ inputRange: [0, heightAboveMinimised],
162
159
  outputRange: [MODAL_RADIUS, 0],
163
160
  extrapolate: "clamp",
164
161
  })
@@ -184,91 +181,92 @@ export function VideoPlayerModal({
184
181
  waitFor={scrollViewRef}
185
182
  {...gestureHandlerProps}
186
183
  >
187
- <Animated.View
188
- pointerEvents={"auto"}
189
- style={[
190
- styles.modalWrapper,
191
- {
192
- borderTopLeftRadius: borderTopRadiusAnimated,
193
- borderTopRightRadius: borderTopRadiusAnimated,
194
- transform: [{ translateY: isFullScreenOrPIP ? 0 : translateY }],
195
- height: screenHeight,
196
- },
197
- ]}
198
- >
184
+ <SafeAreaView pointerEvents="box-none" edges={["bottom"]}>
199
185
  <Animated.View
200
186
  pointerEvents={"auto"}
201
187
  style={[
202
- styles.modal,
203
- collapsed ? styles.collapsedModalBg : null,
188
+ styles.modalWrapper,
204
189
  {
205
190
  borderTopLeftRadius: borderTopRadiusAnimated,
206
191
  borderTopRightRadius: borderTopRadiusAnimated,
207
- height: screenHeight,
192
+ transform: [{ translateY: isFullScreenOrPIP ? 0 : translateY }],
193
+ height: frame.height,
208
194
  },
209
195
  ]}
210
196
  >
211
197
  <Animated.View
212
- pointerEvents={!collapsed ? "auto" : "none"}
198
+ pointerEvents={"auto"}
213
199
  style={[
214
- pip ? styles.modalContentPiP : styles.modalContent,
215
- directionStyles(isTabletLandscape),
200
+ styles.modal,
201
+ collapsed ? styles.collapsedModalBg : null,
202
+ {
203
+ borderTopLeftRadius: borderTopRadiusAnimated,
204
+ borderTopRightRadius: borderTopRadiusAnimated,
205
+ },
216
206
  ]}
217
207
  >
218
208
  <Animated.View
209
+ pointerEvents={!collapsed ? "auto" : "none"}
219
210
  style={[
220
- styles.expandedContentContainer,
221
- {
222
- transform: [
223
- { translateX: xPosition },
224
- { translateY: yPosition },
225
- { scale: scalePosition },
226
- ],
227
- },
211
+ pip ? styles.modalContentPiP : styles.modalContent,
212
+ directionStyles(isTabletPortrait),
228
213
  ]}
229
214
  >
230
- {content}
215
+ <Animated.View
216
+ style={[
217
+ styles.expandedContentContainer,
218
+ {
219
+ transform: [
220
+ { translateX: xPosition },
221
+ { translateY: yPosition },
222
+ { scale: scalePosition },
223
+ ],
224
+ },
225
+ ]}
226
+ >
227
+ {content}
228
+ </Animated.View>
229
+ {extraContent ? (
230
+ <GestureAnimatedScrollView
231
+ ref={scrollViewRef}
232
+ contentContainerStyle={getExtraContentPadding(insets)}
233
+ bounces={false}
234
+ overScrollMode="never"
235
+ onLayout={onScrollViewLayout}
236
+ style={[
237
+ styles.extraContentScrollView,
238
+ { opacity: expandedOpacity },
239
+ ]}
240
+ >
241
+ <PlayerDetailsWrapperHeightContext.Provider
242
+ value={playerDetailsWrapperHeight}
243
+ >
244
+ {extraContent}
245
+ </PlayerDetailsWrapperHeightContext.Provider>
246
+ </GestureAnimatedScrollView>
247
+ ) : null}
231
248
  </Animated.View>
232
- {extraContent ? (
233
- <GestureAnimatedScrollView
234
- ref={scrollViewRef}
235
- contentContainerStyle={{
236
- paddingBottom: insets.bottom,
237
- }}
238
- onLayout={onScrollViewLayout}
249
+
250
+ {!isFullScreenOrPIP ? (
251
+ <Animated.View
252
+ pointerEvents={collapsed ? "box-none" : "none"}
239
253
  style={[
240
- styles.extraContentScrollView,
241
- { opacity: expandedOpacity },
254
+ styles.collapsedBarContainer,
255
+ { opacity: collapsedOpacity },
242
256
  ]}
243
257
  >
244
- <PlayerDetailsWrapperHeightContext.Provider
245
- value={playerDetailsWrapperHeight}
258
+ <Pressable
259
+ testID="collapsedBarWrapper"
260
+ onPress={expand}
261
+ style={styles.collapsedBarWrapper}
246
262
  >
247
- {extraContent}
248
- </PlayerDetailsWrapperHeightContext.Provider>
249
- </GestureAnimatedScrollView>
263
+ {collapsedContent}
264
+ </Pressable>
265
+ </Animated.View>
250
266
  ) : null}
251
267
  </Animated.View>
252
-
253
- {!isFullScreenOrPIP ? (
254
- <Animated.View
255
- pointerEvents={collapsed ? "box-none" : "none"}
256
- style={[
257
- styles.collapsedBarContainer,
258
- { opacity: collapsedOpacity },
259
- ]}
260
- >
261
- <Pressable
262
- testID="collapsedBarWrapper"
263
- onPress={expand}
264
- style={styles.collapsedBarWrapper}
265
- >
266
- {collapsedContent}
267
- </Pressable>
268
- </Animated.View>
269
- ) : null}
270
268
  </Animated.View>
271
- </Animated.View>
269
+ </SafeAreaView>
272
270
  </PanGestureHandler>
273
271
  );
274
272
  }
@@ -49,7 +49,7 @@ export const useVideoModalState = (
49
49
  const collapsedHeight =
50
50
  minimisedHeight +
51
51
  (menuVisible ? bottomTabBarHeight : 0) +
52
- (isOldAndroidDevice ? 0 : insets.bottom) +
52
+ (isOldAndroidDevice ? 0 : insets.bottom) + // insets.bottom is added to properly display docked modal
53
53
  PROGRESS_BAR_HEIGHT;
54
54
 
55
55
  const heightAboveMinimised = frame.height - collapsedHeight;
@@ -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: {
@@ -1,14 +1,20 @@
1
- import { Dimensions } from "react-native";
1
+ import { StyleSheet, Dimensions, ViewStyle } from "react-native";
2
2
 
3
3
  import {
4
4
  isAndroidPlatform,
5
5
  isAndroidVersionAtLeast,
6
+ isTV,
6
7
  } from "@applicaster/zapp-react-native-utils/reactUtils";
8
+ import {
9
+ isTablet,
10
+ useIsTablet as getIsTablet,
11
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
7
12
 
8
- const MIN_SUPPORTED_ANDROID_API = 34;
13
+ const SAFE_AREA_BREAKING_API_VERSION = 35;
9
14
 
10
15
  export const isOldAndroidDevice =
11
- isAndroidPlatform() && !isAndroidVersionAtLeast(MIN_SUPPORTED_ANDROID_API);
16
+ isAndroidPlatform() &&
17
+ !isAndroidVersionAtLeast(SAFE_AREA_BREAKING_API_VERSION);
12
18
 
13
19
  export const getWindowHeight = (): number => {
14
20
  const windowDimensions = Dimensions.get("window");
@@ -27,3 +33,60 @@ export const getWindowWidth = (): number => {
27
33
 
28
34
  return windowDimensions.width;
29
35
  };
36
+
37
+ const getIsTabletLandscape = (isTabletPortrait: boolean): boolean => {
38
+ return !isTV() && isTablet() && !isTabletPortrait;
39
+ };
40
+
41
+ export const getInsetsOffset = (
42
+ insets: { top: number; bottom: number },
43
+ isTabletPortrait: boolean
44
+ ): number => {
45
+ const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
46
+
47
+ return isTablet() && isTabletLandscape
48
+ ? isOldAndroidDevice
49
+ ? insets.top / 2
50
+ : 0
51
+ : insets.top / 2;
52
+ };
53
+
54
+ const orientationStyles = StyleSheet.create({
55
+ landscape: { flexDirection: "row" },
56
+ portrait: { flexDirection: "column" },
57
+ });
58
+
59
+ export const directionStyles = (isTabletPortrait: boolean): ViewStyle => {
60
+ const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
61
+
62
+ return isTabletLandscape
63
+ ? orientationStyles.landscape
64
+ : orientationStyles.portrait;
65
+ };
66
+
67
+ export const getScaledPos = (
68
+ height,
69
+ vidHeight,
70
+ minimisedHeight,
71
+ isTabletPortrait
72
+ ) => {
73
+ const isTabletLandscape = getIsTabletLandscape(isTabletPortrait);
74
+
75
+ const SCALE_FACTOR = minimisedHeight / vidHeight; // Scale factor for expanded content
76
+
77
+ return (
78
+ ((isTabletLandscape ? height : vidHeight) - vidHeight * SCALE_FACTOR) / 2
79
+ );
80
+ };
81
+
82
+ export const getExtraContentPadding = (insets: {
83
+ top: number;
84
+ bottom: number;
85
+ }): ViewStyle => {
86
+ const isTablet = getIsTablet(); // not a hook, just a utility function
87
+
88
+ return {
89
+ paddingTop: isTablet ? insets.top : 0,
90
+ paddingBottom: insets.bottom,
91
+ };
92
+ };
@@ -835,25 +835,25 @@ export default class VideoPlayer extends React.Component<
835
835
  : styles.playerContainerBlack;
836
836
 
837
837
  private getBackgroundImageStyle = (
838
- dimensions: any,
838
+ dimensions: ViewStyle,
839
839
  isAudioItem: boolean
840
- ): ViewStyle => ({
841
- ...(isAudioItem ? styles.audio : { position: "absolute" }),
842
- ...dimensions,
843
- });
840
+ ): ViewStyle[] => [
841
+ isAudioItem ? styles.audio : { position: "absolute" },
842
+ dimensions,
843
+ ];
844
844
 
845
845
  /* Returns player view style based on platform and content type (audio/video) */
846
846
  private getPlayerViewStyle = (
847
847
  dimensions: ViewStyle,
848
848
  isAudioItem: boolean
849
- ): ViewStyle => {
850
- const generalStyles = isAudioItem ? styles.audio : {};
849
+ ): ViewStyle | ViewStyle[] => {
850
+ const generalStyles = isAudioItem ? styles.audio : undefined;
851
851
 
852
852
  if (isAndroidPlatform() && isAudioItem) {
853
853
  return generalStyles;
854
854
  }
855
855
 
856
- return { ...generalStyles, ...dimensions };
856
+ return [generalStyles, dimensions];
857
857
  };
858
858
 
859
859
  private renderAudioPlayer = (
@@ -876,7 +876,7 @@ export default class VideoPlayer extends React.Component<
876
876
  shouldRender: boolean,
877
877
  imageKey: string,
878
878
  entry: ZappEntry,
879
- style: ViewStyle
879
+ style: ViewStyle | ViewStyle[]
880
880
  ) => {
881
881
  if (!shouldRender) return null;
882
882
 
@@ -887,7 +887,7 @@ export default class VideoPlayer extends React.Component<
887
887
 
888
888
  private renderNativePlayer = (
889
889
  shouldRender: boolean,
890
- style: ViewStyle,
890
+ style: ViewStyle | ViewStyle[],
891
891
  nativeEvents: ReturnType<typeof this.getNativeEvents>,
892
892
  nativeProps: ReturnType<typeof this.getNativeProps>,
893
893
  pluginConfiguration: Record<string, any>
@@ -1265,13 +1265,13 @@ export default class VideoPlayer extends React.Component<
1265
1265
  </View>
1266
1266
  }
1267
1267
  />
1268
- ) : (
1268
+ ) : isModal ? (
1269
1269
  <VideoPlayerModal
1270
1270
  isTabletPortrait={isTabletPortrait}
1271
1271
  aspectRatio={aspectRatio}
1272
1272
  pip={piped}
1273
1273
  fullscreen={fullscreen}
1274
- modal={isModal}
1274
+ modal
1275
1275
  style={{
1276
1276
  tabletLandscapeWidth: getTabletWidth(
1277
1277
  this.getPluginConfiguration().tablet_landscape_sidebar_width,
@@ -1309,6 +1309,13 @@ export default class VideoPlayer extends React.Component<
1309
1309
  </View>
1310
1310
  }
1311
1311
  />
1312
+ ) : (
1313
+ <View style={style} testID={"player-view"}>
1314
+ <PlayerWrapper
1315
+ {...sharedWrapperViewProps}
1316
+ playerContent={this.renderPlayerContent}
1317
+ />
1318
+ </View>
1312
1319
  );
1313
1320
  };
1314
1321