@applicaster/quick-brick-player 13.0.0-rc.9 → 13.0.0-rc.91

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-rc.9",
3
+ "version": "13.0.0-rc.91",
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": "12.0.0-rc.112",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.9",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.9",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-rc.9",
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-rc.91",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.91",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-rc.91",
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,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 {
@@ -34,6 +28,15 @@ import {
34
28
  import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
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
+
37
40
  const { log_debug } = createLogger({
38
41
  subsystem: "UIComponent",
39
42
  category: "AudioPlayerWrapper",
@@ -50,6 +53,7 @@ type Configuration = {
50
53
  [key: string]: any;
51
54
  magic_background?: boolean;
52
55
  audio_player_background_image_query?: string;
56
+ audio_player_background_image_default_color?: string;
53
57
  tablet_landscape_sidebar_width?: string;
54
58
  tablet_landscape_player_container_background_color?: string;
55
59
  };
@@ -71,8 +75,7 @@ type Props = {
71
75
  isTabletPortrait?: boolean;
72
76
  };
73
77
 
74
- export const defaultAspectRatioWidth = (height: number): number =>
75
- (height / 9) * 16;
78
+ const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
76
79
 
77
80
  const Styles = {
78
81
  mainContainer: (style: ViewStyle) => {
@@ -98,7 +101,11 @@ const Styles = {
98
101
  height: 96,
99
102
  marginHorizontal: 24,
100
103
  },
101
- playerDetails: { marginTop: 60 },
104
+ playerDetails: {
105
+ marginTop: 60,
106
+ alignSelf: "center",
107
+ width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
108
+ },
102
109
 
103
110
  linearGradient: {
104
111
  flex: 1,
@@ -116,9 +123,20 @@ const sleeps = [
116
123
  { label: "2 hours", value: 120 },
117
124
  ];
118
125
 
126
+ const speeds = [
127
+ { label: "0.8x", value: 0.8 },
128
+ { label: "1x", value: 1.0 },
129
+ { label: "1.2x", value: 1.2 },
130
+ { label: "1.5x", value: 1.5 },
131
+ { label: "2x", value: 2.0 },
132
+ ];
133
+
134
+ const edges: Edge[] = ["right", "left", "top"];
135
+
119
136
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
120
137
  const player = usePlayer();
121
138
  const playerState = usePlayerState("audio-player-wrapper");
139
+ const theme = useTheme();
122
140
 
123
141
  const {
124
142
  docked,
@@ -131,14 +149,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
131
149
  isTabletPortrait,
132
150
  } = props;
133
151
 
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
152
  const isTablet = useIsTablet();
143
153
 
144
154
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
@@ -154,17 +164,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
154
164
  configuration?.audio_live_player_update_interval
155
165
  );
156
166
 
157
- const [playbackSpeed, setPlaybackSpeed] = useState({
158
- asset: configuration["speed_1"],
159
- value: 1.0,
160
- });
167
+ const [sleepTimerManager, setSleepTimerManager] =
168
+ useState<SleepTimerController | null>(null);
161
169
 
162
- const [sleepTimer, setSleepTimer] = useState({
163
- label: "playback_sleep_off_label",
164
- value: 0,
165
- });
170
+ const [speedManager, setSpeedManager] = useState<SpeedController | null>(
171
+ null
172
+ );
166
173
 
167
- const [sleepTimerDate, setSleepTimerDate] = useState(0);
174
+ useEffect(() => {
175
+ if (player) {
176
+ setSleepTimerManager(new SleepTimerController(player));
177
+ setSpeedManager(new SpeedController(player));
178
+ } else {
179
+ setSleepTimerManager(null);
180
+ setSpeedManager(null);
181
+ }
182
+ }, [player]);
183
+
184
+ const [playbackSpeed, setPlaybackSpeed] =
185
+ useState<PlaybackSpeed>(DEFAULT_SPEED);
186
+
187
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
188
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
189
+ );
168
190
 
169
191
  const [defaultDimensions, setDefaultDimensions] =
170
192
  React.useState<Dimensions>(undefined);
@@ -189,82 +211,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
211
  if (!defaultDimensions) setDefaultDimensions({ width, height });
190
212
  };
191
213
 
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
214
  useEffect(() => {
245
- const loadSpeed = async () => {
246
- const speed = await loadItemsFromStorage("speed");
215
+ const sleepSubscription =
216
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
247
217
 
248
- if (playerState.isReadyToPlay && speed) {
249
- setPlaybackSpeed(speed);
250
- player?.setPlaybackRate(speed.value);
251
- }
252
- };
218
+ const speedSubscription =
219
+ speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
253
220
 
254
- const loadSleep = async () => {
255
- const sleep = await loadItemsFromStorage("sleep");
256
-
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
- }
221
+ return () => {
222
+ sleepSubscription?.unsubscribe();
223
+ speedSubscription?.unsubscribe();
263
224
  };
225
+ }, [sleepTimerManager, speedManager]);
264
226
 
265
- loadSpeed().then();
266
- loadSleep().then();
267
- }, [playerState.isReadyToPlay]);
227
+ useEffect(() => {
228
+ if (!playerState.isReadyToPlay) {
229
+ return;
230
+ }
231
+
232
+ void speedManager?.loadSpeed();
233
+ void sleepTimerManager?.loadSleep();
234
+ }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
268
235
 
269
236
  const getValue = (key: string, stylesObj: {}) =>
270
237
  R.propOr(null, key, stylesObj);
@@ -292,7 +259,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
292
259
 
293
260
  const onPlaybackButtonPress = React.useCallback(() => {
294
261
  if (playerState.isBuffering) return;
295
- playerState.isPaused ? player?.play() : player.pause();
262
+ playerState.isPaused ? player?.play() : player?.pause();
296
263
  }, [playerState, player]);
297
264
 
298
265
  const seekDuration = toNumberWithDefault(10, value("seek_duration"));
@@ -307,26 +274,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
307
274
 
308
275
  const onSpeedSelect = React.useCallback(
309
276
  async (item) => {
310
- setPlaybackSpeed(item);
311
- player.setPlaybackRate(item.value);
312
- await localStorage.setItem("speed", item, NAMESPACE);
277
+ await speedManager?.setPlaybackSpeed(item);
313
278
  },
314
- [player, setPlaybackSpeed]
279
+ [speedManager]
315
280
  );
316
281
 
317
282
  const onSleepSelect = React.useCallback(
318
283
  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
- );
284
+ await sleepTimerManager?.setSleepTimer(item);
328
285
  },
329
- [player, setSleepTimer]
286
+ [sleepTimerManager]
330
287
  );
331
288
 
332
289
  const onSpeedButtonPress = React.useCallback(() => {
@@ -341,15 +298,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
341
298
  supportedOrientations: ["portrait"],
342
299
  },
343
300
  });
