@applicaster/quick-brick-player 13.0.0-alpha.5718191297 → 13.0.0-alpha.5790181722

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 +8 -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 +120 -198
  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} +3 -13
  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 +101 -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 +10 -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 +29 -17
  34. package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
  35. 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-alpha.5718191297",
3
+ "version": "13.0.0-alpha.5790181722",
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.28",
40
- "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.26",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.5718191297",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.5718191297",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.5718191297",
44
- "keymirror": "^0.1.1",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.95",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.5790181722",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.5790181722",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.5790181722",
45
43
  "query-string": "7.1.3",
46
44
  "react-native-web-linear-gradient": "1.1.2",
47
45
  "shaka-player": "4.3.5",
@@ -61,12 +59,9 @@
61
59
  "@applicaster/zapp-react-native-tvos-ui-components": "*",
62
60
  "@babel/runtime": "*",
63
61
  "@react-native-community/netinfo": "*",
64
- "@types/node": "*",
65
- "@types/react-native": "0.69.6",
66
62
  "immer": "*",
67
- "react": "17.0.2",
68
- "react-hot-loader": "*",
69
- "react-native": "0.68.6",
63
+ "react": "*",
64
+ "react-native": "*",
70
65
  "react-native-fs": "*",
71
66
  "react-native-gesture-handler": "*",
72
67
  "react-native-linear-gradient": "*",
@@ -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}>
84
+ <>
87
85
  <Text style={titleStyles(value, prefix)} numberOfLines={1}>
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";
@@ -15,31 +20,36 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
15
20
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
16
21
  import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/PlayerDetails";
17
22
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
- import { useCallback, useEffect, useState } from "react";
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";
36
42
  import {
37
- speed_0_8,
38
- speed_1,
39
- speed_1_2,
40
- speed_1_5,
41
- speed_2,
42
- } from "@applicaster/quick-brick-mobile-transport-controls";
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 { sleepTimerOptions } from "../Const/sleepTimerOptions";
52
+ import { speedOptions } from "../Const/speedOptions";
43
53
 
