@applicaster/quick-brick-player 13.0.0-alpha.5033140607 → 13.0.0-alpha.5049772260

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +27 -13
  2. package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
  3. package/src/Player/AudioLayer/AudioDescription.tsx +4 -6
  4. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +136 -181
  5. package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +27 -0
  6. package/src/Player/AudioLayer/Layout/Controls/index.tsx +305 -0
  7. package/src/Player/AudioLayer/Layout/{defaultLayoutsConts.ts → Controls/utils.ts} +5 -15
  8. package/src/Player/AudioLayer/Layout/{DockedLayout.tsx → DockedControls/index.tsx} +44 -67
  9. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +212 -0
  10. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +83 -0
  11. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +32 -0
  12. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +95 -0
  13. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +17 -0
  14. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +214 -0
  15. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +224 -0
  16. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +102 -0
  17. package/src/Player/AudioLayer/Layout/VerticalGutter/index.tsx +8 -0
  18. package/src/Player/AudioLayer/Layout/const.ts +20 -0
  19. package/src/Player/AudioLayer/Layout/debug.ts +6 -0
  20. package/src/Player/AudioLayer/Layout/hooks/index.ts +3 -0
  21. package/src/Player/AudioLayer/Layout/hooks/useImageSize.ts +100 -0
  22. package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +54 -0
  23. package/src/Player/AudioLayer/Layout/index.tsx +21 -48
  24. package/src/Player/AudioLayer/Layout/types.ts +7 -0
  25. package/src/Player/AudioLayer/MagicBG/index.tsx +1 -1
  26. package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
  27. package/src/Player/AudioLayer/SpeedManager.ts +52 -0
  28. package/src/Player/AudioLayer/utils.ts +27 -0
  29. package/src/Player/Const/sleepTimerOptions.ts +39 -0
  30. package/src/Player/Const/speedOptions.ts +4 -0
  31. package/src/Player/Utils/Utils.ts +0 -5
  32. package/src/Player/Utils/index.tsx +117 -0
  33. package/src/Player/index.tsx +31 -19
  34. package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
  35. package/src/Player/Helpers/VideoResizeMode.ts +0 -7
