@applicaster/quick-brick-player 13.0.0-alpha.2245033574 → 13.0.0-alpha.2308533304

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 +8 -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 +130 -205
  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} +5 -23
  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 +29 -17
  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-alpha.2245033574",
3
+ "version": "13.0.0-alpha.2308533304",
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-alpha.2245033574",
40
- "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.26",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.2245033574",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.2245033574",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.2245033574",
44
- "keymirror": "^0.1.1",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-alpha.2308533304",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.2308533304",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.2308533304",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.2308533304",
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}>
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";
@@ -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 { getSleepTimerOptions } 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,78 @@ 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
- supportedOrientations: ["portrait"],
269
+ supportedOrientations: [
270
+ "portrait",
271
+ "landscape",
272
+ "portrait-upside-down",
273
+ ],
357
274
  },
358
275
  });
359
- }, [
360
- playbackSpeed,
361
- setPlaybackSpeed,
362
- navigator,
363
- onSpeedSelect,
364
- configuration["playback_speed_title"],
365
- ]);
276
+ }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
366
277
 
367
278
  const onSleepButtonPress = React.useCallback(() => {
368
- navigator.openModalBottomSheet({
279
+ openBottomSheetModal({
369
280
  modalBottomSheetContentProps: {
370
- items: [
371
- {
372
- label: configuration["playback_sleep_off_label"],
373
- value: 0,
374
- },
375
- ].concat(sleeps),
376
- current_selection: sleepTimer,
281
+ items: getSleepTimerOptions(configuration),
282
+ current_selection: sleepTimerState.timer,
377
283
  onPress: onSleepSelect,
378
284
  title: configuration["playback_sleep_title"],
285
+ itemProps: getBottomSheetModalItemProps,
286
+ iconProps: getBottomSheetModalIconProps,
287
+ labelProps: getBottomSheetModalLabelProps,
288
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
289
+ iconPlacement: "right",
379
290
  },
380
291
  options: {
381
- supportedOrientations: ["portrait"],
292
+ supportedOrientations: [
293
+ "portrait",
294
+ "landscape",
295
+ "portrait-upside-down",
296
+ ],
382
297
  },
383
298
  });
384
299
  }, [
385
- navigator,
386
- sleepTimer,
300
+ sleepTimerState.timer,
387
301
  configuration["playback_sleep_title"],
388
302
  configuration["playback_sleep_off_label"],
389
303
  onSleepSelect,
390
304
  ]);
391
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]);
313
+
392
314
  const onCloseVideoModal = React.useCallback(() => {
393
315
  playerManager.close();
394
316
  }, [playerManager]);
@@ -408,13 +330,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
408
330
  player?.seekTo(0);
409
331
  }, [player]);
410
332
 
411
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
412
-
413
- const ContainerView = SafeAreaView;
414
-
415
333
  const MainContent = React.useMemo(() => {
416
334
  return (
417
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
335
+ <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
418
336
  <AnimationComponent
419
337
  animationType={ComponentAnimationType.moveUpComponent}
420
338
  additionalData={{ saveArea: true }}
@@ -436,9 +354,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
436
354
  onSleepButtonPress={onSleepButtonPress}
437
355
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
438
356
  playbackSleepName={
439
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
357
+ sleepTimerState.timer.value === 0
358
+ ? "sleep_timer"
359
+ : "sleep_timer_active"
440
360
  }
441
- sleepTimerDate={sleepTimerDate}
361
+ sleepTimerDate={sleepTimerState.timerDate}
442
362
  cancelSleepTimer={cancelSleepTimer}
443
363
  onSkipLive={onSkipLive}
444
364
  onCloseVideoModal={onCloseVideoModal}
@@ -456,18 +376,18 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
456
376
  animationType={ComponentAnimationType.componentFade}
457
377
  >
458
378
  <PlayerDetails
379
+ isAudioPlayer
459
380
  configuration={configuration}
460
381
  style={Styles.playerDetails}
461
382
  entry={entry}
462
383
  isTabletLandscape={isTabletLandscape}
463
- isAudioPlayer={true}
464
384
  />
465
385
  </AnimationComponent>
466
386
  </View>
467
387
  </View>
468
388
  </AnimatedScrollModal>
469
389
  </AnimationComponent>
470
- </ContainerView>
390
+ </SafeAreaView>
471
391
  );
472
392
  }, [
473
393
  docked,
@@ -488,26 +408,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
488
408
  playNextData,
489
409
  onSkipStart,
490
410
  onSkipLive,
491
- sleepTimer.value,
492
- sleepTimerDate,
411
+ sleepTimerState.timer.value,
412
+ sleepTimerState.timerDate,
493
413
  ]);
494
414
 
495
415
  if (configuration.magic_background) {
496
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
416
+ const baseUrl = imageSrcFromMediaItem(entry, null);
497
417
 
498
418
  const queryParamName =
499
419
  configuration.audio_player_background_image_query || "width";
500
420
 
421
+ const defaultBackgroundColor =
422
+ configuration.audio_player_background_image_default_color ||
423
+ theme?.app_background_color;
424
+
501
425
  const queryParamValue = 15;
502
426
 
503
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
427
+ const url =
428
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
504
429
 
505
430
  return (
506
431
  <MagicBG
507
432
  image={url}
508
433
  fallbackUrl={baseUrl}
509
434
  colors={entry?.extensions?.backgroundColors}
510
- defaultColor="#000000"
435
+ defaultColor={defaultBackgroundColor}
511
436
  >
512
437
  {MainContent}
513
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
+ };