@applicaster/quick-brick-player 13.0.0-alpha.1436249127 → 13.0.0-alpha.1486614302

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 (31) 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 +116 -136
  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 +109 -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/index.tsx +31 -19
  30. package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
  31. 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.1436249127",
3
+ "version": "13.0.0-alpha.1486614302",
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": "13.0.0-rc.13",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.1436249127",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.1436249127",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.1436249127",
44
- "keymirror": "^0.1.1",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-alpha.1486614302",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.1486614302",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.1486614302",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.1486614302",
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}>
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,5 @@
1
1
  import * as React from "react";
2
- import { ViewStyle, View } from "react-native";
2
+ import { ViewStyle, View, Dimensions as ScreenDimensions } from "react-native";
3
3
 
4
4
  import { SafeAreaView, Edge } from "react-native-safe-area-context";
5
5
  import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
@@ -17,13 +17,7 @@ import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Comp
17
17
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
18
  import { useEffect, useState } from "react";
19
19
  import { MagicBG } from "./MagicBG";
20
- import {
21
- formatSpeed,
22
- hasDatePassed,
23
- loadItemsFromStorage,
24
- NAMESPACE,
25
- } from "./utils";
26
- import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
20
+ import { formatSpeed } from "./utils";
27
21
  import { addQueryParam, getLiveUrl } from "../Utils";
28
22
  import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
29
23
  import {
@@ -31,9 +25,19 @@ import {
31
25
  ComponentAnimationType,
32
26
  AnimatedScrollModal,
33
27
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
34
- import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
28
+ import { PADDING_HORIZONTAL } from "./Layout/const";
35
29
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
36
30
 
31
+ import {
32
+ SleepTimerController,
33
+ SleepTimerState,
34
+ SLEEP_TIMER_OFF,
35
+ } from "./SleepTimerManager";
36
+ import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
37
+ import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
38
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
39
+ import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
40
+
37
41
  const { log_debug } = createLogger({
38
42
  subsystem: "UIComponent",
39
43
  category: "AudioPlayerWrapper",
@@ -50,6 +54,7 @@ type Configuration = {
50
54
  [key: string]: any;
51
55
  magic_background?: boolean;
52
56
  audio_player_background_image_query?: string;
57
+ audio_player_background_image_default_color?: string;
53
58
  tablet_landscape_sidebar_width?: string;
54
59
  tablet_landscape_player_container_background_color?: string;
55
60
  };
@@ -71,8 +76,7 @@ type Props = {
71
76
  isTabletPortrait?: boolean;
72
77
  };
73
78
 
74
- export const defaultAspectRatioWidth = (height: number): number =>
75
- (height / 9) * 16;
79
+ const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
76
80
 
77
81
  const Styles = {
78
82
  mainContainer: (style: ViewStyle) => {
@@ -98,7 +102,11 @@ const Styles = {
98
102
  height: 96,
99
103
  marginHorizontal: 24,
100
104
  },
101
- playerDetails: { marginTop: 60 },
105
+ playerDetails: {
106
+ marginTop: 60,
107
+ alignSelf: "center",
108
+ width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
109
+ },
102
110
 
103
111
  linearGradient: {
104
112
  flex: 1,
@@ -116,9 +124,20 @@ const sleeps = [
116
124
  { label: "2 hours", value: 120 },
117
125
  ];
118
126
 
127
+ const speeds = [
128
+ { label: "0.8x", value: 0.8 },
129
+ { label: "1x", value: 1.0 },
130
+ { label: "1.2x", value: 1.2 },
131
+ { label: "1.5x", value: 1.5 },
132
+ { label: "2x", value: 2.0 },
133
+ ];
134
+
135
+ const edges: Edge[] = ["right", "left", "top"];
136
+
119
137
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
120
138
  const player = usePlayer();
121
139
  const playerState = usePlayerState("audio-player-wrapper");
140
+ const theme = useTheme();
122
141
 
123
142
  const {
124
143
  docked,
@@ -131,14 +150,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
131
150
  isTabletPortrait,
132
151
  } = props;
133
152
 
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
153
  const isTablet = useIsTablet();
143
154
 
144
155
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
@@ -154,17 +165,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
154
165
  configuration?.audio_live_player_update_interval
155
166
  );
156
167
 
157
- const [playbackSpeed, setPlaybackSpeed] = useState({
158
- asset: configuration["speed_1"],
159
- value: 1.0,
160
- });
168
+ const [sleepTimerManager, setSleepTimerManager] =
169
+ useState<SleepTimerController | null>(null);
161
170
 
162
- const [sleepTimer, setSleepTimer] = useState({
163
- label: "playback_sleep_off_label",
164
- value: 0,
165
- });
171
+ const [speedManager, setSpeedManager] = useState<SpeedController | null>(
172
+ null
173
+ );
174
+
175
+ useEffect(() => {
176
+ if (player) {
177
+ setSleepTimerManager(new SleepTimerController(player));
178
+ setSpeedManager(new SpeedController(player));
179
+ } else {
180
+ setSleepTimerManager(null);
181
+ setSpeedManager(null);
182
+ }
183
+ }, [player]);
184
+
185
+ const [playbackSpeed, setPlaybackSpeed] =
186
+ useState<PlaybackSpeed>(DEFAULT_SPEED);
166
187
 
167
- const [sleepTimerDate, setSleepTimerDate] = useState(0);
188
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
189
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
190
+ );
168
191
 
169
192
  const [defaultDimensions, setDefaultDimensions] =
170
193
  React.useState<Dimensions>(undefined);
@@ -174,8 +197,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
174
197
  height: 0,
175
198
  });
176
199
 
177
- const navigator = useNavigation();
178
-
179
200
  const { maximiseVideoModal } = useNavigation();
180
201
 
181
202
  const onLayout = (event: any) => {
@@ -189,82 +210,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
210
  if (!defaultDimensions) setDefaultDimensions({ width, height });
190
211
  };
191
212
 
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
213
  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
- };
214
+ const sleepSubscription =
215
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
253
216
 
254
- const loadSleep = async () => {
255
- const sleep = await loadItemsFromStorage("sleep");
217
+ const speedSubscription =
218
+ speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
256
219
 
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
- }
220
+ return () => {
221
+ sleepSubscription?.unsubscribe();
222
+ speedSubscription?.unsubscribe();
263
223
  };
224
+ }, [sleepTimerManager, speedManager]);
264
225
 