@@ -0,0 +1,224 @@
1
+ import * as React from "react";
2
+ import { View, ViewStyle, StyleSheet } from "react-native";
3
+ import {
4
+ useSafeAreaInsets,
5
+ useSafeAreaFrame,
6
+ } from "react-native-safe-area-context";
7
+ import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
8
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
9
+
10
+ import { withDebug } from "./debug";
11
+ import { TABLET_PORTRAIT } from "./const";
12
+
13
+ import { TransportInfo } from "./TransportInfo";
14
+ import { PlayerImage } from "./PlayerImage";
15
+ import { Controls } from "./Controls";
16
+
17
+ import { useImageSize } from "./hooks";
18
+ import { DockedControls } from "./DockedControls";
19
+ import { VerticalGutter } from "./VerticalGutter";
20
+
21
+ import { Props } from "./types";
22
+
23
+ const styles = StyleSheet.create({
24
+ container: {
25
+ flex: 1,
26
+
27
+ ...withDebug({
28
+ borderWidth: 2,
29
+ borderColor: "magenta",
30
+ }),
31
+ },
32
+
33
+ flex: {
34
+ flex: 1,
35
+ },
36
+
37
+ fillContainer: {
38
+ ...StyleSheet.absoluteFillObject,
39
+
40
+ ...withDebug({
41
+ borderWidth: 2,
42
+ borderColor: "red",
43
+ }),
44
+ },
45
+
46
+ audioPlayerContainer: {
47
+ flex: 1,
48
+ maxWidth: TABLET_PORTRAIT.MAX_WIDTH,
49
+ marginLeft: TABLET_PORTRAIT.MARGIN_HORIZONTAL,
50
+ marginRight: TABLET_PORTRAIT.MARGIN_HORIZONTAL,
51
+
52
+ ...withDebug({
53
+ borderWidth: 2,
54
+ borderColor: "blue",
55
+ }),
56
+ },
57
+ });
58
+
59
+ const tabletStyles = ({
60
+ screenHeight,
61
+ topInset,
62
+ bottomInset,
63
+ topOffset,
64
+ }): ViewStyle => ({
65
+ alignItems: "center",
66
+ height: screenHeight - topInset - bottomInset,
67
+ opacity: topOffset ? 1 : 0, // hide content until topOffset is calculated
68
+ });
69
+
70
+ const justifyContent = (topOffset: Option<number>) =>
71
+ topOffset ? "flex-start" : "center";
72
+
73
+ export function TabletPortraitLayout({
74
+ entry,
75
+ layoutState,
76
+ playerState,
77
+ value,
78
+ onPlaybackButtonPress,
79
+ onRewindButtonPress,
80
+ onForwardButtonPress,
81
+ onPlayerSeek,
82
+ onSkipStart,
83
+ onSkipLive,
84
+ onSpeedButtonPress,
85
+ onSleepButtonPress,
86
+ onCloseVideoModal,
87
+ playbackSpeedName,
88
+ playbackSleepName,
89
+ configuration,
90
+ dimensions,
91
+ playNextData,
92
+ cancelSleepTimer,
93
+ sleepTimerDate,
94
+ title,
95
+ summary,
96
+ }: Props) {
97
+ const { docked } = layoutState;
98
+
99
+ const { bottom: bottomInset, top: topInset } = useSafeAreaInsets();
100
+ const { height: screenHeight } = useSafeAreaFrame();
101
+ const { progressBarHeight } = useModalAnimationContext();
102
+
103
+ const {
104
+ imageSize,
105
+ offsetY: topOffset,
106
+ onLayout,
107
+ } = useImageSize({ isPortraitMode: true });
108
+
109
+ return (
110
+ <View
111
+ style={[
112
+ styles.container,
113
+ tabletStyles({
114
+ screenHeight,
115
+ topInset,
116
+ bottomInset,
117
+ topOffset,
118
+ }),
119
+ ]}
120
+ >
121
+ {/* TransportInfo */}
122
+ <TransportInfo
123
+ entry={entry}
124
+ layoutState={layoutState}
125
+ value={value}
126
+ configuration={configuration}
127
+ />
128
+
129
+ {/* TransportInfo */}
130
+
131
+ {/* PlayerImage */}
132
+ <View style={styles.fillContainer} pointerEvents="box-none">
133
+ <View
134
+ style={[
135
+ styles.audioPlayerContainer,
136
+ {
137
+ justifyContent: justifyContent(topOffset),
138
+ width: imageSize,
139
+ },
140
+ ]}
141
+ >
142
+ {topOffset ? <VerticalGutter height={topOffset} /> : null}
143
+
144
+ <View
145
+ style={[
146
+ styles.flex,
147
+ {
148
+ maxHeight: imageSize,
149
+
150
+ ...withDebug({
151
+ borderWidth: 2,
152
+ borderColor: "yellow",
153
+ }),
154
+ },
155
+ ]}
156
+ onLayout={onLayout}
157
+ >
158
+ <PlayerImage
159
+ entry={entry}
160
+ layoutState={layoutState}
161
+ playerState={playerState}
162
+ value={value}
163
+ configuration={configuration}
164
+ onPlayerSeek={onPlayerSeek}
165
+ onCloseVideoModal={onCloseVideoModal}
166
+ dimensions={dimensions}
167
+ playNextData={playNextData}
168
+ title={title}
169
+ summary={summary}
170
+ docked={docked}
171
+ imageWidth={imageSize}
172
+ verticalOffset={
173
+ toNumberWithDefaultZero(topOffset) - progressBarHeight
174
+ }
175
+ />
176
+ </View>
177
+
178
+ <View
179
+ style={{
180
+ maxWidth: imageSize,
181
+ }}
182
+ >
183
+ <Controls
184
+ entry={entry}
185
+ layoutState={layoutState}
186
+ playerState={playerState}
187
+ value={value}
188
+ title={title}
189
+ summary={summary}
190
+ sleepTimerDate={sleepTimerDate}
191
+ cancelSleepTimer={cancelSleepTimer}
192
+ playbackSpeedName={playbackSpeedName}
193
+ playbackSleepName={playbackSleepName}
194
+ onPlaybackButtonPress={onPlaybackButtonPress}
195
+ onRewindButtonPress={onRewindButtonPress}
196
+ onForwardButtonPress={onForwardButtonPress}
197
+ onPlayerSeek={onPlayerSeek}
198
+ onSkipStart={onSkipStart}
199
+ onSkipLive={onSkipLive}
200
+ onSpeedButtonPress={onSpeedButtonPress}
201
+ onSleepButtonPress={onSleepButtonPress}
202
+ docked={docked}
203
+ />
204
+ </View>
205
+ </View>
206
+ </View>
207
+ {/* PlayerImage */}
208
+
209
+ <DockedControls
210
+ entry={entry}
211
+ layoutState={layoutState}
212
+ playerState={playerState}
213
+ value={value}
214
+ configuration={configuration}
215
+ dimensions={dimensions}
216
+ onCloseVideoModal={onCloseVideoModal}
217
+ onPlayerSeek={onPlayerSeek}
218
+ playNextData={playNextData}
219
+ title={title}
220
+ summary={summary}
221
+ />
222
+ </View>
223
+ );
224
+ }
@@ -0,0 +1,102 @@
1
+ import * as React from "react";
2
+ import { compose, take, map, split, propOr, trim } from "ramda";
3
+ import { StyleSheet, View, LayoutChangeEvent } from "react-native";
4
+
5
+ import { TransportInfo as MobileTransportInfo } from "@applicaster/quick-brick-mobile-transport-controls";
6
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
7
+ import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
8
+ import {
9
+ AnimationComponent,
10
+ ComponentAnimationType,
11
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
12
+ import { toPositiveNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
13
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
14
+
15
+ import { TABLET_LANDSCAPE } from "../const";
16
+ import { withDebug } from "../debug";
17
+
18
+ const styles = StyleSheet.create({
19
+ container: {
20
+ width: "100%",
21
+ alignItems: "center",
22
+ justifyContent: "space-between",
23
+ },
24
+ });
25
+
26
+ const applyMarginBottom = ({ isTablet, marginBottom }) =>
27
+ isTablet ? marginBottom : 0;
28
+
29
+ type Props = {
30
+ entry: ZappEntry;
31
+ layoutState: QuickBrickPlayer.LayoutState;
32
+ value: GetValue;
33
+ configuration: QuickBrickPlayer.AudioPlayerProps["configuration"];
34
+ onLayout?: (event: LayoutChangeEvent) => void;
35
+ };
36
+
37
+ export const TransportInfo = ({
38
+ entry,
39
+ layoutState,
40
+ value,
41
+ configuration,
42
+ onLayout = noop,
43
+ }: Props) => {
44
+ const isTablet = useIsTablet();
45
+ const languageData = useLanguageData("audio-player");
46
+
47
+ const [marginBottom, setMarginBottom] = React.useState(0);
48
+
49
+ const handleLayout = React.useCallback(
50
+ (event: LayoutChangeEvent) => {
51
+ const height = toPositiveNumberWithDefaultZero(
52
+ event?.nativeEvent?.layout?.height
53
+ );
54
+
55
+ const minHeight = Math.min(height, TABLET_LANDSCAPE.MARGIN_VERTICAL);
56
+ setMarginBottom(TABLET_LANDSCAPE.MARGIN_VERTICAL - minHeight);
57
+
58
+ // pass layoutChangeEvent to upper level
59
+ onLayout(event);
60
+ },
61
+ [onLayout]
62
+ );
63
+
64
+ const actionButtons = React.useMemo(
65
+ () =>
66
+ compose(
67
+ take(2),
68
+ map(trim),
69
+ split(","),
70
+ propOr("", "player_action_buttons")
71
+ )(configuration),
72
+ [configuration.player_action_buttons]
73
+ );
74
+
75
+ return (
76
+ <View
77
+ style={{
78
+ marginBottom: applyMarginBottom({ isTablet, marginBottom }),
79
+
80
+ ...withDebug({
81
+ borderWidth: 2,
82
+ borderColor: "red",
83
+ }),
84
+ }}
85
+ onLayout={handleLayout}
86
+ >
87
+ <AnimationComponent
88
+ animationType={ComponentAnimationType.componentFade}
89
+ style={styles.container}
90
+ >
91
+ <MobileTransportInfo
92
+ content={entry}
93
+ value={value}
94
+ visible
95
+ languageData={languageData}
96
+ layoutState={layoutState}
97
+ actionButtons={actionButtons}
98
+ />
99
+ </AnimationComponent>
100
+ </View>
101
+ );
102
+ };
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ import { View } from "react-native";
3
+
4
+ import { withDebug } from "../debug";
5
+
6
+ export const VerticalGutter = ({ height }: { height: number }) => (
7
+ <View style={{ height, ...withDebug({ backgroundColor: "red" }) }} />
8
+ );
@@ -0,0 +1,20 @@
1
+ export const PADDING_HORIZONTAL = 15;
2
+
3
+ export const TABLET_VERTICAL_SEPARATOR_HEIGHT = 32;
4
+
5
+ export const TABLET_LANDSCAPE = {
6
+ MARGIN_VERTICAL: 80, // gap between safe-area edge and audio-player container
7
+ MAX_HEIGHT: 750,
8
+ };
9
+
10
+ export const TABLET_PORTRAIT = {
11
+ MARGIN_HORIZONTAL: 180,
12
+ MAX_WIDTH: 550,
13
+ };
14
+
15
+ export const MOBILE = {
16
+ MARGIN_HORIZONTAL: 30,
17
+ MARGIN_BOTTOM: 48,
18
+ MARGIN_TOP: 10,
19
+ MIN_HEIGHT: 458,
20
+ };
@@ -0,0 +1,6 @@
1
+ // use this variable to activate borders on AudioPlayer. It will help you to fix layout if needed.
2
+ const DEBUG = false;
3
+
4
+ export const withDebug = (style) => {
5
+ return DEBUG ? style : {};
6
+ };
@@ -0,0 +1,3 @@
1
+ export { useImageSize } from "./useImageSize";
2
+
3
+ export { useTransportInfoHeight } from "./useTransportInfoHeight";
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+ import { LayoutChangeEvent } from "react-native";
3
+
4
+ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
5
+ import { Subject } from "rxjs";
6
+ import { take, last } from "rxjs/operators";
7
+ import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
8
+
9
+ type Layout = {
10
+ x: number;
11
+ y: number;
12
+ width: number;
13
+ height: number;
14
+ };
15
+
16
+ const builder = () => {
17
+ const layoutChange$ = new Subject<Layout>();
18
+
19
+ const emitLayoutChange = (layout: Layout): void => {
20
+ layoutChange$.next(layout);
21
+ };
22
+
23
+ return {
24
+ emitLayoutChange,
25
+ layoutChange$,
26
+ };
27
+ };
28
+
29
+ type Props = {
30
+ isPortraitMode: boolean;
31
+ };
32
+
33
+ type Return = {
34
+ imageSize: Option<number>;
35
+ offsetY: Option<number>;
36
+ onLayout: (event: LayoutChangeEvent) => void;
37
+ };
38
+
39
+ export const useImageSize = ({ isPortraitMode }: Props): Return => {
40
+ const [imageSize, setImageSize] = React.useState(undefined);
41
+ const [offsetY, setOffsetY] = React.useState(undefined);
42
+
43
+ const layoutRef = useRefWithInitialValue<ReturnType<typeof builder>>(builder);
44
+
45
+ React.useEffect(() => {
46
+ const subscription = layoutRef.current.layoutChange$
47
+ // for landscape layout we need to wait the second layout change in order to get proper size of image
48
+ .pipe(take(isPortraitMode ? 1 : 2), last())
49
+ .subscribe({
50
+ next: (layout: Layout) => {
51
+ const size = isPortraitMode
52
+ ? Math.min(layout.height, layout.width)
53
+ : layout.height;
54
+
55
+ setImageSize(size);
56
+ },
57
+ });
58
+
59
+ return () => {
60
+ subscription.unsubscribe();
61
+ };
62
+ }, [isPortraitMode]);
63
+
64
+ React.useEffect(() => {
65
+ const subscription = layoutRef.current.layoutChange$
66
+ // wait the second layout change in order to get proper offsetY
67
+ .pipe(take(2), last())
68
+ .subscribe({
69
+ next: (layout: Layout) => {
70
+ setOffsetY(layout.y);
71
+ },
72
+ });
73
+
74
+ return () => {
75
+ subscription.unsubscribe();
76
+ };
77
+ }, []);
78
+
79
+ const handleLayout = React.useCallback((event: LayoutChangeEvent) => {
80
+ const height = toNumber(event?.nativeEvent?.layout?.height);
81
+ const width = toNumber(event?.nativeEvent?.layout?.width);
82
+ const y = toNumber(event?.nativeEvent?.layout?.y);
83
+ const x = toNumber(event?.nativeEvent?.layout?.x);
84
+
85
+ const layout: Layout = {
86
+ x,
87
+ y,
88
+ width,
89
+ height,
90
+ };
91
+
92
+ layoutRef.current.emitLayoutChange(layout);
93
+ }, []);
94
+
95
+ return {
96
+ imageSize,
97
+ offsetY,
98
+ onLayout: handleLayout,
99
+ };
100
+ };
@@ -0,0 +1,54 @@
1
+ import * as React from "react";
2
+ import { LayoutChangeEvent } from "react-native";
3
+ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
4
+ import { Subject } from "rxjs";
5
+ import { first } from "rxjs/operators";
6
+ import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
7
+
8
+ const builder = () => {
9
+ const layoutChange$ = new Subject<number>();
10
+
11
+ const emitLayoutChange = (height: number): void => {
12
+ layoutChange$.next(height);
13
+ };
14
+
15
+ return {
16
+ emitLayoutChange,
17
+ layoutChange$,
18
+ };
19
+ };
20
+
21
+ export const useTransportInfoHeight = () => {
22
+ const [height, setHeight] = React.useState(undefined);
23
+
24
+ const layoutRef = useRefWithInitialValue<ReturnType<typeof builder>>(builder);
25
+
26
+ React.useEffect(() => {
27
+ const subscription = layoutRef.current.layoutChange$
28
+ // skip first layout and take the second with proper size
29
+ // then ignore any layout update
30
+ .pipe(first())
31
+ .subscribe({
32
+ next: (size: number) => {
33
+ setHeight(size);
34
+ },
35
+ });
36
+
37
+ return () => {
38
+ subscription.unsubscribe();
39
+ };
40
+ }, []);
41
+
42
+ const handleLayout = React.useCallback((event: LayoutChangeEvent) => {
43
+ const height = toNumber(event?.nativeEvent?.layout?.height);
44
+
45
+ if (height) {
46
+ layoutRef.current.emitLayoutChange(height);
47
+ }
48
+ }, []);
49
+
50
+ return {
51
+ height,
52
+ onLayout: handleLayout,
53
+ };
54
+ };
@@ -1,52 +1,25 @@
1
- import React from "react";
2
- import DefaultLayout from "./DefaultLayout";
3
- import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
1
+ import * as React from "react";
4
2
 
5
- type Dimensions = {
6
- width: number;
7
- height: number;
8
- };
3
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
4
+ import { useIsTabletLandscape } from "@applicaster/zapp-react-native-utils/reactHooks/device/useMemoizedIsTablet";
9
5
 
10
- type LayoutProps = {
11
- entry: ZappEntry;
12
- layoutState: {
13
- inline: boolean;
14
- docked: boolean;
15
- isModal: boolean;
16
- };
17
- playerState: {
18
- currentTime: number;
19
- duration: number;
20
- seekableDuration: number;
21
- isLive: boolean;
22
- isPaused: boolean;
23
- isBuffering: boolean;
24
- };
25
- value: GetValue;
26
- sleepTimerDate: number;
27
- playbackSpeedName: string;
28
- playbackSleepName: string;
29
- configuration: { [key: string]: any };
30
- onPlaybackButtonPress: () => void;
31
- onRewindButtonPress: () => void;
32
- onForwardButtonPress: () => void;
33
- onSpeedButtonPress: () => void;
34
- onSleepButtonPress: () => void;
35
- onCloseVideoModal: () => void;
36
- cancelSleepTimer: () => void;
37
- onPlayerSeek: (targetTime: number) => void;
38
- onSkipStart: () => void;
39
- onSkipLive: () => void;
40
- maximiseVideoModal: () => void;
41
- dimensions: Dimensions;
42
- defaultDimensions: Dimensions;
43
- playNextData: PlayNextData;
44
- title: string | number;
45
- summary: string | number;
46
- };
6
+ import { TabletPortraitLayout } from "./TabletPortraitLayout";
7
+ import { TabletLandscapeLayout } from "./TabletLandscapeLayout";
8
+ import { MobileLayout } from "./MobileLayout";
47
9
 
48
- export default function Layout(
49
- props: LayoutProps
50
- ): React.ReactElement<LayoutProps> {
51
- return <DefaultLayout {...props} />;
10
+ import { Props } from "./types";
11
+
12
+ export default function Layout(props: Props) {
13
+ const isTablet = useIsTablet();
14
+ const isTabletLandscape = useIsTabletLandscape();
15
+
16
+ if (!isTablet) {
17
+ return <MobileLayout {...props} />;
18
+ }
19
+
20
+ if (isTabletLandscape) {
21
+ return <TabletLandscapeLayout {...props} />;
22
+ }
23
+
24
+ return <TabletPortraitLayout {...props} />;
52
25
  }
@@ -0,0 +1,7 @@
1
+ import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
2
+
3
+ export type Props = QuickBrickPlayer.AudioPlayerProps & {
4
+ playNextData: PlayNextData;
5
+ };
6
+
7
+ export { PlayNextData };
@@ -47,7 +47,7 @@ const fetchPaletteAndDominantColor = async (imageUrl: string) => {
47
47
  const dominant = await getDominantColor(imageUrl);
48
48
  const dominantColor = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
49
49
 
50
- return { gradientColors: colors.slice(0, 2), dominantColor };
50
+ return { gradientColors: colors.slice(0, 2).reverse(), dominantColor };
51
51
  };
52
52
 
53
53
  export const MagicBG: React.FC<MagicBGProps> = ({