44
54
  const { log_debug } = createLogger({
45
55
  subsystem: "UIComponent",
@@ -57,6 +67,7 @@ type Configuration = {
57
67
  [key: string]: any;
58
68
  magic_background?: boolean;
59
69
  audio_player_background_image_query?: string;
70
+ audio_player_background_image_default_color?: string;
60
71
  tablet_landscape_sidebar_width?: string;
61
72
  tablet_landscape_player_container_background_color?: string;
62
73
  };
@@ -78,54 +89,26 @@ type Props = {
78
89
  isTabletPortrait?: boolean;
79
90
  };
80
91
 
81
- export const defaultAspectRatioWidth = (height: number): number =>
82
- (height / 9) * 16;
83
-
84
- const Styles = {
85
- mainContainer: (style: ViewStyle) => {
86
- const combinedStyle: ViewStyle = {
87
- flex: 1,
88
- };
92
+ const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
89
93
 
90
- return {
91
- ...combinedStyle,
92
- ...style,
93
- };
94
- },
94
+ const Styles = StyleSheet.create({
95
95
  playerContainer: {
96
96
  flex: 1,
97
97
  },
98
98
  paddingHorizontal: { paddingTop: 0, paddingHorizontal: PADDING_HORIZONTAL },
99
-
100
- controlsMobile: {
101
- height: 50,
102
- marginHorizontal: 24,
103
- },
104
- controlsTablet: {
105
- height: 96,
106
- marginHorizontal: 24,
107
- },
108
- playerDetails: { marginTop: 60 },
109
-
110
- linearGradient: {
111
- flex: 1,
99
+ playerDetails: {
100
+ marginTop: 60,
101
+ alignSelf: "center",
102
+ width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
112
103
  },
113
- };
104
+ });
114
105
 
115
- const sleeps = [
116
- // { label: "End of episode", value: "episode" },
117
- { label: "5 minutes", value: 5 },
118
- { label: "10 minutes", value: 10 },
119
- { label: "15 minutes", value: 15 },
120
- { label: "30 minutes", value: 30 },
121
- { label: "45 minutes", value: 45 },
122
- { label: "1 hour", value: 60 },
123
- { label: "2 hours", value: 120 },
124
- ];
106
+ const edges: Edge[] = ["right", "left", "top"];
125
107
 
126
108
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
127
109
  const player = usePlayer();
128
110
  const playerState = usePlayerState("audio-player-wrapper");
111
+ const theme = useTheme();
129
112
 
130
113
  const {
131
114
  docked,
@@ -138,40 +121,39 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
138
121
  isTabletPortrait,
139
122
  } = props;
140
123
 
141
- const speeds = [
142
- { asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
143
- { asset: configuration["speed_1"] || speed_1, value: 1.0 },
144
- { asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
145
- { asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
146
- { asset: configuration["speed_2"] || speed_2, value: 2.0 },
147
- ];
148
-
149
124
  const isTablet = useIsTablet();
150
125
 
151
126
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
152
127
 
153
- // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
154
-
155
- const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
156
-
157
128
  const { title, summary } = useTitleSummaryOverlay(
158
- feedUrl,
159
129
  entry,
160
130
  playerState?.isLive,
161
131
  configuration?.audio_live_player_update_interval
162
132
  );
163
133
 
164
- const [playbackSpeed, setPlaybackSpeed] = useState({
165
- asset: configuration["speed_1"],
166
- value: 1.0,
167
- });
134
+ const [sleepTimerManager, setSleepTimerManager] =
135
+ useState<SleepTimerController | null>(null);
168
136
 
169
- const [sleepTimer, setSleepTimer] = useState({
170
- label: "playback_sleep_off_label",
171
- value: 0,
172
- });
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]);
150
+
151
+ const [playbackSpeed, setPlaybackSpeed] =
152
+ useState<PlaybackSpeed>(DEFAULT_SPEED);
173
153
 
174
- const [sleepTimerDate, setSleepTimerDate] = useState(0);
154
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
155
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
156
+ );
175
157
 
176
158
  const [defaultDimensions, setDefaultDimensions] =
177
159
  React.useState<Dimensions>(undefined);
@@ -181,8 +163,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
181
163
  height: 0,
182
164
  });
183
165
 
184
- const navigator = useNavigation();
185
-
186
166
  const { maximiseVideoModal } = useNavigation();
187
167
 
188
168
  const onLayout = (event: any) => {
@@ -196,90 +176,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
196
176
  if (!defaultDimensions) setDefaultDimensions({ width, height });
197
177
  };
198
178
 
199
- const cancelSleepTimer = useCallback(async () => {
200
- player?.cancelSleepTimer();
201
-
202
- setSleepTimer({
203
- label: "playback_sleep_off_label",
204
- value: 0,
205
- });
206
-
207
- setSleepTimerDate(0);
208
-
209
- await localStorage.removeItem("sleep", NAMESPACE);
210
- }, [player, setSleepTimer, setSleepTimerDate]);
211
-
212
- const startSleepTimer = useCallback(
213
- (timerValue: number) => {
214
- try {
215
- const futureSleepDate = Date.now() + timerValue * 60000;
216
-
217
- player?.cancelSleepTimer?.();
218
-
219
- player?.startSleepTimer?.(futureSleepDate);
220
-
221
- return futureSleepDate;
222
- } catch (e) {
223
- return 0;
224
- }
225
- },
226
- [player]
227
- );
228
-
229
- const handleSleep = useCallback(
230
- async (timerValue: number, currentFutureSleepDate = null) => {
231
- if (currentFutureSleepDate) {
232
- if (hasDatePassed(currentFutureSleepDate)) {
233
- await cancelSleepTimer();
234
-
235
- return 0;
236
- }
237
-
238
- return currentFutureSleepDate;
239
- }
240
-
241
- if (timerValue === 0) {
242
- await cancelSleepTimer();
243
-
244
- return timerValue;
245
- }
246
-
247
- return startSleepTimer(timerValue);
248
- },
249
- [startSleepTimer, cancelSleepTimer]
250
- );
251
-
252
179
  useEffect(() => {
253
- const loadSpeed = async () => {
254
- const speed = await loadItemsFromStorage("speed");
180
+ const sleepSubscription =
181
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
255
182
 
256
- if (playerState.isReadyToPlay && speed) {
257
- setPlaybackSpeed(speed);
258
- player?.setPlaybackRate(speed.value);
259
- }
260
- };
183
+ const speedSubscription =
184
+ speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
261
185
 
262
- const loadSleep = async () => {
263
- const sleep = await loadItemsFromStorage("sleep");
264
-
265
- if (playerState.isReadyToPlay && sleep) {
266
- const { futureSleepDate, ...rest } = sleep;
267
- setSleepTimer(rest);
268
- const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
269
- setSleepTimerDate(sleepTimerDate);
270
- }
186
+ return () => {
187
+ sleepSubscription?.unsubscribe();
188
+ speedSubscription?.unsubscribe();
271
189
  };
190
+ }, [sleepTimerManager, speedManager]);
272
191
 
273
- void loadSpeed();
274
- void loadSleep();
275
- }, [
276
- playerState.isReadyToPlay,
277
- handleSleep,
278
- setSleepTimer,
279
- setSleepTimerDate,
280
- setPlaybackSpeed,
281
- player,
282
- ]);
192
+ useEffect(() => {
193
+ if (!playerState.isReadyToPlay) {
194
+ return;
195
+ }
196
+
197
+ void speedManager?.loadSpeed();
198
+ void sleepTimerManager?.loadSleep();
199
+ }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
283
200
 
284
201
  const getValue = (key: string, stylesObj: {}) =>
285
202
  R.propOr(null, key, stylesObj);
@@ -322,73 +239,75 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
322
239
 
323
240
  const onSpeedSelect = React.useCallback(
324
241
  async (item) => {
325
- setPlaybackSpeed(item);
326
- player?.setPlaybackRate(item.value);
327
- await localStorage.setItem("speed", item, NAMESPACE);
242
+ await speedManager?.setPlaybackSpeed(item);
328
243
  },
329
- [player, setPlaybackSpeed]
244
+ [speedManager]
330
245
  );
331
246
 
332
247
  const onSleepSelect = React.useCallback(
333
248
  async (item) => {
334
- setSleepTimer(item);
335
- const futureSleepDate = await handleSleep(item.value);
336
- setSleepTimerDate(futureSleepDate);
337
-
338
- await localStorage.setItem(
339
- "sleep",
340
- { ...item, futureSleepDate },
341
- NAMESPACE
342
- );
249
+ await sleepTimerManager?.setSleepTimer(item);
343
250
  },
344
- [setSleepTimer, setSleepTimerDate, handleSleep]
251
+ [sleepTimerManager]
345
252
  );
346
253
 
347
254
  const onSpeedButtonPress = React.useCallback(() => {
348
- navigator.openModalBottomSheet({
255
+ openBottomSheetModal({
349
256
  modalBottomSheetContentProps: {
350
- items: speeds,
257
+ items: speedOptions,
351
258
  current_selection: playbackSpeed,
352
259
  onPress: onSpeedSelect,
353
260
  title: configuration["playback_speed_title"],
261
+ itemProps: getBottomSheetModalItemProps,
262
+ iconProps: getBottomSheetModalIconProps,
263
+ labelProps: getBottomSheetModalLabelProps,
264
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
265
+ getDefaultItemIcon: getBottomSheetModalDefaultItemIcon,
266
+ iconPlacement: "right",
354
267
  },
355
268
  options: {
356
269
  supportedOrientations: ["portrait"],
357
270
  },
358
271
  });
359
- }, [
360
- playbackSpeed,
361
- setPlaybackSpeed,
362
- navigator,
363
- onSpeedSelect,
364
- configuration["playback_speed_title"],
365
- ]);
272
+ }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
366
273
 
367
274
  const onSleepButtonPress = React.useCallback(() => {
368
- navigator.openModalBottomSheet({
275
+ openBottomSheetModal({
369
276
  modalBottomSheetContentProps: {
370
277
  items: [
371
278
  {
372
279
  label: configuration["playback_sleep_off_label"],
373
280
  value: 0,
374
281
  },
375
- ].concat(sleeps),
376
- current_selection: sleepTimer,
282
+ ].concat(sleepTimerOptions),
283
+ current_selection: sleepTimerState.timer,
377
284
  onPress: onSleepSelect,
378
285
  title: configuration["playback_sleep_title"],
286
+ itemProps: getBottomSheetModalItemProps,
287
+ iconProps: getBottomSheetModalIconProps,
288
+ labelProps: getBottomSheetModalLabelProps,
289
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
290
+ iconPlacement: "right",
379
291
  },
380
292
  options: {
381
293
  supportedOrientations: ["portrait"],
382
294
  },
383
295
  });
384
296
  }, [
385
- navigator,
386
- sleepTimer,
297
+ sleepTimerState.timer,
387
298
  configuration["playback_sleep_title"],
388
299
  configuration["playback_sleep_off_label"],
389
300
  onSleepSelect,
390
301
  ]);
391
302
 
303
+ // This method does not actually cancel the sleep timer
304
+ // (and should not to avoid race condition),
305
+ // it just performs the cleanup when timer expires
306
+ // TODO: rename it in code above
307
+ const cancelSleepTimer = React.useCallback(() => {
308
+ sleepTimerManager?.onTimerExpired();
309
+ }, [sleepTimerManager]);
310
+
392
311
  const onCloseVideoModal = React.useCallback(() => {
393
312
  playerManager.close();
394
313
  }, [playerManager]);
@@ -408,13 +327,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
408
327
  player?.seekTo(0);
409
328
  }, [player]);
410
329
 
411
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
412
-
413
- const ContainerView = SafeAreaView;
414
-
415
330
  const MainContent = React.useMemo(() => {
416
331
  return (
417
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
332
+ <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
418
333
  <AnimationComponent
419
334
  animationType={ComponentAnimationType.moveUpComponent}
420
335
  additionalData={{ saveArea: true }}
@@ -436,9 +351,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
436
351
  onSleepButtonPress={onSleepButtonPress}
437
352
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
438
353
  playbackSleepName={
439
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
354
+ sleepTimerState.timer.value === 0
355
+ ? "sleep_timer"
356
+ : "sleep_timer_active"
440
357
  }
441
- sleepTimerDate={sleepTimerDate}
358
+ sleepTimerDate={sleepTimerState.timerDate}
442
359
  cancelSleepTimer={cancelSleepTimer}
443
360
  onSkipLive={onSkipLive}
444
361
  onCloseVideoModal={onCloseVideoModal}
@@ -456,18 +373,18 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
456
373
  animationType={ComponentAnimationType.componentFade}
457
374
  >
458
375
  <PlayerDetails
376
+ isAudioPlayer
459
377
  configuration={configuration}
460
378
  style={Styles.playerDetails}
461
379
  entry={entry}
462
380
  isTabletLandscape={isTabletLandscape}
463
- isAudioPlayer={true}
464
381
  />
465
382
  </AnimationComponent>
466
383
  </View>
467
384
  </View>
468
385
  </AnimatedScrollModal>
469
386
  </AnimationComponent>
470
- </ContainerView>
387
+ </SafeAreaView>
471
388
  );
472
389
  }, [
473
390
  docked,
@@ -488,26 +405,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
488
405
  playNextData,
489
406
  onSkipStart,
490
407
  onSkipLive,
491
- sleepTimer.value,
492
- sleepTimerDate,
408
+ sleepTimerState.timer.value,
409
+ sleepTimerState.timerDate,
493
410
  ]);
494
411
 
495
412
  if (configuration.magic_background) {
496
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
413
+ const baseUrl = imageSrcFromMediaItem(entry, null);
497
414
 
498
415
  const queryParamName =
499
416
  configuration.audio_player_background_image_query || "width";
500
417
 
418
+ const defaultBackgroundColor =
419
+ configuration.audio_player_background_image_default_color ||
420
+ theme?.app_background_color;
421
+
501
422
  const queryParamValue = 15;
502
423
 
503
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
424
+ const url =
425
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
504
426
 
505
427
  return (
506
428
  <MagicBG
507
429
  image={url}
508
430
  fallbackUrl={baseUrl}
509
431
  colors={entry?.extensions?.backgroundColors}
510
- defaultColor="#000000"
432
+ defaultColor={defaultBackgroundColor}
511
433
  >
512
434
  {MainContent}
513
435
  </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
+ };