265
- loadSpeed().then();
266
- loadSleep().then();
267
- }, [playerState.isReadyToPlay]);
226
+ useEffect(() => {
227
+ if (!playerState.isReadyToPlay) {
228
+ return;
229
+ }
230
+
231
+ void speedManager?.loadSpeed();
232
+ void sleepTimerManager?.loadSleep();
233
+ }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
268
234
 
269
235
  const getValue = (key: string, stylesObj: {}) =>
270
236
  R.propOr(null, key, stylesObj);
@@ -292,7 +258,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
292
258
 
293
259
  const onPlaybackButtonPress = React.useCallback(() => {
294
260
  if (playerState.isBuffering) return;
295
- playerState.isPaused ? player?.play() : player.pause();
261
+ playerState.isPaused ? player?.play() : player?.pause();
296
262
  }, [playerState, player]);
297
263
 
298
264
  const seekDuration = toNumberWithDefault(10, value("seek_duration"));
@@ -307,30 +273,20 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
307
273
 
308
274
  const onSpeedSelect = React.useCallback(
309
275
  async (item) => {
310
- setPlaybackSpeed(item);
311
- player.setPlaybackRate(item.value);
312
- await localStorage.setItem("speed", item, NAMESPACE);
276
+ await speedManager?.setPlaybackSpeed(item);
313
277
  },
314
- [player, setPlaybackSpeed]
278
+ [speedManager]
315
279
  );
316
280
 
317
281
  const onSleepSelect = React.useCallback(
318
282
  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
- );
283
+ await sleepTimerManager?.setSleepTimer(item);
328
284
  },
329
- [player, setSleepTimer]
285
+ [sleepTimerManager]
330
286
  );
331
287
 
332
288
  const onSpeedButtonPress = React.useCallback(() => {
333
- navigator.openModalBottomSheet({
289
+ openBottomSheetModal({
334
290
  modalBottomSheetContentProps: {
335
291
  items: speeds,
336
292
  current_selection: playbackSpeed,
@@ -341,15 +297,23 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
341
297
  supportedOrientations: ["portrait"],
342
298
  },
343
299
  });
344
- }, [playbackSpeed, setPlaybackSpeed]);
300
+ }, [
301
+ playbackSpeed,
302
+ setPlaybackSpeed,
303
+ onSpeedSelect,
304
+ configuration["playback_speed_title"],
305
+ ]);
345
306
 
