@applicaster/quick-brick-player 13.0.0-alpha.1338463240 → 13.0.0-alpha.1354246175

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.
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "13.0.0-alpha.1338463240",
3
+ "version": "13.0.0-alpha.1354246175",
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,11 +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.1338463240",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.1338463240",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.1338463240",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.88",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.1354246175",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.1354246175",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.1354246175",
44
43
  "keymirror": "^0.1.1",
45
44
  "query-string": "7.1.3",
46
45
  "react-native-web-linear-gradient": "1.1.2",
@@ -51,12 +50,28 @@
51
50
  "videojs-contrib-quality-levels": "3.0.0"
52
51
  },
53
52
  "peerDependencies": {
53
+ "@applicaster/applicaster-types": "*",
54
+ "@applicaster/blur": "*",
54
55
  "@applicaster/quick-brick-xray": "*",
55
56
  "@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": "*"
57
+ "@applicaster/zapp-pipes-v2-client": "*",
58
+ "@applicaster/zapp-react-dom-ui-components": "*",
59
+ "@applicaster/zapp-react-native-redux": "*",
60
+ "@applicaster/zapp-react-native-tvos-ui-components": "*",
61
+ "@babel/runtime": "*",
62
+ "@react-native-community/netinfo": "*",
63
+ "immer": "*",
64
+ "react": "*",
65
+ "react-native": "*",
66
+ "react-native-fs": "*",
67
+ "react-native-gesture-handler": "*",
68
+ "react-native-linear-gradient": "*",
69
+ "react-native-safe-area-context": "*",
70
+ "react-native-svg": "*",
71
+ "react-native-web": "*",
72
+ "react-native-webview": "*",
73
+ "uglify-js": "*",
74
+ "validate-color": "*",
75
+ "zustand": "*"
61
76
  }
62
77
  }
@@ -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,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,14 +22,15 @@ 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,
@@ -33,6 +39,17 @@ import {
33
39
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
34
40
  import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
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 { sleepTimerOptions } 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]);
264
191
 
265
- loadSpeed().then();
266
- loadSleep().then();
267
- }, [playerState.isReadyToPlay]);
192
+ useEffect(() => {
193
+ if (!playerState.isReadyToPlay) {
194
+ return;
195
+ }
196
+
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,74 @@ 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
269
  supportedOrientations: ["portrait"],
342
270
  },
343
271
  });
344
- }, [playbackSpeed, setPlaybackSpeed]);
272
+ }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
345
273
 
346
274
  const onSleepButtonPress = React.useCallback(() => {
347
- navigator.openModalBottomSheet({
275
+ openBottomSheetModal({
348
276
  modalBottomSheetContentProps: {
349
277
  items: [
350
- { label: configuration["playback_sleep_off_label"], value: 0 },
351
- ].concat(sleeps),
352
- current_selection: sleepTimer,
278
+ {
279
+ label: configuration["playback_sleep_off_label"],
280
+ value: 0,
281
+ },
282
+ ].concat(sleepTimerOptions),
283
+ current_selection: sleepTimerState.timer,
353
284
  onPress: onSleepSelect,
354
285
  title: configuration["playback_sleep_title"],
286
+ itemProps: getBottomSheetModalItemProps,
287
+ iconProps: getBottomSheetModalIconProps,
288
+ labelProps: getBottomSheetModalLabelProps,
289
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
290
+ iconPlacement: "right",
355
291
  },
356
292
  options: {
357
293
  supportedOrientations: ["portrait"],
358
294
  },
359
295
  });
360
- }, [sleepTimer, setSleepTimer, configuration]);
296
+ }, [
297
+ sleepTimerState.timer,
298
+ configuration["playback_sleep_title"],
299
+ configuration["playback_sleep_off_label"],
300
+ onSleepSelect,
301
+ ]);
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]);
361
310
 
362
311
  const onCloseVideoModal = React.useCallback(() => {
363
312
  playerManager.close();
@@ -378,13 +327,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
378
327
  player?.seekTo(0);
379
328
  }, [player]);
380
329
 
381
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
382
-
383
330
  const ContainerView = SafeAreaView;
384
331
 
385
332
  const MainContent = React.useMemo(() => {
386
333
  return (
387
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
334
+ <ContainerView style={[style, Styles.playerContainer]} edges={edges}>
388
335
  <AnimationComponent
389
336
  animationType={ComponentAnimationType.moveUpComponent}
390
337
  additionalData={{ saveArea: true }}
@@ -406,9 +353,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
406
353
  onSleepButtonPress={onSleepButtonPress}
407
354
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
408
355
  playbackSleepName={
409
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
356
+ sleepTimerState.timer.value === 0
357
+ ? "sleep_timer"
358
+ : "sleep_timer_active"
410
359
  }
411
- sleepTimerDate={sleepTimerDate}
360
+ sleepTimerDate={sleepTimerState.timerDate}
412
361
  cancelSleepTimer={cancelSleepTimer}
413
362
  onSkipLive={onSkipLive}
414
363
  onCloseVideoModal={onCloseVideoModal}
@@ -426,11 +375,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
426
375
  animationType={ComponentAnimationType.componentFade}
427
376
  >
428
377
  <PlayerDetails
378
+ isAudioPlayer
429
379
  configuration={configuration}
430
380
  style={Styles.playerDetails}
431
381
  entry={entry}
432
382
  isTabletLandscape={isTabletLandscape}
433
- isAudioPlayer={true}
434
383
  />
435
384
  </AnimationComponent>
436
385
  </View>
@@ -458,26 +407,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
458
407
  playNextData,
459
408
  onSkipStart,
460
409
  onSkipLive,
461
- sleepTimer.value,
462
- sleepTimerDate,
410
+ sleepTimerState.timer.value,
411
+ sleepTimerState.timerDate,
463
412
  ]);
464
413
 
465
414
  if (configuration.magic_background) {
466
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
415
+ const baseUrl = imageSrcFromMediaItem(entry, null);
467
416
 
468
417
  const queryParamName =
469
418
  configuration.audio_player_background_image_query || "width";
470
419
 
420
+ const defaultBackgroundColor =
421
+ configuration.audio_player_background_image_default_color ||
422
+ theme?.app_background_color;
423
+
471
424
  const queryParamValue = 15;
472
425
 
473
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
426
+ const url =
427
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
474
428
 
475
429
  return (
476
430
  <MagicBG
477
431
  image={url}
478
432
  fallbackUrl={baseUrl}
479
433
  colors={entry?.extensions?.backgroundColors}
480
- defaultColor="#000000"
434
+ defaultColor={defaultBackgroundColor}
481
435
  >
482
436
  {MainContent}
483
437
  </MagicBG>