@applicaster/quick-brick-player 13.0.0-rc.110 → 13.0.0-rc.112

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.
@@ -12,6 +12,7 @@ import { FlexImage } from "./FlexImage";
12
12
  import { styles } from "./styles";
13
13
  import { calculateBorderRadius } from "../../utils";
14
14
  import { PlayNextData } from "../types";
15
+ import { LayoutChangeEvent, View } from "react-native";
15
16
 
16
17
  type Props = {
17
18
  entry: ZappEntry;
@@ -26,8 +27,9 @@ type Props = {
26
27
  title: string | number;
27
28
  summary: string | number;
28
29
  docked: boolean;
29
- imageWidth: Option<number>;
30
+ imageWidth?: Option<number>;
30
31
  verticalOffset: number;
32
+ onLayoutImage: (event: LayoutChangeEvent) => void;
31
33
  };
32
34
 
33
35
  export const PlayerImage = (props: Props) => {
@@ -35,9 +37,10 @@ export const PlayerImage = (props: Props) => {
35
37
  entry,
36
38
  dimensions,
37
39
  docked,
38
- imageWidth,
39
40
  verticalOffset,
41
+ imageWidth,
40
42
  configuration,
43
+ onLayoutImage,
41
44
  } = props;
42
45
 
43
46
  const {
@@ -76,25 +79,33 @@ export const PlayerImage = (props: Props) => {
76
79
  }}
77
80
  style={styles.alignItemsCenter}
78
81
  >
79
- {!imageWidth ? (
80
- // we render image with flex until we get size of this image
81
- <FlexImage
82
- entry={entry}
83
- style={styles.backgroundImageContainer}
84
- imageStyle={{ borderRadius }}
85
- />
86
- ) : null}
87
-
88
- {imageWidth ? (
89
- // we have imageSize here and now we could animate resizing of this image
90
- <AnimatedImage
91
- entry={entry}
92
- style={styles.backgroundImageContainer}
93
- imageStyle={{ borderRadius }}
94
- imageWidth={imageWidth}
95
- wrapperViewStyle={shouldShowShadow ? styles.shadow : undefined}
96
- />
97
- ) : null}
82
+ <View onLayout={onLayoutImage}>
83
+ {!imageWidth ? (
84
+ <View
85
+ style={[
86
+ styles.defaultImageWrapperView,
87
+ shouldShowShadow ? styles.shadow : undefined,
88
+ {
89
+ borderRadius,
90
+ },
91
+ ]}
92
+ >
93
+ <FlexImage
94
+ entry={entry}
95
+ style={styles.backgroundImageContainer}
96
+ />
97
+ </View>
98
+ ) : (
99
+ // we have imageSize here and now we could animate resizing of this image
100
+ <AnimatedImage
101
+ entry={entry}
102
+ style={styles.backgroundImageContainer}
103
+ imageStyle={{ borderRadius }}
104
+ imageWidth={imageWidth}
105
+ wrapperViewStyle={shouldShowShadow ? styles.shadow : undefined}
106
+ />
107
+ )}
108
+ </View>
98
109
  </AnimationComponent>
99
110
  </AnimationComponent>
100
111
  );
@@ -16,6 +16,9 @@ export const styles = StyleSheet.create({
16
16
  flex: {
17
17
  flex: 1,
18
18
  },
19
+ defaultImageWrapperView: {
20
+ overflow: "hidden",
21
+ },
19
22
  shadow: {
20
23
  shadowColor: SHADOW_COLOR,
21
24
  shadowOffset: { width: 0, height: 24 },
@@ -1,28 +1,36 @@
1
1
  import * as React from "react";
2
- import { View, ViewStyle, StyleSheet } from "react-native";
2
+ import { StyleSheet, View } from "react-native";
3
3
  import {
4
- useSafeAreaInsets,
5
4
  useSafeAreaFrame,
5
+ useSafeAreaInsets,
6
6
  } from "react-native-safe-area-context";
7
7
  import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
8
8
  import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
9
9
 
10
- import { TABLET_LANDSCAPE } from "./const";
11
10
  import { TransportInfo } from "./TransportInfo";
12
11
  import { PlayerImage } from "./PlayerImage";
13
12
  import { Controls } from "./Controls";
14
13
 
15
- import { useTabletLayoutImageSize, useTransportInfoHeight } from "./hooks";
16
14
  import { DockedControls } from "./DockedControls";
17
15
  import { withDebug } from "./debug";
18
16
 
19
17
  import { Props } from "./types";
18
+ import { isSmallTabletHeight as getIsSmallTabletHeight } from "./utils";
19
+ import { NAVBAR_HEIGHT, TABLET_LANDSCAPE } from "./const";
20
+ import { FlexImage } from "./PlayerImage/FlexImage";
20
21
 
21
22
  const isIOS = isApplePlatform();
22
23
 
24
+ const MIN_WIDTH = TABLET_LANDSCAPE.MIN_WIDTH; // Minimum width for the audio player container
25
+ const MAX_SPACING = TABLET_LANDSCAPE.MARGIN_VERTICAL; // Maximum spacing for the audio player container
26
+ const MIN_SPACING = TABLET_LANDSCAPE.MARGIN_VERTICAL_S; // Minimum spacing for the audio player container
27
+ const transportInfoHeight = NAVBAR_HEIGHT;
28
+
23
29
  const styles = StyleSheet.create({
24
30
  container: {
25
31
  flex: 1,
32
+ alignItems: "center",
33
+ height: "100%",
26
34
  ...withDebug({
27
35
  borderWidth: 2,
28
36
  borderColor: "magenta",
@@ -32,30 +40,30 @@ const styles = StyleSheet.create({
32
40
  flex: 1,
33
41
  },
34
42
  audioPlayerContainer: {
35
- flex: 1,
36
- maxHeight: TABLET_LANDSCAPE.MAX_HEIGHT,
37
43
  flexDirection: "column",
38
44
  alignItems: "center",
45
+ minWidth: MIN_WIDTH,
39
46
 
40
47
  ...withDebug({
41
48
  borderWidth: 2,
42
49
  borderColor: "blue",
43
50
  }),
44
51
  },
45
- });
46
-
47
- const tabletStyles = ({
48
- screenHeight,
49
- topInset,
50
- bottomInset,
51
- imageSize,
52
- }): ViewStyle => ({
53
- alignItems: "center",
54
-
55
- height: screenHeight - topInset - bottomInset,
56
- paddingBottom: TABLET_LANDSCAPE.MARGIN_VERTICAL,
57
-
58
- opacity: imageSize ? 1 : 0, // hide content until imageSize is calculated
52
+ audioPlayerContainerPaddings: {
53
+ paddingBottom: MAX_SPACING,
54
+ paddingTop: MAX_SPACING,
55
+ },
56
+ // For small screens, we reduce the padding to fit better
57
+ audioPlayerContainerPaddingsSmall: {
58
+ paddingBottom: MIN_SPACING,
59
+ paddingTop: MIN_SPACING,
60
+ },
61
+ controlsContainer: { flexGrow: 0, alignSelf: "stretch" },
62
+ // eslint-disable-next-line react-native/no-color-literals
63
+ debugYellow: {
64
+ borderWidth: 2,
65
+ borderColor: "yellow",
66
+ },
59
67
  });
60
68
 
61
69
  const isTabletWithHomeButton = (bottomInset: number): boolean =>
@@ -98,83 +106,73 @@ export function TabletLandscapeLayout({
98
106
  title,
99
107
  summary,
100
108
  }: Props) {
109
+ const [imageWidth, setImageWidth] = React.useState(0);
101
110
  const { docked } = layoutState;
102
111
 
103
- const { bottom: bottomInset, top: topInset } = useSafeAreaInsets();
112
+ const { bottom: bottomInset } = useSafeAreaInsets();
104
113
  const { height: screenHeight } = useSafeAreaFrame();
114
+ const isSmallScreen = getIsSmallTabletHeight(screenHeight);
105
115
  const { progressBarHeight } = useModalAnimationContext();
106
116
 
107
- const { imageSize, onLayout } = useTabletLayoutImageSize({
108
- isPortraitMode: false,
109
- });
110
-
111
- const { height: transportInfoHeight, onLayout: onTransportInfoLayout } =
112
- useTransportInfoHeight();
117
+ const onLayoutImage = React.useCallback((e) => {
118
+ setImageWidth(e.nativeEvent.layout.width);
119
+ }, []);
113
120
 
114
121
  return (
115
- <View
116
- style={[
117
- styles.container,
118
- tabletStyles({
119
- screenHeight,
120
- topInset,
121
- bottomInset,
122
- imageSize,
123
- }),
124
- ]}
125
- >
122
+ <View style={styles.container}>
126
123
  {/* TransportInfo */}
127
124
  <TransportInfo
128
125
  entry={entry}
129
126
  layoutState={layoutState}
130
127
  value={value}
131
128
  configuration={configuration}
132
- onLayout={onTransportInfoLayout}
133
129
  />
134
130
 
135
131
  {/* TransportInfo */}
136
132
 
137
- <View style={styles.audioPlayerContainer}>
138
- {/* PlayerImage */}
139
- <View
140
- style={[
141
- styles.flex,
142
- withDebug({
143
- borderWidth: 2,
144
- borderColor: "yellow",
145
- }),
146
- ]}
147
- onLayout={onLayout}
148
- >
149
- <PlayerImage
150
- entry={entry}
151
- layoutState={layoutState}
152
- playerState={playerState}
153
- value={value}
154
- configuration={configuration}
155
- onPlayerSeek={onPlayerSeek}
156
- onCloseVideoModal={onCloseVideoModal}
157
- dimensions={dimensions}
158
- playNextData={playNextData}
159
- title={title}
160
- summary={summary}
161
- docked={docked}
162
- imageWidth={imageSize}
163
- verticalOffset={
164
- transportInfoHeight +
165
- topInset +
166
- withAdditionalOffset({ progressBarHeight, bottomInset })
167
- }
168
- />
133
+ <View
134
+ style={[
135
+ styles.audioPlayerContainer,
136
+ {
137
+ marginTop: isSmallScreen
138
+ ? -TABLET_LANDSCAPE.MARGIN_VERTICAL_S
139
+ : -TABLET_LANDSCAPE.MARGIN_VERTICAL,
140
+ height: screenHeight - transportInfoHeight,
141
+ },
142
+ isSmallScreen
143
+ ? styles.audioPlayerContainerPaddingsSmall
144
+ : styles.audioPlayerContainerPaddings,
145
+ ]}
146
+ >
147
+ {/* PlayerImage non-docked */}
148
+ <View style={[styles.flex, withDebug(styles.debugYellow)]}>
149
+ {!docked ? (
150
+ <PlayerImage
151
+ docked={false}
152
+ entry={entry}
153
+ layoutState={layoutState}
154
+ playerState={playerState}
155
+ value={value}
156
+ configuration={configuration}
157
+ onPlayerSeek={onPlayerSeek}
158
+ onCloseVideoModal={onCloseVideoModal}
159
+ dimensions={dimensions}
160
+ playNextData={playNextData}
161
+ title={title}
162
+ summary={summary}
163
+ verticalOffset={withAdditionalOffset({
164
+ progressBarHeight,
165
+ bottomInset,
166
+ })}
167
+ onLayoutImage={onLayoutImage}
168
+ />
169
+ ) : null}
169
170
  </View>
170
171
  {/* PlayerImage */}
171
172
 
172
- <View
173
- style={{
174
- maxWidth: imageSize,
175
- }}
176
- >
173
+ <View style={styles.controlsContainer}>
177
174
  <Controls
175
+ maxWidth={imageWidth}
178
176
  entry={entry}
179
177
  layoutState={layoutState}
180
178
  playerState={playerState}
@@ -199,11 +197,11 @@ export function TabletLandscapeLayout({
199
197
  </View>
200
198
 
201
199
  <DockedControls
200
+ ImageComponent={<FlexImage entry={entry} style={styles.flex} />}
202
201
  entry={entry}
203
202
  layoutState={layoutState}
204
203
  playerState={playerState}
205
204
  value={value}
206
- configuration={configuration}
207
205
  dimensions={dimensions}
208
206
  onCloseVideoModal={onCloseVideoModal}
209
207
  onPlayerSeek={onPlayerSeek}
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
- import { View, ViewStyle, StyleSheet } from "react-native";
2
+ import { StyleSheet, View, ViewStyle } from "react-native";
3
3
  import {
4
- useSafeAreaInsets,
5
4
  useSafeAreaFrame,
5
+ useSafeAreaInsets,
6
6
  } from "react-native-safe-area-context";
7
7
  import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
8
8
  import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
@@ -211,7 +211,6 @@ export function TabletPortraitLayout({
211
211
  layoutState={layoutState}
212
212
  playerState={playerState}
213
213
  value={value}
214
- configuration={configuration}
215
214
  dimensions={dimensions}
216
215
  onCloseVideoModal={onCloseVideoModal}
217
216
  onPlayerSeek={onPlayerSeek}
@@ -14,6 +14,8 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
14
14
 
15
15
  import { NAVBAR_HEIGHT, TABLET_LANDSCAPE } from "../const";
16
16
  import { withDebug } from "../debug";
17
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
18
+ import { isSmallTabletHeight } from "../utils";
17
19
 
18
20
  const styles = StyleSheet.create({
19
21
  container: {
@@ -52,6 +54,7 @@ export const TransportInfo = ({
52
54
  onLayout = noop,
53
55
  }: Props) => {
54
56
  const isTablet = useIsTablet();
57
+ const safeAreaFrame = useSafeAreaFrame();
55
58
  const languageData = useLanguageData("audio-player");
56
59
 
57
60
  const [marginBottom, setMarginBottom] = React.useState(0);
@@ -62,8 +65,18 @@ export const TransportInfo = ({
62
65
  event?.nativeEvent?.layout?.height
63
66
  );
64
67
 
65
- const minHeight = Math.min(height, TABLET_LANDSCAPE.MARGIN_VERTICAL);
66
- setMarginBottom(TABLET_LANDSCAPE.MARGIN_VERTICAL - minHeight);
68
+ const minHeight = Math.min(
69
+ height,
70
+ isTablet && isSmallTabletHeight(safeAreaFrame.height)
71
+ ? TABLET_LANDSCAPE.MARGIN_VERTICAL_S
72
+ : TABLET_LANDSCAPE.MARGIN_VERTICAL
73
+ );
74
+
75
+ setMarginBottom(
76
+ (isTablet && isSmallTabletHeight(safeAreaFrame.height)
77
+ ? TABLET_LANDSCAPE.MARGIN_VERTICAL_S
78
+ : TABLET_LANDSCAPE.MARGIN_VERTICAL) - minHeight
79
+ );
67
80
 
68
81
  // pass layoutChangeEvent to upper level
69
82
  onLayout(event);
@@ -2,9 +2,13 @@ export const PADDING_HORIZONTAL = 15;
2
2
 
3
3
  export const TABLET_VERTICAL_SEPARATOR_HEIGHT = 32;
4
4
 
5
+ export const TABLET_VERTICAL_SEPARATOR_HEIGHT_S = 16;
6
+
5
7
  export const TABLET_LANDSCAPE = {
6
8
  MARGIN_VERTICAL: 80, // gap between safe-area edge and audio-player container
9
+ MARGIN_VERTICAL_S: 60, // gap between safe-area edge and audio-player container
7
10
  MAX_HEIGHT: 750,
11
+ MIN_WIDTH: 320, // Minimum width for the audio player container
8
12
  };
9
13
 
10
14
  export const TABLET_PORTRAIT = {
@@ -19,7 +19,7 @@ const builder = () => {
19
19
  };
20
20
 
21
21
  export const useTransportInfoHeight = () => {
22
- const [height, setHeight] = React.useState(undefined);
22
+ const [height, setHeight] = React.useState(0);
23
23
 
24
24
  const layoutRef = useRefWithInitialValue<ReturnType<typeof builder>>(builder);
25
25
 
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks whether the screen height is matches with our threshold for small tablets.
3
+ * @param height screen height
4
+ * @returns boolean indicating if the height is less than 800 pixels
5
+ */
6
+ export const isSmallTabletHeight = (height: number): boolean =>
7
+ !(height >= 800);
@@ -0,0 +1,57 @@
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
+ };
@@ -74,6 +74,7 @@ export default class VideoPlayer extends React.Component<
74
74
  state: QuickBrickPlayer.NativePlayerState = {
75
75
  audioTrackId: null,
76
76
  textTrackId: null,
77
+ isCasting: this.props.isCasting,
77
78
 
78
79
  // TODO: Can be deleted, on controller logic
79
80
  isAd: false,
@@ -698,6 +699,10 @@ export default class VideoPlayer extends React.Component<
698
699
  ];
699
700
  };
700
701
 
702
+ get supportNativeCast() {
703
+ return this.props.controller.supportNativeCast();
704
+ }
705
+
701
706
  render() {
702
707
  const {
703
708
  docked,
@@ -710,12 +715,9 @@ export default class VideoPlayer extends React.Component<
710
715
  muted,
711
716
  style,
712
717
  castAction,
713
- controller,
714
718
  startComponentsAnimation,
715
719
  } = this.props;
716
720
 
717
- const supportNativeCast = controller.supportNativeCast();
718
-
719
721
  const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
720
722
  this.state;
721
723
 
@@ -995,11 +997,14 @@ export default class VideoPlayer extends React.Component<
995
997
  const backgroundImageKey =
996
998
  this.getPluginConfiguration()?.player_preview_image_key;
997
999
 
1000
+ const shouldRenderNativePlayer =
1001
+ !this.props.isCasting || this.supportNativeCast;
1002
+
998
1003
  const renderMobilePlayer = () => {
999
1004
  return isFullScreenAudioPlayer && isAudioItem ? (
1000
1005
  // Ensure the View remains in the native hierarchy for proper rendering and interaction
1001
1006
  <View collapsable={false} style={containerStyle}>
1002
- {!castAction?.state?.connected || supportNativeCast ? (
1007
+ {shouldRenderNativePlayer ? (
1003
1008
  <this.props.PlayerComponent
1004
1009
  ref={this._assignRoot}
1005
1010
  {...nativeEvents}
@@ -1051,7 +1056,7 @@ export default class VideoPlayer extends React.Component<
1051
1056
  />
1052
1057
  ) : null}
1053
1058
 
1054
- {!castAction?.state?.connected || supportNativeCast ? (
1059
+ {shouldRenderNativePlayer ? (
1055
1060
  <AnimationComponent
1056
1061
  animationType={ComponentAnimationType.player}
1057
1062
  additionalData={{
@@ -5,6 +5,7 @@ import Player from "./Player";
5
5
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
6
6
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
7
7
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
8
+ import { useIsCasting } from "./Player/hooks/useIsCasting";
8
9
 
9
10
  const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
10
11
 
@@ -24,6 +25,7 @@ const QuickBrickPlayerComponent = (
24
25
  }, [props.PlayerComponent]);
25
26
 
26
27
  const castAction = useActions(CHROMECAST_IDENTIFIER);
28
+ const isCasting = useIsCasting();
27
29
 
28
30
  const isModal = Boolean(props?.isModal);
29
31
  const isScreenDisabled = !controller?.isFullScreenSupported() || !isModal;
@@ -48,6 +50,7 @@ const QuickBrickPlayerComponent = (
48
50
  {...props}
49
51
  ref={ref}
50
52
  castAction={!props?.disableCastAction && castAction}
53
+ isCasting={isCasting}
51
54
  PlayerComponent={props.PlayerComponent}
52
55
  openPiP={openPiP}
53
56
  closePiP={closePiP}
@@ -1,3 +0,0 @@
1
- import LinearGradient from "react-native-linear-gradient";
2
-
3
- export default LinearGradient;
@@ -1,3 +0,0 @@
1
- const WebLinearGradient = require("react-native-web-linear-gradient").default;
2
-
3
- export default WebLinearGradient;
@@ -1 +0,0 @@
1
- export { default } from "./LinearGradientProxy";