344
- }, [playbackSpeed, setPlaybackSpeed]);
301
+ }, [
302
+ playbackSpeed,
303
+ setPlaybackSpeed,
304
+ navigator,
305
+ onSpeedSelect,
306
+ configuration["playback_speed_title"],
307
+ ]);
345
308
 
346
309
  const onSleepButtonPress = React.useCallback(() => {
347
310
  navigator.openModalBottomSheet({
348
311
  modalBottomSheetContentProps: {
349
312
  items: [
350
- { label: configuration["playback_sleep_off_label"], value: 0 },
313
+ {
314
+ label: configuration["playback_sleep_off_label"],
315
+ value: 0,
316
+ },
351
317
  ].concat(sleeps),
352
- current_selection: sleepTimer,
318
+ current_selection: sleepTimerState.timer,
353
319
  onPress: onSleepSelect,
354
320
  title: configuration["playback_sleep_title"],
355
321
  },
@@ -357,7 +323,21 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
357
323
  supportedOrientations: ["portrait"],
358
324
  },
359
325
  });
360
- }, [sleepTimer, setSleepTimer, configuration]);
326
+ }, [
327
+ navigator,
328
+ sleepTimerState.timer,
329
+ configuration["playback_sleep_title"],
330
+ configuration["playback_sleep_off_label"],
331
+ onSleepSelect,
332
+ ]);
333
+
334
+ // This method does not actually cancel the sleep timer
335
+ // (and should not to avoid race condition),
336
+ // it just performs the cleanup when timer expires
337
+ // TODO: rename it in code above
338
+ const cancelSleepTimer = React.useCallback(() => {
339
+ sleepTimerManager?.onTimerExpired();
340
+ }, [sleepTimerManager]);
361
341
 
362
342
  const onCloseVideoModal = React.useCallback(() => {
363
343
  playerManager.close();
@@ -378,8 +358,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
378
358
  player?.seekTo(0);
379
359
  }, [player]);
380
360
 
381
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
382
-
383
361
  const ContainerView = SafeAreaView;
384
362
 
385
363
  const MainContent = React.useMemo(() => {
@@ -406,9 +384,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
406
384
  onSleepButtonPress={onSleepButtonPress}
407
385
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
408
386
  playbackSleepName={
409
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
387
+ sleepTimerState.timer.value === 0
388
+ ? "sleep_timer"
389
+ : "sleep_timer_active"
410
390
  }
411
- sleepTimerDate={sleepTimerDate}
391
+ sleepTimerDate={sleepTimerState.timerDate}
412
392
  cancelSleepTimer={cancelSleepTimer}
413
393
  onSkipLive={onSkipLive}
414
394
  onCloseVideoModal={onCloseVideoModal}
@@ -458,26 +438,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
458
438
  playNextData,
459
439
  onSkipStart,
460
440
  onSkipLive,
461
- sleepTimer.value,
462
- sleepTimerDate,
441
+ sleepTimerState.timer.value,
442
+ sleepTimerState.timerDate,
463
443
  ]);
