@applicaster/quick-brick-player 13.0.0-rc.10 → 13.0.0-rc.100

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 (37) hide show
  1. package/package.json +27 -13
  2. package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
  3. package/src/Player/AudioLayer/AudioDescription.tsx +5 -7
  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 +316 -0
  7. package/src/Player/AudioLayer/Layout/{defaultLayoutsConts.ts → Controls/utils.ts} +7 -25
  8. package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
  9. package/src/Player/AudioLayer/Layout/{DockedLayout.tsx → DockedControls/index.tsx} +44 -67
  10. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +231 -0
  11. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +83 -0
  12. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +25 -0
  13. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +101 -0
  14. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +26 -0
  15. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +216 -0
  16. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +224 -0
  17. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +114 -0
  18. package/src/Player/AudioLayer/Layout/VerticalGutter/index.tsx +8 -0
  19. package/src/Player/AudioLayer/Layout/const.ts +22 -0
  20. package/src/Player/AudioLayer/Layout/debug.ts +6 -0
  21. package/src/Player/AudioLayer/Layout/hooks/index.ts +5 -0
  22. package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
  23. package/src/Player/AudioLayer/Layout/hooks/useTabletLayoutImageSize.ts +105 -0
  24. package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +54 -0
  25. package/src/Player/AudioLayer/Layout/index.tsx +21 -48
  26. package/src/Player/AudioLayer/Layout/types.ts +7 -0
  27. package/src/Player/AudioLayer/MagicBG/index.tsx +1 -1
  28. package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
  29. package/src/Player/AudioLayer/SpeedManager.ts +52 -0
  30. package/src/Player/AudioLayer/utils.ts +27 -0
  31. package/src/Player/Const/sleepTimerOptions.ts +39 -0
  32. package/src/Player/Const/speedOptions.ts +4 -0
  33. package/src/Player/Utils/Utils.ts +0 -5
  34. package/src/Player/Utils/index.tsx +117 -0
  35. package/src/Player/index.tsx +31 -19
  36. package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
  37. package/src/Player/Helpers/VideoResizeMode.ts +0 -7
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "13.0.0-rc.10",
3
+ "version": "13.0.0-rc.100",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
7
7
  "scripts": {
8
- "test": "jest -w=2",
9
8
  "build": "echo 'skipping ts build for core packages'"
10
9
  },
11
10
  "files": [
@@ -36,12 +35,11 @@
36
35
  },
37
36
  "homepage": "https://github.com/applicaster/quickbrick#readme",
38
37
  "dependencies": {
39
- "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.6",
40
- "@applicaster/quick-brick-tv-transport-controls": "12.0.0-rc.112",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.10",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.10",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-rc.10",
44
- "keymirror": "^0.1.1",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.99",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.100",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.100",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-rc.100",
45
43
  "query-string": "7.1.3",
46
44
  "react-native-web-linear-gradient": "1.1.2",
47
45
  "shaka-player": "4.3.5",
@@ -51,12 +49,28 @@
51
49
  "videojs-contrib-quality-levels": "3.0.0"
52
50
  },
53
51
  "peerDependencies": {
52
+ "@applicaster/applicaster-types": "*",
53
+ "@applicaster/blur": "*",
54
54
  "@applicaster/quick-brick-xray": "*",
55
55
  "@applicaster/react-native-color-thief": "*",
56
- "@types/node": "*",
57
- "@types/react-native": "0.69.6",
58
- "react": "17.0.2",
59
- "react-native": "0.68.6",
60
- "react-native-linear-gradient": "*"
56
+ "@applicaster/zapp-pipes-v2-client": "*",
57
+ "@applicaster/zapp-react-dom-ui-components": "*",
58
+ "@applicaster/zapp-react-native-redux": "*",
59
+ "@applicaster/zapp-react-native-tvos-ui-components": "*",
60
+ "@babel/runtime": "*",
61
+ "@react-native-community/netinfo": "*",
62
+ "immer": "*",
63
+ "react": "*",
64
+ "react-native": "*",
65
+ "react-native-fs": "*",
66
+ "react-native-gesture-handler": "*",
67
+ "react-native-linear-gradient": "*",
68
+ "react-native-safe-area-context": "*",
69
+ "react-native-svg": "*",
70
+ "react-native-web": "*",
71
+ "react-native-webview": "*",
72
+ "uglify-js": "*",
73
+ "validate-color": "*",
74
+ "zustand": "*"
61
75
  }