346
307
  const onSleepButtonPress = React.useCallback(() => {
347
- navigator.openModalBottomSheet({
308
+ openBottomSheetModal({
348
309
  modalBottomSheetContentProps: {
349
310
  items: [
350
- { label: configuration["playback_sleep_off_label"], value: 0 },
311
+ {
312
+ label: configuration["playback_sleep_off_label"],
313
+ value: 0,
314
+ },
351
315
  ].concat(sleeps),
352
- current_selection: sleepTimer,
316
+ current_selection: sleepTimerState.timer,
353
317
  onPress: onSleepSelect,
354
318
  title: configuration["playback_sleep_title"],
355
319
  },
@@ -357,7 +321,20 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
357
321
  supportedOrientations: ["portrait"],
358
322
  },
359
323
  });
360
- }, [sleepTimer, setSleepTimer, configuration]);
324
+ }, [
325
+ sleepTimerState.timer,
326
+ configuration["playback_sleep_title"],
327
+ configuration["playback_sleep_off_label"],
328
+ onSleepSelect,
329
+ ]);
330
+
331
+ // This method does not actually cancel the sleep timer
332
+ // (and should not to avoid race condition),
333
+ // it just performs the cleanup when timer expires
334
+ // TODO: rename it in code above
335
+ const cancelSleepTimer = React.useCallback(() => {
336
+ sleepTimerManager?.onTimerExpired();
337
+ }, [sleepTimerManager]);
361
338
 
362
339
  const onCloseVideoModal = React.useCallback(() => {
363
340
  playerManager.close();
@@ -378,13 +355,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
378
355
  player?.seekTo(0);
379
356
  }, [player]);
380
357
 
381
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
382
-
383
- const ContainerView = SafeAreaView;
384
-
385
358
  const MainContent = React.useMemo(() => {
386
359
  return (
387
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
360
+ <SafeAreaView style={Styles.mainContainer(style)} edges={edges}>
388
361
  <AnimationComponent
389
362
  animationType={ComponentAnimationType.moveUpComponent}
390
363
  additionalData={{ saveArea: true }}
@@ -406,9 +379,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
406
379
  onSleepButtonPress={onSleepButtonPress}
407
380
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
408
381
  playbackSleepName={
409
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
382
+ sleepTimerState.timer.value === 0
383
+ ? "sleep_timer"
384
+ : "sleep_timer_active"
410
385
  }
411
- sleepTimerDate={sleepTimerDate}
386
+ sleepTimerDate={sleepTimerState.timerDate}
412
387
  cancelSleepTimer={cancelSleepTimer}
413
388
  onSkipLive={onSkipLive}
414
389
  onCloseVideoModal={onCloseVideoModal}
@@ -437,7 +412,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
437
412
  </View>
438
413
  </AnimatedScrollModal>
439
414
  </AnimationComponent>
440
- </ContainerView>
415
+ </SafeAreaView>
441
416
  );
442
417
  }, [
443
418
  docked,
@@ -458,26 +433,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
458
433
  playNextData,
459
434
  onSkipStart,
460
435
  onSkipLive,
461
- sleepTimer.value,
462
- sleepTimerDate,
436
+ sleepTimerState.timer.value,
437
+ sleepTimerState.timerDate,
463
438
  ]);
464
439
 
465
440
  if (configuration.magic_background) {
466
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
441
+ const baseUrl = imageSrcFromMediaItem(entry, null);
467
442
 
468
443
  const queryParamName =
469
444
  configuration.audio_player_background_image_query || "width";
470
445
 
446
+ const defaultBackgroundColor =
447
+ configuration.audio_player_background_image_default_color ||
448
+ theme?.app_background_color;
449
+
471
450
  const queryParamValue = 15;
472
451
 
473
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
452
+ const url =
453
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
474
454
 
475
455
  return (
476
456
  <MagicBG
477
457
  image={url}
478
458
  fallbackUrl={baseUrl}
479
459
  colors={entry?.extensions?.backgroundColors}
480
- defaultColor="#000000"
460
+ defaultColor={defaultBackgroundColor}
481
461
  >
482
462
  {MainContent}
483
463
  </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
+ };