464
444
 
465
445
  if (configuration.magic_background) {
466
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
446
+ const baseUrl = imageSrcFromMediaItem(entry, null);
467
447
 
468
448
  const queryParamName =
469
449
  configuration.audio_player_background_image_query || "width";
470
450
 
451
+ const defaultBackgroundColor =
452
+ configuration.audio_player_background_image_default_color ||
453
+ theme?.app_background_color;
454
+
471
455
  const queryParamValue = 15;
472
456
 
473
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
457
+ const url =
458
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
474
459
 
475
460
  return (
476
461
  <MagicBG
477
462
  image={url}
478
463
  fallbackUrl={baseUrl}
479
464
  colors={entry?.extensions?.backgroundColors}
480
- defaultColor="#000000"
465
+ defaultColor={defaultBackgroundColor}
481
466
  >
482
467
  {MainContent}
483
468
  </MagicBG>
@@ -1,13 +1,14 @@
1
1
  import * as React from "react";
2
+ import { useEffect, useMemo, useState } from "react";
2
3
  import * as R from "ramda";
3
- import { View } from "react-native";
4
+ import { View, Dimensions as ScreenDimensions } from "react-native";
4
5
  import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
5
6
 
6
7
  import {
7
- ProgressBar,
8
8
  Controls,
9
- TransportInfo,
10
9
  CountDown,
10
+ ProgressBar,
11
+ TransportInfo,
11
12
  } from "@applicaster/quick-brick-mobile-transport-controls";
12
13
 
13
14
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
@@ -15,26 +16,30 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
15
16
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
16
17
  import {
17
18
  forwardButton,
19
+ PADDING_HORIZONTAL,
18
20
  playbackButton,
19
21
  rewindButton,
20
- skipToStartButton,
21
22
  skipToLiveButton,
22
- PADDING_HORIZONTAL,
23
- speedButton,
23
+ skipToStartButton,
24
24
  sleepButton,
25
+ speedButton,
25
26
  } from "./defaultLayoutsConts";
26
27
  import { AudioDescription } from "../AudioDescription";
27
28
  import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
28
29
  import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
29
30
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
30
- import { useEffect, useMemo, useState } from "react";
31
31
  import {
32
- useModalAnimationContext,
33
32
  AnimationComponent,
34
33
  ComponentAnimationType,
34
+ useModalAnimationContext,
35
35
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
36
+ import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
36
37
  import DockedLayout from "./DockedLayout";
37
38
  import { isValidDate } from "../../Utils";
39
+ import { calculateBorderRadius } from "../utils";
40
+
41
+ const getAudioPlayerWidthForTablet = (isPortraitMode: boolean) =>
42
+ isPortraitMode ? 463 : 350;
38
43
 
39
44
  type Dimensions = {
40
45
  width: number;
@@ -79,6 +84,9 @@ type LayoutProps = {
79
84
  defaultDimensions: Dimensions;
80
85
  };
81
86
 
87
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } =
88
+ ScreenDimensions.get("screen");
89
+
82
90
  const Styles = {
83
91
  container: { flex: 1 },
84
92
  tabletContainer: (isPortraitMode: boolean) => ({
@@ -86,42 +94,63 @@ const Styles = {
86
94
  alignItems: "center",
87
95
  justifyContent: isPortraitMode ? "center" : "flex-start",
88
96
  }),
89
- playerImageWrapper: {
97
+ playerImageWrapper: (docked: boolean) => ({
90
98
  width: "100%",
91
99
  maxHeight: 750,
92
- marginTop: 80,
100
+ marginTop: docked ? 80 : 20,
101
+ }),
102
+ playerInfoWrapper: {
103
+ flex: 1,
104
+ alignItems: "center",
105
+ justifyContent: "space-between",
93
106
  },
94
- tabletLayout: {
95
- width: 463,
107
+ tabletLayout: (isPortraitMode: boolean) => ({
108
+ width: getAudioPlayerWidthForTablet(isPortraitMode),
96
109
  marginBottom: 80,
97
- },
110
+ paddingHorizontal: undefined, // override paddingHorizontal
111
+ }),
98
112
  controls: (isTablet: boolean, isLiveShowFullControls: boolean) => ({
99
113
  height: isTablet ? 64 : 50,
100
- marginHorizontal: 10,
114
+ marginHorizontal: isTablet ? undefined : 10,
101
115
  justifyContent: isLiveShowFullControls ? "space-between" : "space-evenly",
102
116
  }),
103
- backgroundImageContainer: (dimensions: Dimensions, isTablet: boolean) => {
104
- const mobileValue = dimensions.width
117
+ backgroundImageContainer: (
118
+ dimensions: Dimensions,
119
+ isTablet: boolean,
120
+ isPortraitMode: boolean
121
+ ) => {
122
+ if (isTablet) {
123
+ const size = getAudioPlayerWidthForTablet(isPortraitMode);
124
+
125
+ return {
126
+ width: size,
127
+ height: size,
128
+ };
129
+ }
130
+
131
+ const mobileValue = dimensions?.width
105
132
  ? dimensions.width - PADDING_HORIZONTAL * 2
106
- : 330;
133
+ : SCREEN_WIDTH - PADDING_HORIZONTAL * 2;
107
134
 
108
135
  return {
109
- width: isTablet ? 463 : mobileValue,
110
- height: isTablet ? 463 : mobileValue,
136
+ width: mobileValue,
137
+ height: mobileValue,
111
138
  };
112
139
  },
113
140
  backgroundImageContainerImageStyle: (
114
141
  isTablet: boolean,
115
142
  docked: boolean,
116
143
  startComponentsAnimation: boolean,
117
- isActiveGesture: boolean
144
+ isActiveGesture: boolean,
145
+ playerAnimationState: PlayerAnimationStateT
118
146
  ) => ({
119
- borderRadius:
120
- (startComponentsAnimation && isActiveGesture) || docked
121
- ? 0
122
- : isTablet
123
- ? 24
124
- : 10,
147
+ borderRadius: calculateBorderRadius(
148
+ isTablet,
149
+ docked,
150
+ startComponentsAnimation,
151
+ isActiveGesture,
152
+ playerAnimationState
153
+ ),
125
154
  }),
126
155
 
127
156
  paddingHorizontal: {
@@ -173,8 +202,7 @@ export default function DefaultLayout(props: LayoutProps) {
173
202
  } = props;
174
203
 
175
204
  const isTablet = useIsTablet();
176
- const { width, height } = dimensions;
177
- const isPortraitMode = width < height;
205
+ const isPortraitMode = SCREEN_WIDTH < SCREEN_HEIGHT;
178
206
  const isLiveContent = playerState.isLive;
179
207
  const docked = layoutState.docked;
180
208
 
@@ -330,12 +358,15 @@ export default function DefaultLayout(props: LayoutProps) {
330
358
 
331
359
  const defaultImageDimensions = React.useMemo(
332
360
  () =>
333
- defaultDimensions &&
334
- Styles.backgroundImageContainer(defaultDimensions, isTablet),
335
- [defaultDimensions, isTablet]
361
+ Styles.backgroundImageContainer(
362
+ defaultDimensions,
363
+ isTablet,
364
+ isPortraitMode
365
+ ),
366
+ [defaultDimensions, isTablet, isPortraitMode]
336
367
  );
337
368
 
338
- const { isActiveGesture, startComponentsAnimation } =
369
+ const { isActiveGesture, startComponentsAnimation, playerAnimationState } =
339
370
  useModalAnimationContext();
340
371
 
341
372
  return (
@@ -344,7 +375,10 @@ export default function DefaultLayout(props: LayoutProps) {
344
375
  isTablet ? Styles.tabletContainer(isPortraitMode) : Styles.container
345
376
  }
346
377
  >
347
- <AnimationComponent animationType={ComponentAnimationType.componentFade}>
378
+ <AnimationComponent
379
+ animationType={ComponentAnimationType.componentFade}
380
+ style={isTablet ? Styles.playerInfoWrapper : undefined}
381
+ >
348
382
  <TransportInfo
349
383
  content={entry}
350
384
  value={value}
@@ -356,13 +390,16 @@ export default function DefaultLayout(props: LayoutProps) {
356
390
  </AnimationComponent>
357
391
  <AnimationComponent
358
392
  animationType={ComponentAnimationType.moveUpComponent}
359
- style={isTablet ? Styles.playerImageWrapper : undefined}
393
+ style={isTablet ? Styles.playerImageWrapper(docked) : undefined}
360
394
  >
361
395
  <AnimationComponent
362
396
  animationType={ComponentAnimationType.moveComponentHorizontal}
363
397
  additionalData={{
364
398
  disableAnimatedComponent: !isTablet,
365
- moveValue: (dimensions.width - 463) / 2,
399
+ moveValue:
400
+ (dimensions.width -
401
+ getAudioPlayerWidthForTablet(isPortraitMode)) /
402
+ 2,
366
403
  extraAnimation: !isTablet
367
404
  ? {
368
405
  [ComponentAnimationType.audioPlayerPaddingHorizontal]: {
@@ -375,12 +412,17 @@ export default function DefaultLayout(props: LayoutProps) {
375
412
  >
376
413
  <PlayerImageBackground
377
414
  entry={entry}
378
- style={Styles.backgroundImageContainer(dimensions, isTablet)}
415
+ style={Styles.backgroundImageContainer(
416
+ dimensions,
417
+ isTablet,
418
+ isPortraitMode
419
+ )}
379
420
  imageStyle={Styles.backgroundImageContainerImageStyle(
380
421
  isTablet,
381
422
  docked,
382
423
  startComponentsAnimation,
383
- isActiveGesture
424
+ isActiveGesture,
425
+ playerAnimationState
384
426
  )}
385
427
  defaultImageDimensions={defaultImageDimensions}
386
428
  />
@@ -391,7 +433,7 @@ export default function DefaultLayout(props: LayoutProps) {
391
433
  <View
392
434
  style={[
393
435
  Styles.paddingHorizontal,
394
- isTablet ? Styles.tabletLayout : undefined,
436
+ isTablet ? Styles.tabletLayout(isPortraitMode) : undefined,
395
437
  ]}
396
438
  >
397
439
  {!docked && (
@@ -405,9 +447,10 @@ export default function DefaultLayout(props: LayoutProps) {
405
447
  layoutState={layoutState}
406
448
  />
407
449
  <Separator />
408
- {needsToShowProgressBar && (
450
+ {needsToShowProgressBar ? (
409
451
  <View>
410
452
  <ProgressBar
453
+ name={"audio-default-layout"}
411
454
  content={entry}
412
455
  value={value}
413
456
  visible={true}
@@ -422,17 +465,17 @@ export default function DefaultLayout(props: LayoutProps) {
422
465
  onSkipLive={onSkipLive}
423
466
  />
424
467
 
425
- {showCounter && timeLeft >= 0 && (
468
+ {showCounter && timeLeft >= 0 ? (
426
469
  <CountDown
427
470
  timeLeft={timeLeft}
428
471
  value={value}
429
472
  title={sleepTitle}
430
473
  />
431
- )}
474
+ ) : null}
432
475
 
433
476
  <Separator />
434
477
  </View>
435
- )}
478
+ ) : null}
436
479
  <Controls
437
480
  value={value}
438
481
  visible={true}
@@ -9,18 +9,19 @@ import * as R from "ramda";
9
9
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
10
10
 
11
11
  import {
12
+ Button,
12
13
  ContentInfo,
13
- ProgressBar,
14
14
  Controls,
15
- Button,
15
+ ProgressBar,
16
16
  } from "@applicaster/quick-brick-mobile-transport-controls";
17
17
 
18
18
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
19
19
  import {
20
- useModalAnimationContext,
21
20
  AnimationComponent,
22
21
  ComponentAnimationType,
22
+ defaultAspectRatioWidth,
23
23
  PlayerAnimationStateEnum,
24
+ useModalAnimationContext,
24
25
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
25
26
 
26
27
  type Dimensions = {
@@ -113,7 +114,7 @@ const playerDimensionsStyle = (
113
114
  minimisedHeight: number,
114
115
  addPadding: boolean
115
116
  ): ViewStyle => ({
116
- paddingLeft: addPadding ? minimisedHeight || 100 : 0,
117
+ paddingLeft: addPadding ? defaultAspectRatioWidth(minimisedHeight) || 100 : 0,
117
118
  });
118
119
 
119
120
  const animatedContainerStyle = (docked: boolean): ViewStyle => ({
@@ -133,24 +134,11 @@ const backgroundImageStyle = (
133
134
  });
134
135
 
135
136
  const controlButtons = (
136
- isPlayNextEnabled: boolean,
137
137
  live: boolean,
138
138
  paused: boolean,
139
139
  buffering: boolean,
140
140
  playerId: string
141
141
  ): Array<ControlsButton> => {
142
- if (isPlayNextEnabled) {
143
- return [
144
- {
145
- size: "medium",
146
- name: "play_next",
147
- icon: "play_next",
148
- preferredFocus: true,
149
- style: styles.buttons,
150
- },
151
- ];
152
- }
153
-
154
142
  if (buffering) {
155
143
  return [
156
144
  {
@@ -230,7 +218,7 @@ export default function DockedLayout(props: LayoutProps) {
230
218
  } = useModalAnimationContext();
231
219
 
232
220
  const maximazeVideoModal = React.useCallback(
233
- () => setPlayerAnimationState(PlayerAnimationStateEnum.maximaze),
221
+ () => setPlayerAnimationState(PlayerAnimationStateEnum.maximize),
234
222
  []
235
223
  );
236
224
 
@@ -245,18 +233,19 @@ export default function DockedLayout(props: LayoutProps) {
245
233
  ]}
246
234
  >
247
235
  {!playerState.isLive &&
248
- (layoutState.docked || startComponentsAnimation) && (
249
- <ProgressBar
250
- content={entry}
251
- value={value}
252
- visible={visible}
253
- layoutState={layoutState}
254
- onPlayerSeek={onPlayerSeek}
255
- playerState={playerState}
256
- startComponentsAnimation={startComponentsAnimation}
257
- docked
258
- />
259
- )}
236
+ (layoutState.docked || startComponentsAnimation) ? (
237
+ <ProgressBar
238
+ name={"audio-docked-layout"}
239
+ content={entry}
240
+ value={value}
241
+ visible={visible}
242
+ layoutState={layoutState}
243
+ onPlayerSeek={onPlayerSeek}
244
+ playerState={playerState}
245
+ startComponentsAnimation={startComponentsAnimation}
246
+ docked
247
+ />
248
+ ) : null}
260
249
 
261
250
  <View style={styles.content}>
262
251
  <View
@@ -302,7 +291,6 @@ export default function DockedLayout(props: LayoutProps) {
302
291
  value={value}
303
292
  visible={visible}
304
293
  buttons={R.partial(controlButtons, [
305
- playNextData,
306
294
  playerState.isLive && liveButton,
307
295
  ])}
308
296
  style={styles.playPauseButton}
@@ -17,7 +17,7 @@ const playbackButtonMargins = (
17
17
  isTablet: boolean,
18
18
  isLiveShowFullControls: boolean
19
19
  ): number =>
20
- isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ? 95 : 40;
20
+ isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ? undefined : 40;
21
21
 
22
22
  export const PADDING_HORIZONTAL = 15;
23
23
 
@@ -37,8 +37,8 @@ export const playbackButton = ({
37
37
  const icon = playerState.isPaused
38
38
  ? "play"
39
39
  : playerState.isLive && liveButton
40
- ? "stop"
41
- : "pause";
40
+ ? "stop"
41
+ : "pause";
42
42
 
43
43
  return {
44
44
  size: playbackButtonSize(isTablet),
@@ -47,7 +47,7 @@ const fetchPaletteAndDominantColor = async (imageUrl: string) => {
47
47
  const dominant = await getDominantColor(imageUrl);
48
48
  const dominantColor = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
49
49
 
50
- return { gradientColors: colors.slice(0, 2), dominantColor };
50
+ return { gradientColors: colors.slice(0, 2).reverse(), dominantColor };
51
51
  };
52
52
 
53
53
  export const MagicBG: React.FC<MagicBGProps> = ({
@@ -0,0 +1,119 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+ import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
3
+ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
4
+ import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
5
+ import { hasDatePassed, NAMESPACE } from "./utils";
6
+
7
+ const { log_debug, log_error } = createLogger({
8
+ subsystem: "Player",
9
+ category: "SleepTimerManager",
10
+ });
11
+
12
+ export type SleepTimer = {
13
+ label: string;
14
+ // interval in minutes for UI at the moment when timer was first started
15
+ value: number;
16
+ };
17
+
18
+ export const SLEEP_TIMER_OFF: SleepTimer = {
19
+ label: "playback_sleep_off_label",
20
+ value: 0,
21
+ };
22
+
23
+ export class SleepTimerState {
24
+ constructor(
25
+ public timer: SleepTimer,
26
+ public timerDate: number
27
+ ) {}
28
+ }
29
+
30
+ const SLEEP_PREFERENCE_KEY = "sleep";
31
+
32
+ export class SleepTimerController {
33
+ private sleepTimerStateSubject = new BehaviorSubject<SleepTimerState>(
34
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
35
+ );
36
+
37
+ constructor(private player: Player) {}
38
+
39
+ get sleepTimerState$() {
40
+ return this.sleepTimerStateSubject.asObservable();
41
+ }
42
+
43
+ private async scheduleSleep(sleepDate): Promise<boolean> {
44
+ log_debug(`Setting sleep interval to ${sleepDate}`);
45
+
46
+ try {
47
+ this.player.cancelSleepTimer?.();
48
+ this.player.startSleepTimer?.(sleepDate);
49
+
50
+ return true;
51
+ } catch (e) {
52
+ log_error("Failed to start sleep timer", { error: e.message });
53
+
54
+ return false;
55
+ }
56
+ }
57
+
58
+ async setSleepTimer(item: SleepTimer) {
59
+ // User has selected Off state
60
+ if (item.value === 0) {
61
+ log_debug("Cancelling sleep timer");
62
+ this.player.cancelSleepTimer?.();
63
+
64
+ return this.onTimerExpired();
65
+ }
66
+
67
+ log_debug(`Starting sleep timer in ${item.value} minutes`);
68
+
69
+ const futureSleepDate = Date.now() + item.value * 60000;
70
+
71
+ if (await this.scheduleSleep(futureSleepDate)) {
72
+ await localStorage.setItem(
73
+ SLEEP_PREFERENCE_KEY,
74
+ { ...item, futureSleepDate },
75
+ NAMESPACE
76
+ );
77
+
78
+ this.sleepTimerStateSubject.next(
79
+ new SleepTimerState(item, futureSleepDate)
80
+ );
81
+ }
82
+ }
83
+
84
+ async onTimerExpired() {
85
+ await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
86
+
87
+ this.sleepTimerStateSubject.next(new SleepTimerState(SLEEP_TIMER_OFF, 0));
88
+ }
89
+
90
+ async loadSleep() {
91
+ const sleep = await localStorage.getItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
92
+
93
+ if (!sleep) return;
94
+
95
+ const { futureSleepDate, ...rest } = sleep;
96
+
97
+ if (!futureSleepDate || hasDatePassed(futureSleepDate)) {
98
+ log_debug("Sleep date has already passed");
99
+ await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
100
+
101
+ return;
102
+ }
103
+
104
+ if (await this.scheduleSleep(futureSleepDate)) {
105
+ log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
106
+
107
+ this.sleepTimerStateSubject.next(
108
+ new SleepTimerState(rest as SleepTimer, futureSleepDate)
109
+ );
110
+ }
111
+ }
112
+
113
+ // Will be called if user closed the player explicitly.
114
+ // TODO: check if it will be called on Android when we need.
115
+ // Probably we will have to handle it on native instead.
116
+ public static async onPlayerDestroy() {
117
+ await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
118
+ }
119
+ }
@@ -0,0 +1,52 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+ import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
3
+ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
4
+ import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
5
+ import { NAMESPACE } from "./utils";
6
+
7
+ const { log_debug } = createLogger({
8
+ subsystem: "Player",
9
+ category: "PlaybackSpeedManager",
10
+ });
11
+
12
+ export type PlaybackSpeed = {
13
+ asset: string;
14
+ value: number;
15
+ };
16
+
17
+ const SPEED_PREFERENCE_KEY = "speed";
18
+
19
+ export const DEFAULT_SPEED: PlaybackSpeed = {
20
+ asset: "speed_1",
21
+ value: 1.0,
22
+ };
23
+
24
+ // todo: should not be applied to live streams
25
+ export class SpeedController {
26
+ private playbackSpeedSubject = new BehaviorSubject<PlaybackSpeed>(
27
+ DEFAULT_SPEED
28
+ );
29
+
30
+ constructor(private player: Player) {}
31
+
32
+ get playbackSpeed$() {
33
+ return this.playbackSpeedSubject.asObservable();
34
+ }
35
+
36
+ async loadSpeed() {
37
+ const speed = await localStorage.getItem(SPEED_PREFERENCE_KEY, NAMESPACE);
38
+
39
+ // checking for value in case we will pull empty or broken object
40
+ if (speed && speed.value) {
41
+ this.playbackSpeedSubject.next(speed);
42
+ this.player.setPlaybackRate?.(speed.value);
43
+ log_debug(`Speed loaded: ${speed}`);
44
+ }
45
+ }
46
+
47
+ async setPlaybackSpeed(speed: PlaybackSpeed) {
48
+ await localStorage.setItem(SPEED_PREFERENCE_KEY, speed, NAMESPACE);
49
+ this.playbackSpeedSubject.next(speed);
50
+ this.player.setPlaybackRate?.(speed.value);
51
+ }
52
+ }
@@ -1,4 +1,8 @@
1
1
  import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
2
+ import {
3
+ PlayerAnimationStateEnum,
4
+ PlayerAnimationStateT,
5
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
2
6
  import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
3
7
 
4
8
  export const NAMESPACE = "audio-player";
@@ -31,3 +35,23 @@ export const formatSpeed = (value: number) => {
31
35
  // Return the formatted string with "speed_" prefix
32
36
  return `speed_${formattedValue}`;
33
37
  };
38
+
39
+ export const calculateBorderRadius = (
40
+ isTablet: boolean,
41
+ docked: boolean,
42
+ startComponentsAnimation: boolean,
43
+ isActiveGesture: boolean,
44
+ playerAnimationState: PlayerAnimationStateT
45
+ ): number => {
46
+ const shouldRemoveBorderRadius =
47
+ (startComponentsAnimation &&
48
+ isActiveGesture &&
49
+ playerAnimationState !== PlayerAnimationStateEnum.maximize) ||
50
+ docked;
51
+
52
+ if (shouldRemoveBorderRadius) {
53
+ return 0;
54
+ }
55
+
56
+ return isTablet ? 24 : 10;
57
+ };
@@ -721,7 +721,7 @@ export default class VideoPlayer extends React.Component<
721
721
 
722
722
  const isAudioItem =
723
723
  entry?.content?.type?.includes?.("audio") ||
724
- entry?.type?.value === "audio";
724
+ entry?.type?.value?.includes?.("audio");
725
725
 
726
726
  const getStyle = (dimensions) => {
727
727
  const generalStyles = isAudioItem ? styles.audio : styles.video;
@@ -791,8 +791,13 @@ export default class VideoPlayer extends React.Component<
791
791
  controls: shouldUseNativeControls,
792
792
  };
793
793
 
794
+ const isFullScreenAudioPlayer =
795
+ this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
796
+
797
+ const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
798
+
794
799
  const pictureInPictureEnabled =
795
- this.props.pluginConfiguration?.pictureInPictureEnabled;
800
+ this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
796
801
 
797
802
  const nativeProps = {
798
803
  entry, // Must be passed first in list
@@ -806,7 +811,6 @@ export default class VideoPlayer extends React.Component<
806
811
  nowPlayingEnabled,
807
812
  paused,
808
813
  seek,
809
- rate,
810
814
  textTrackId,
811
815
  liveSeekingEnabled,
812
816
  liveCatchUpEnabled,
@@ -817,7 +821,14 @@ export default class VideoPlayer extends React.Component<
817
821
  accessibilityProps: getAllAccessibilityProps(
818
822
  this.getPluginConfiguration()
819
823
  ),
820
- }; // rate temporary removed, because of bug with subtitles
824
+ };
825
+
826
+ if (!isAppleTV()) {
827
+ // TODO: Move to RN method and remove from props.
828
+ // Causes tvOS bug: native player passes rate on fast seek,
829
+ // RN returns it and it breaks playback.
830
+ nativeProps.rate = rate;
831
+ }
821
832
 
822
833
  if (
823
834
  this.isReactNativeMethodsSupported() &&
@@ -918,8 +929,8 @@ export default class VideoPlayer extends React.Component<
918
929
  const playerContainerStyles = isApplePlatform()
919
930
  ? styles.playerContainerBlack
920
931
  : isTV() && isAudioItem
921
- ? styles.playerContainerTransparent
922
- : styles.playerContainerBlack;
932
+ ? styles.playerContainerTransparent
933
+ : styles.playerContainerBlack;
923
934
 
924
935
  const isInline = () => {
925
936
  if (isTV()) {
@@ -960,9 +971,6 @@ export default class VideoPlayer extends React.Component<
960
971
  ) : null;
961
972
  };
962
973
 
963
- const isFullScreenAudioPlayer =
964
- this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
965
-
966
974
  const sharedWrapperViewProps = {
967
975
  style,
968
976
  entry,
@@ -989,15 +997,16 @@ export default class VideoPlayer extends React.Component<
989
997
 
990
998
  const renderMobilePlayer = () => {
991
999
  return isFullScreenAudioPlayer && isAudioItem ? (
992
- <View style={containerStyle}>
993
- {(!castAction?.state?.connected || supportNativeCast) && (
1000
+ // Ensure the View remains in the native hierarchy for proper rendering and interaction
1001
+ <View collapsable={false} style={containerStyle}>
1002
+ {!castAction?.state?.connected || supportNativeCast ? (
994
1003
  <this.props.PlayerComponent
995
1004
  ref={this._assignRoot}
996
1005
  {...nativeEvents}
997
1006
  {...nativeProps}
998
1007
  listener={this.props.listener}
999
1008
  />
1000
- )}
1009
+ ) : null}
1001
1010
  <AudioPlayerWrapper {...sharedWrapperViewProps} />
1002
1011
  </View>
1003
1012
  ) : (
@@ -1012,7 +1021,7 @@ export default class VideoPlayer extends React.Component<
1012
1021
  return (
1013
1022
  <View style={styles.container}>
1014
1023
  <View style={[playerContainerStyles, playerDimensions]}>
1015
- {isAudioItem && (
1024
+ {isAudioItem ? (
1016
1025
  <AnimationComponent
1017
1026
  animationType={ComponentAnimationType.player}
1018
1027
  additionalData={{
@@ -1031,21 +1040,24 @@ export default class VideoPlayer extends React.Component<
1031
1040
  plugin_configuration={this.getPluginConfiguration()}
1032
1041
  />
1033
1042
  </AnimationComponent>
1034
- )}
1043
+ ) : null}
1035
1044
 
1036
- {shouldRenderBackground && (
1045
+ {shouldRenderBackground ? (
1037
1046
  <PlayerImageBackground
1038
1047
  imageKey={backgroundImageKey}
1039
1048
  configuration={this.getPluginConfiguration()}
1040
1049
  entry={entry}
1041
1050
  style={getBackgroundImageStyle(playerDimensions)}
1042
1051
  />
1043
- )}
1052
+ ) : null}
1044
1053
 
1045
- {(!castAction?.state?.connected || supportNativeCast) && (
1054
+ {!castAction?.state?.connected || supportNativeCast ? (
1046
1055
  <AnimationComponent
1047
1056
  animationType={ComponentAnimationType.player}
1048
- additionalData={playerDimensions}
1057
+ additionalData={{
1058
+ ...playerDimensions,
1059
+ disableAnimatedComponent: isAudioItem,
1060
+ }}
1049
1061
  style={getStyle(playerDimensions)}
1050
1062
  >
1051
1063
  <this.props.PlayerComponent
@@ -1064,7 +1076,7 @@ export default class VideoPlayer extends React.Component<
1064
1076
  {this.childrenWithPlayNext()}
1065
1077
  </this.props.PlayerComponent>
1066
1078
  </AnimationComponent>
1067
- )}
1079
+ ) : null}
1068
1080
  </View>
1069
1081
 
1070
1082
  {renderTransportControls(playerDimensions)}