62
76
  }
@@ -55,6 +55,11 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
55
55
  return;
56
56
  }
57
57
 
58
+ // initial state setup - show correct mute/unmute icon based on current state
59
+ if (player?.playerState?.isReadyToPlay) {
60
+ setImageUri(player.getIsMuted() ? asset.active : asset.inactive);
61
+ }
62
+
58
63
  // active action = muted, inactive = unmuted
59
64
  return player.addListener({
60
65
  id: liveImageButtonsListenerKey,
@@ -73,10 +78,17 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
73
78
  setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
74
79
  },
75
80
  onPlayerClose: () => {
76
- setImageUri(null);
81
+ if (isCurrentPlayer(playerId)) {
82
+ setImageUri(null);
83
+ }
77
84
  },
78
85
  onVideoEnd: () => {
79
- setImageUri(null);
86
+ if (isCurrentPlayer(playerId)) {
87
+ setImageUri(null);
88
+ }
89
+ },
90
+ onPlayerResume: () => {
91
+ setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
80
92
  },
81
93
  },
82
94
  });
@@ -93,7 +105,7 @@ const styles = StyleSheet.create({
93
105
  },
94
106
  });
95
107
 
96
- export function AssetComponent(props: CellActionAssetComponentProps) {
108
+ function AssetImage(props: CellActionAssetComponentProps) {
97
109
  const { cellUUID: playerId, asset } = props;
98
110
  const [imageUri, setImageUri] = useState<Asset>(null);
99
111
 
@@ -108,3 +120,5 @@ export function AssetComponent(props: CellActionAssetComponentProps) {
108
120
 
109
121
  return <View />;
110
122
  }
123
+
124
+ export const AssetComponent = React.memo(AssetImage);
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Text, TextStyle, View } from "react-native";
2
+ import { Text, TextStyle } from "react-native";
3
3
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
4
4
 
5
5
  type AudioDescriptionProps = {
@@ -13,8 +13,6 @@ type AudioDescriptionProps = {
13
13
  value: GetValue;
14
14
  };
15
15
 
16
- const containerStyles = { paddingTop: 32, paddingBottom: 12 };
17
-
18
16
  const titleStyles = (value: GetValue, prefix: string): TextStyle => ({
19
17
  textAlign: "left",
20
18
  color: value(`${prefix}title_color`),
@@ -83,13 +81,13 @@ export const AudioDescription = (props: AudioDescriptionProps) => {
83
81
  const prefix = layoutState.docked ? "docked_player_" : "";
84
82
 
85
83
  return (
86
- <View style={containerStyles}>
87
- <Text style={titleStyles(value, prefix)} numberOfLines={1}>
84
+ <>
85
+ <Text style={titleStyles(value, prefix)} numberOfLines={2}>
88
86
  {title}
89
87
  </Text>
90
- <Text style={summaryStyles(value, prefix)} numberOfLines={2}>
88
+ <Text style={summaryStyles(value, prefix)} numberOfLines={1}>
91
89
  {summary}
92
90
  </Text>
93
- </View>
91
+ </>
94
92
  );
95
93
  };
@@ -1,5 +1,10 @@
1
1
  import * as React from "react";
2
- import { ViewStyle, View } from "react-native";
2
+ import {
3
+ StyleSheet,
4
+ ViewStyle,
5
+ View,
6
+ Dimensions as ScreenDimensions,
7
+ } from "react-native";
3
8
 
4
9
  import { SafeAreaView, Edge } from "react-native-safe-area-context";
5
10
  import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
@@ -17,22 +22,34 @@ import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Comp
17
22
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
23
  import { useEffect, useState } from "react";
19
24
  import { MagicBG } from "./MagicBG";
25
+ import { formatSpeed } from "./utils";
20
26
  import {
21
- formatSpeed,
22
- hasDatePassed,
23
- loadItemsFromStorage,
24
- NAMESPACE,
25
- } from "./utils";
26
- import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
27
- import { addQueryParam, getLiveUrl } from "../Utils";
27
+ addQueryParam,
28
+ getBottomSheetModalDefaultItemIcon,
29
+ getBottomSheetModalIconProps,
30
+ getBottomSheetModalItemProps,
31
+ getBottomSheetModalLabelProps,
32
+ getBottomSheetModalSelectedItemIcon,
33
+ } from "../Utils";
28
34
  import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
29
35
  import {
30
36
  AnimationComponent,
31
37
  ComponentAnimationType,
32
38
  AnimatedScrollModal,
33
39
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
34
- import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
40
+ import { PADDING_HORIZONTAL } from "./Layout/const";
35
41
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
42
+ import {
43
+ SleepTimerController,
44
+ SleepTimerState,
45
+ SLEEP_TIMER_OFF,
46
+ } from "./SleepTimerManager";
47
+ import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
48
+ import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
49
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
50
+ import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
51
+ import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
52
+ import { speedOptions } from "../Const/speedOptions";
36
53
 
37
54
  const { log_debug } = createLogger({
38
55
  subsystem: "UIComponent",
@@ -50,6 +67,7 @@ type Configuration = {
50
67
  [key: string]: any;
51
68
  magic_background?: boolean;
52
69
  audio_player_background_image_query?: string;
70
+ audio_player_background_image_default_color?: string;
53
71
  tablet_landscape_sidebar_width?: string;
54
72
  tablet_landscape_player_container_background_color?: string;
55
73
  };
@@ -71,54 +89,26 @@ type Props = {
71
89
  isTabletPortrait?: boolean;
72
90
  };
73
91
 
74
- export const defaultAspectRatioWidth = (height: number): number =>
75
- (height / 9) * 16;
76
-
77
- const Styles = {
78
- mainContainer: (style: ViewStyle) => {
79
- const combinedStyle: ViewStyle = {
80
- flex: 1,
81
- };
92
+ const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
82
93
 
83
- return {
84
- ...combinedStyle,
85
- ...style,
86
- };
87
- },
94
+ const Styles = StyleSheet.create({
88
95
  playerContainer: {
89
96
  flex: 1,
90
97
  },
91
98
  paddingHorizontal: { paddingTop: 0, paddingHorizontal: PADDING_HORIZONTAL },
92
-
93
- controlsMobile: {
94
- height: 50,
95
- marginHorizontal: 24,
96
- },
97
- controlsTablet: {
98
- height: 96,
99
- marginHorizontal: 24,
99
+ playerDetails: {
100
+ marginTop: 60,
101
+ alignSelf: "center",
102
+ width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
100
103
  },
101
- playerDetails: { marginTop: 60 },
102
-
103
- linearGradient: {
104
- flex: 1,
105
- },
106
- };
104
+ });
107
105
 
108
- const sleeps = [
109
- // { label: "End of episode", value: "episode" },
110
- { label: "5 minutes", value: 5 },
111
- { label: "10 minutes", value: 10 },
112
- { label: "15 minutes", value: 15 },
113
- { label: "30 minutes", value: 30 },
114
- { label: "45 minutes", value: 45 },
115
- { label: "1 hour", value: 60 },
116
- { label: "2 hours", value: 120 },
117
- ];
106
+ const edges: Edge[] = ["right", "left", "top"];
118
107
 
119
108
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
120
109
  const player = usePlayer();
121
110
  const playerState = usePlayerState("audio-player-wrapper");
111
+ const theme = useTheme();
122
112
 
123
113
  const {
124
114
  docked,
@@ -131,40 +121,39 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
131
121
  isTabletPortrait,
132
122
  } = props;
133
123
 
134
- const speeds = [
135
- { asset: configuration["speed_0_8"], value: 0.8 },
136
- { asset: configuration["speed_1"], value: 1.0 },
137
- { asset: configuration["speed_1_2"], value: 1.2 },
138
- { asset: configuration["speed_1_5"], value: 1.5 },
139
- { asset: configuration["speed_2"], value: 2.0 },
140
- ];
141
-
142
124
  const isTablet = useIsTablet();
143
125
 
144
126
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
145
127
 
146
- // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
147
-
148
- const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
149
-
150
128
  const { title, summary } = useTitleSummaryOverlay(
151
- feedUrl,
152
129
  entry,
153
130
  playerState?.isLive,
154
131
  configuration?.audio_live_player_update_interval
155
132
  );
156
133
 
157
- const [playbackSpeed, setPlaybackSpeed] = useState({
158
- asset: configuration["speed_1"],
159
- value: 1.0,
160
- });
134
+ const [sleepTimerManager, setSleepTimerManager] =
135
+ useState<SleepTimerController | null>(null);
161
136
 
162
- const [sleepTimer, setSleepTimer] = useState({
163
- label: "playback_sleep_off_label",
164
- value: 0,
165
- });
137
+ const [speedManager, setSpeedManager] = useState<SpeedController | null>(
138
+ null
139
+ );
140
+
141
+ useEffect(() => {
142
+ if (player) {
143
+ setSleepTimerManager(new SleepTimerController(player));
144
+ setSpeedManager(new SpeedController(player));
145
+ } else {
146
+ setSleepTimerManager(null);
147
+ setSpeedManager(null);
148
+ }
149
+ }, [player]);
166
150
 
167
- const [sleepTimerDate, setSleepTimerDate] = useState(0);
151
+ const [playbackSpeed, setPlaybackSpeed] =
152
+ useState<PlaybackSpeed>(DEFAULT_SPEED);
153
+
154
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
155
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
156
+ );
168
157
 
169
158
  const [defaultDimensions, setDefaultDimensions] =
170
159
  React.useState<Dimensions>(undefined);
@@ -174,8 +163,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
174
163
  height: 0,
175
164
  });
176
165
 
177
- const navigator = useNavigation();
178
-
179
166
  const { maximiseVideoModal } = useNavigation();
180
167
 
181
168
  const onLayout = (event: any) => {
@@ -189,82 +176,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
176
  if (!defaultDimensions) setDefaultDimensions({ width, height });
190
177
  };
191
178
 
192
- const cancelSleepTimer = async () => {
193
- player?.cancelSleepTimer();
194
-
195
- setSleepTimer({
196
- label: "playback_sleep_off_label",
197
- value: 0,
198
- });
199
-
200
- setSleepTimerDate(0);
201
-
202
- await localStorage.removeItem("sleep", NAMESPACE);
203
- };
204
-
205
- const startSleepTimer = (timerValue: number) => {
206
- try {
207
- const currentDate = new Date();
208
- const futureDate = new Date(currentDate.getTime() + timerValue * 60000);
209
- const futureSleepDate = futureDate.getTime();
210
-
211
- player?.cancelSleepTimer();
212
-
213
- player?.startSleepTimer(futureSleepDate);
214
-
215
- return futureSleepDate;
216
- } catch (e) {
217
- return 0;
218
- }
219
- };
220
-
221
- const handleSleep = async (
222
- timerValue: number,
223
- currentFutureSleepDate = null
224
- ) => {
225
- if (currentFutureSleepDate) {
226
- if (hasDatePassed(currentFutureSleepDate)) {
227
- await cancelSleepTimer();
228
-
229
- return 0;
230
- }
231
-
232
- return currentFutureSleepDate;
233
- }
234
-
235
- if (timerValue === 0) {
236
- await cancelSleepTimer();
237
-
238
- return timerValue;
239
- }
240
-
241
- return startSleepTimer(timerValue);
242
- };
243
-
244
179
  useEffect(() => {
245
- const loadSpeed = async () => {
246
- const speed = await loadItemsFromStorage("speed");
247
-
248
- if (playerState.isReadyToPlay && speed) {
249
- setPlaybackSpeed(speed);
250
- player?.setPlaybackRate(speed.value);
251
- }
252
- };
180
+ const sleepSubscription =
181
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
253
182
 
254
- const loadSleep = async () => {
255
- const sleep = await loadItemsFromStorage("sleep");
183
+ const speedSubscription =
184
+ speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
256
185
 
257
- if (playerState.isReadyToPlay && sleep) {
258
- const { futureSleepDate, ...rest } = sleep;
259
- setSleepTimer(rest);
260
- const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
261
- setSleepTimerDate(sleepTimerDate);
262
- }
186
+ return () => {
187
+ sleepSubscription?.unsubscribe();
188
+ speedSubscription?.unsubscribe();
263
189
  };
190
+ }, [sleepTimerManager, speedManager]);
191
+
192
+ useEffect(() => {
193
+ if (!playerState.isReadyToPlay) {
194
+ return;
195
+ }
264
196
 
265
- loadSpeed().then();
266
- loadSleep().then();
267
- }, [playerState.isReadyToPlay]);
197
+ void speedManager?.loadSpeed();
198
+ void sleepTimerManager?.loadSleep();
199
+ }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
268
200
 
269
201
  const getValue = (key: string, stylesObj: {}) =>
270
202
  R.propOr(null, key, stylesObj);
@@ -292,7 +224,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
292
224
 
293
225
  const onPlaybackButtonPress = React.useCallback(() => {
294
226
  if (playerState.isBuffering) return;
295
- playerState.isPaused ? player?.play() : player.pause();
227
+ playerState.isPaused ? player?.play() : player?.pause();
296
228
  }, [playerState, player]);
297
229
 
298
230
  const seekDuration = toNumberWithDefault(10, value("seek_duration"));
@@ -307,57 +239,77 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
307
239
 
308
240
  const onSpeedSelect = React.useCallback(
309
241
  async (item) => {
310
- setPlaybackSpeed(item);
311
- player.setPlaybackRate(item.value);
312
- await localStorage.setItem("speed", item, NAMESPACE);
242
+ await speedManager?.setPlaybackSpeed(item);
313
243
  },
314
- [player, setPlaybackSpeed]
244
+ [speedManager]
315
245
  );
316
246
 
317
247
  const onSleepSelect = React.useCallback(
318
248
  async (item) => {
319
- setSleepTimer(item);
320
- const futureSleepDate = await handleSleep(item.value);
321
- setSleepTimerDate(futureSleepDate);
322
-
323
- await localStorage.setItem(
324
- "sleep",
325
- { ...item, futureSleepDate },
326
- NAMESPACE
327
- );
249
+ await sleepTimerManager?.setSleepTimer(item);
328
250
  },
329
- [player, setSleepTimer]
251
+ [sleepTimerManager]
330
252
  );
331
253
 
332
254
  const onSpeedButtonPress = React.useCallback(() => {
333
- navigator.openModalBottomSheet({
255
+ openBottomSheetModal({
334
256
  modalBottomSheetContentProps: {
335
- items: speeds,
257
+ items: speedOptions,
336
258
  current_selection: playbackSpeed,
337
259
  onPress: onSpeedSelect,
338
260
  title: configuration["playback_speed_title"],
261
+ itemProps: getBottomSheetModalItemProps,
262
+ iconProps: getBottomSheetModalIconProps,
263
+ labelProps: getBottomSheetModalLabelProps,
264
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
265
+ getDefaultItemIcon: getBottomSheetModalDefaultItemIcon,
266
+ iconPlacement: "right",
339
267
  },
340
268
  options: {
341
- supportedOrientations: ["portrait"],
269
+ supportedOrientations: [
270
+ "portrait",
271
+ "landscape",
272
+ "portrait-upside-down",
273
+ ],
342
274
  },
343
275
  });
344
- }, [playbackSpeed, setPlaybackSpeed]);
276
+ }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
345
277
 
346
278
  const onSleepButtonPress = React.useCallback(() => {
347
- navigator.openModalBottomSheet({
279
+ openBottomSheetModal({
348
280
  modalBottomSheetContentProps: {
349
- items: [
350
- { label: configuration["playback_sleep_off_label"], value: 0 },
351
- ].concat(sleeps),
352
- current_selection: sleepTimer,
281
+ items: getSleepTimerOptions(configuration),
282
+ current_selection: sleepTimerState.timer,
353
283
  onPress: onSleepSelect,
354
284
  title: configuration["playback_sleep_title"],
285
+ itemProps: getBottomSheetModalItemProps,
286
+ iconProps: getBottomSheetModalIconProps,
287
+ labelProps: getBottomSheetModalLabelProps,
288
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
289
+ iconPlacement: "right",
355
290
  },
356
291
  options: {
357
- supportedOrientations: ["portrait"],
292
+ supportedOrientations: [
293
+ "portrait",
294
+ "landscape",
295
+ "portrait-upside-down",
296
+ ],
358
297
  },
359
298
  });
360
- }, [sleepTimer, setSleepTimer, configuration]);
299
+ }, [
300
+ sleepTimerState.timer,
301
+ configuration["playback_sleep_title"],
302
+ configuration["playback_sleep_off_label"],
303
+ onSleepSelect,
304
+ ]);
305
+
306
+ // This method does not actually cancel the sleep timer
307
+ // (and should not to avoid race condition),
308
+ // it just performs the cleanup when timer expires
309
+ // TODO: rename it in code above
310
+ const cancelSleepTimer = React.useCallback(() => {
311
+ sleepTimerManager?.onTimerExpired();
312
+ }, [sleepTimerManager]);
361
313
 
362
314
  const onCloseVideoModal = React.useCallback(() => {
363
315
  playerManager.close();
@@ -378,13 +330,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
378
330
  player?.seekTo(0);
379
331
  }, [player]);
380
332
 
381
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
382
-
383
- const ContainerView = SafeAreaView;
384
-
385
333
  const MainContent = React.useMemo(() => {
386
334
  return (
387
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
335
+ <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
388
336
  <AnimationComponent
389
337
  animationType={ComponentAnimationType.moveUpComponent}
390
338
  additionalData={{ saveArea: true }}
@@ -406,9 +354,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
406
354
  onSleepButtonPress={onSleepButtonPress}
407
355
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
408
356
  playbackSleepName={
409
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
357
+ sleepTimerState.timer.value === 0
358
+ ? "sleep_timer"
359
+ : "sleep_timer_active"
410
360
  }
411
- sleepTimerDate={sleepTimerDate}
361
+ sleepTimerDate={sleepTimerState.timerDate}
412
362
  cancelSleepTimer={cancelSleepTimer}
413
363
  onSkipLive={onSkipLive}
414
364
  onCloseVideoModal={onCloseVideoModal}
@@ -426,18 +376,18 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
426
376
  animationType={ComponentAnimationType.componentFade}
427
377
  >
428
378
  <PlayerDetails
379
+ isAudioPlayer
429
380
  configuration={configuration}
430
381
  style={Styles.playerDetails}
431
382
  entry={entry}
432
383
  isTabletLandscape={isTabletLandscape}
433
- isAudioPlayer={true}
434
384
  />
435
385
  </AnimationComponent>
436
386
  </View>
437
387
  </View>
438
388
  </AnimatedScrollModal>
439
389
  </AnimationComponent>
440
- </ContainerView>
390
+ </SafeAreaView>
441
391
  );
442
392
  }, [
443
393
  docked,
@@ -458,26 +408,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
458
408
  playNextData,
459
409
  onSkipStart,
460
410
  onSkipLive,
461
- sleepTimer.value,
462
- sleepTimerDate,
411
+ sleepTimerState.timer.value,
412
+ sleepTimerState.timerDate,
463
413
  ]);
464
414
 
465
415
  if (configuration.magic_background) {
466
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
416
+ const baseUrl = imageSrcFromMediaItem(entry, null);
467
417
 
468
418
  const queryParamName =
469
419
  configuration.audio_player_background_image_query || "width";
470
420
 
421
+ const defaultBackgroundColor =
422
+ configuration.audio_player_background_image_default_color ||
423
+ theme?.app_background_color;
424
+
471
425
  const queryParamValue = 15;
472
426
 
473
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
427
+ const url =
428
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
474
429
 
475
430
  return (
476
431
  <MagicBG
477
432
  image={url}
478
433
  fallbackUrl={baseUrl}
479
434
  colors={entry?.extensions?.backgroundColors}
480
- defaultColor="#000000"
435
+ defaultColor={defaultBackgroundColor}
481
436
  >
482
437
  {MainContent}
483
438
  </MagicBG>
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+
4
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+
6
+ import { TABLET_VERTICAL_SEPARATOR_HEIGHT } from "../const";
7
+
8
+ import { withDebug } from "../debug";
9
+
10
+ const styles = StyleSheet.create({
11
+ flexingHeight: {
12
+ flex: 1,
13
+
14
+ ...withDebug({ backgroundColor: "red" }),
15
+ },
16
+ fixedHeight: {
17
+ height: TABLET_VERTICAL_SEPARATOR_HEIGHT,
18
+
19
+ ...withDebug({ backgroundColor: "red" }),
20
+ },
21
+ });
22
+
23
+ export const VerticalGutter = () => {
24
+ const isTablet = useIsTablet();
25
+
26
+ return <View style={isTablet ? styles.fixedHeight : styles.flexingHeight} />;
27
+ };