@applicaster/quick-brick-player 13.0.0-alpha.3013095924 → 13.0.0-alpha.3044917147

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "13.0.0-alpha.3013095924",
3
+ "version": "13.0.0-alpha.3044917147",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -35,11 +35,11 @@
35
35
  },
36
36
  "homepage": "https://github.com/applicaster/quickbrick#readme",
37
37
  "dependencies": {
38
- "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-alpha.3013095924",
39
- "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.3013095924",
41
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.3013095924",
42
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.3013095924",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.124",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-alpha.3044917147",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.3044917147",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.3044917147",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.3044917147",
43
43
  "query-string": "7.1.3",
44
44
  "react-native-web-linear-gradient": "1.1.2",
45
45
  "shaka-player": "4.3.5",
@@ -37,18 +37,13 @@ import {
37
37
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
38
38
  import { PADDING_HORIZONTAL } from "./Layout/const";
39
39
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
40
- import {
41
- SLEEP_TIMER_OFF,
42
- SleepTimerController,
43
- SleepTimerState,
44
- } from "./SleepTimerManager";
40
+
45
41
  import { DEFAULT_SPEED, PlaybackSpeed, SpeedController } from "./SpeedManager";
46
42
  import {
47
43
  dismissModal as dismissBottomSheetModal,
48
44
  openBottomSheetModal,
49
45
  useModalStoreState,
50
46
  } from "@applicaster/zapp-react-native-utils/modalState";
51
- import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
52
47
  import { speedOptions } from "../Const/speedOptions";
53
48
 
54
49
  const { log_debug } = createLogger({
@@ -128,19 +123,14 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
128
123
  configuration?.audio_live_player_update_interval
129
124
  );
130
125
 
131
- const [sleepTimerManager, setSleepTimerManager] =
132
- useState<SleepTimerController | null>(null);
133
-
134
126
  const [speedManager, setSpeedManager] = useState<SpeedController | null>(
135
127
  null
136
128
  );
137
129
 
138
130
  useEffect(() => {
139
131
  if (player) {
140
- setSleepTimerManager(new SleepTimerController(player));
141
132
  setSpeedManager(new SpeedController(player));
142
133
  } else {
143
- setSleepTimerManager(null);
144
134
  setSpeedManager(null);
145
135
  }
146
136
  }, [player]);
@@ -161,22 +151,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
161
151
  });
162
152
 
163
153
  return () => unsubscribe?.();
164
- }, [player, modalBottomSheetState.visible, sleepTimerManager]);
165
-
166
- React.useEffect(() => {
167
- if (playerState?.isLive) {
168
- // Resetting a sleep timer when audio content is live (Timer not supported on live video)
169
- sleepTimerManager.setSleepTimer(SLEEP_TIMER_OFF).catch();
170
- }
171
- }, [playerState?.isLive]);
154
+ }, [player, modalBottomSheetState.visible]);
172
155
 
173
156
  const [playbackSpeed, setPlaybackSpeed] =
174
157
  useState<PlaybackSpeed>(DEFAULT_SPEED);
175
158
 
176
- const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
177
- new SleepTimerState(SLEEP_TIMER_OFF, 0)
178
- );
179
-
180
159
  const [defaultDimensions, setDefaultDimensions] =
181
160
  React.useState<Dimensions>(undefined);
182
161
 
@@ -199,17 +178,13 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
199
178
  };
200
179
 
201
180
  useEffect(() => {
202
- const sleepSubscription =
203
- sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
204
-
205
181
  const speedSubscription =
206
182
  speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
207
183
 
208
184
  return () => {
209
- sleepSubscription?.unsubscribe();
210
185
  speedSubscription?.unsubscribe();
211
186
  };
212
- }, [sleepTimerManager, speedManager]);
187
+ }, [speedManager]);
213
188
 
214
189
  useEffect(() => {
215
190
  if (!playerState.isReadyToPlay) {
@@ -217,8 +192,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
217
192
  }
218
193
 
219
194
  void speedManager?.loadSpeed();
220
- void sleepTimerManager?.loadSleep();
221
- }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
195
+ }, [playerState.isReadyToPlay, speedManager]);
222
196
 
223
197
  const getValue = (key: string, stylesObj: {}) =>
224
198
  R.propOr(null, key, stylesObj);
@@ -266,13 +240,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
266
240
  [speedManager]
267
241
  );
268
242
 
269
- const onSleepSelect = React.useCallback(
270
- async (item) => {
271
- await sleepTimerManager?.setSleepTimer(item);
272
- },
273
- [sleepTimerManager]
274
- );
275
-
276
243
  const onSpeedButtonPress = React.useCallback(() => {
277
244
  openBottomSheetModal({
278
245
  modalBottomSheetContentProps: {
@@ -297,42 +264,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
297
264
  });
298
265
  }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
299
266
 
300
- const onSleepButtonPress = React.useCallback(() => {
301
- openBottomSheetModal({
302
- modalBottomSheetContentProps: {
303
- items: getSleepTimerOptions(configuration),
304
- current_selection: sleepTimerState.timer,
305
- onPress: onSleepSelect,
306
- title: configuration["playback_sleep_title"],
307
- itemProps: getBottomSheetModalItemProps,
308
- iconProps: getBottomSheetModalIconProps,
309
- labelProps: getBottomSheetModalLabelProps,
310
- getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
311
- iconPlacement: "right",
312
- },
313
- options: {
314
- supportedOrientations: [
315
- "portrait",
316
- "landscape",
317
- "portrait-upside-down",
318
- ],
319
- },
320
- });
321
- }, [
322
- sleepTimerState.timer,
323
- configuration["playback_sleep_title"],
324
- configuration["playback_sleep_off_label"],
325
- onSleepSelect,
326
- ]);
327
-
328
- // This method does not actually cancel the sleep timer
329
- // (and should not to avoid race condition),
330
- // it just performs the cleanup when timer expires
331
- // TODO: rename it in code above
332
- const cancelSleepTimer = React.useCallback(() => {
333
- sleepTimerManager?.onTimerExpired();
334
- }, [sleepTimerManager]);
335
-
336
267
  const onCloseVideoModal = React.useCallback(() => {
337
268
  playerManager.close();
338
269
  }, [playerManager]);
@@ -372,15 +303,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
372
303
  onPlayerSeek={onPlayerSeek}
373
304
  onSkipStart={onSkipStart}
374
305
  onSpeedButtonPress={onSpeedButtonPress}
375
- onSleepButtonPress={onSleepButtonPress}
376
306
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
377
- playbackSleepName={
378
- sleepTimerState.timer.value === 0
379
- ? "sleep_timer"
380
- : "sleep_timer_active"
381
- }
382
- sleepTimerDate={sleepTimerState.timerDate}
383
- cancelSleepTimer={cancelSleepTimer}
384
307
  onSkipLive={onSkipLive}
385
308
  onCloseVideoModal={onCloseVideoModal}
386
309
  maximiseVideoModal={maximiseVideoModal}
@@ -2,7 +2,10 @@ import * as React from "react";
2
2
  import { StyleSheet } from "react-native";
3
3
 
4
4
  import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
5
- import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+ import {
6
+ useIsTablet,
7
+ useZStore,
8
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
6
9
  import {
7
10
  Controls as MobileTransportControls,
8
11
  ProgressBar,
@@ -27,6 +30,7 @@ import {
27
30
  sleepButton,
28
31
  speedButton,
29
32
  } from "./utils";
33
+ import { useSleepTimerModal } from "../../useSleepTimerModal";
30
34
 
31
35
  const styles = StyleSheet.create({
32
36
  flex: {
@@ -64,11 +68,8 @@ type Props = {
64
68
  value: GetValue;
65
69
  title: string | number;
66
70
  summary: string | number;
67
- sleepTimerDate: number;
68
- cancelSleepTimer: Callback;
69
71
 
70
72
  playbackSpeedName: string;
71
- playbackSleepName: string;
72
73
 
73
74
  onPlaybackButtonPress: Callback;
74
75
  onRewindButtonPress: Callback;
@@ -77,7 +78,6 @@ type Props = {
77
78
  onSkipStart: Callback;
78
79
  onSkipLive: Callback;
79
80
  onSpeedButtonPress: Callback;
80
- onSleepButtonPress: Callback;
81
81
 
82
82
  maxWidth?: number;
83
83
 
@@ -92,11 +92,8 @@ export const Controls = (props: Props) => {
92
92
  value,
93
93
  title,
94
94
  summary,
95
- sleepTimerDate,
96
- cancelSleepTimer,
97
95
 
98
96
  playbackSpeedName,
99
- playbackSleepName,
100
97
 
101
98
  onPlaybackButtonPress,
102
99
  onRewindButtonPress,
@@ -105,12 +102,26 @@ export const Controls = (props: Props) => {
105
102
  onSkipStart,
106
103
  onSkipLive,
107
104
  onSpeedButtonPress,
108
- onSleepButtonPress,
109
105
  maxWidth = 320,
110
106
 
111
107
  docked,
112
108
  } = props;
113
109
 
110
+ const configuration = useZStore("playerConfiguration");
111
+
112
+ const { sleepTimerState, onSleepButtonPress } = useSleepTimerModal(
113
+ playerManager.getActivePlayer(),
114
+ configuration
115
+ );
116
+
117
+ const timerRemaining =
118
+ sleepTimerState.timerDate !== 0 && +sleepTimerState.timerDate > Date.now()
119
+ ? sleepTimerState.timerDate
120
+ : null;
121
+
122
+ const playbackSleepName =
123
+ timerRemaining === null ? "sleep_timer" : "sleep_timer_active";
124
+
114
125
  const isTablet = useIsTablet();
115
126
  const controller = playerManager.getActivePlayer();
116
127
  let showFullControls = !controller?.isAd();
@@ -124,45 +135,6 @@ export const Controls = (props: Props) => {
124
135
  showFullControls = !!(controller?.hasSeekableDuration?.() && seekEnabled);
125
136
  }
126
137
 
127
- const [timeLeft, setTimeLeft] = React.useState(
128
- sleepTimerDate !== 0 ? sleepTimerDate - Date.now() : 0
129
- );
130
-
131
- const showCounter = React.useMemo(() => sleepTimerDate > 0, [sleepTimerDate]);
132
-
133
- const countDownOptions = React.useMemo(() => {
134
- return {
135
- showCounter: showCounter && timeLeft >= 0,
136
- timeLeft,
137
- };
138
- }, [showCounter, timeLeft]);
139
-
140
- React.useEffect(() => {
141
- if (showCounter && sleepTimerDate !== 0) {
142
- setTimeLeft(sleepTimerDate - Date.now());
143
-
144
- const intervalId = setInterval(() => {
145
- setTimeLeft(() => {
146
- const newTimeLeft = sleepTimerDate - Date.now();
147
-
148
- if (newTimeLeft <= 0) {
149
- clearInterval(intervalId);
150
-
151
- cancelSleepTimer();
152
-
153
- return 0;
154
- } else {
155
- return newTimeLeft;
156
- }
157
- });
158
- }, 1000);
159
-
160
- return () => clearInterval(intervalId);
161
- } else {
162
- setTimeLeft(0);
163
- }
164
- }, [sleepTimerDate, showCounter, cancelSleepTimer]);
165
-
166
138
  const startTime = entry.extensions?.live?.start_time;
167
139
  const endTime = entry.extensions?.live?.end_time;
168
140
 
@@ -295,7 +267,6 @@ export const Controls = (props: Props) => {
295
267
  isDateBasedLiveContent={isDateBasedLiveContent}
296
268
  onSkipLive={onSkipLive}
297
269
  progressBarStyles={styles.progressBar}
298
- countDownOptions={countDownOptions}
299
270
  thumbStyles={styles.progressBarThumbStyles}
300
271
  thumbStylesPressed={styles.progressBarThumbStyles_pressed}
301
272
  trackStyles={styles.progressBarTrackStyles}
@@ -99,7 +99,7 @@ export const skipToLiveButton = (props: ButtonsProps): ControlsButton =>
99
99
  skipButtonByName({ ...props, name: "skip_live" });
100
100
 
101
101
  export const speedButton = (props: ButtonsProps): ControlsButton =>
102
- speedButtonByName({ ...props });
102
+ speedButtonByName(props);
103
103
 
104
104
  export const sleepButton = (props: ButtonsProps): ControlsButton =>
105
- sleepButtonByName({ ...props });
105
+ sleepButtonByName(props);
@@ -24,6 +24,10 @@ import {
24
24
  useModalAnimationContext,
25
25
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
26
26
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
27
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
28
+ import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
29
+
30
+ const bottomTabBarHeight = getTabBarHeight();
27
31
 
28
32
  type Props = {
29
33
  entry: ZappEntry;
@@ -88,10 +92,6 @@ const styles = StyleSheet.create({
88
92
  },
89
93
  });
90
94
 
91
- const backgroundColorStyle = (value: GetValue): ViewStyle => ({
92
- backgroundColor: value("docked_player_background_color"),
93
- });
94
-
95
95
  const playerDimensionsStyle = (
96
96
  minimisedHeight: number,
97
97
  addPadding: boolean
@@ -208,6 +208,10 @@ export const DockedControls = (props: Props) => {
208
208
  []
209
209
  );
210
210
 
211
+ const dockedBackgroundColor = value("docked_player_background_color");
212
+
213
+ const insets = useSafeAreaInsets();
214
+
211
215
  if (!layoutState.docked && !startComponentsAnimation) return null;
212
216
 
213
217
  return (
@@ -219,7 +223,9 @@ export const DockedControls = (props: Props) => {
219
223
  isActiveGesture || layoutState.docked,
220
224
  progressBarHeight
221
225
  ),
222
- { height: minimisedHeight },
226
+ {
227
+ height: minimisedHeight + bottomTabBarHeight + insets.bottom,
228
+ },
223
229
  ]}
224
230
  >
225
231
  {!playerState.isLive &&
@@ -261,7 +267,12 @@ export const DockedControls = (props: Props) => {
261
267
  )}
262
268
  />
263
269
  )}
264
- <View style={[styles.contentInfo, backgroundColorStyle(value)]}>
270
+ <View
271
+ style={[
272
+ styles.contentInfo,
273
+ { backgroundColor: dockedBackgroundColor },
274
+ ]}
275
+ >
265
276
  <ContentInfo
266
277
  content={{ ...entry, title, summary }}
267
278
  value={value}
@@ -275,7 +286,7 @@ export const DockedControls = (props: Props) => {
275
286
  isTablet
276
287
  ? styles.controlsContainerForTablet
277
288
  : styles.controlsContainerForMobile,
278
- backgroundColorStyle(value),
289
+ { backgroundColor: dockedBackgroundColor },
279
290
  ]}
280
291
  >
281
292
  <Controls
@@ -297,6 +308,12 @@ export const DockedControls = (props: Props) => {
297
308
  />
298
309
  </View>
299
310
  </View>
311
+ <View
312
+ style={{
313
+ height: insets.bottom + bottomTabBarHeight,
314
+ backgroundColor: dockedBackgroundColor,
315
+ }}
316
+ />
300
317
  </AnimationComponent>
301
318
  );
302
319
  };
@@ -77,15 +77,11 @@ export function MobileLayout({
77
77
  onSkipStart,
78
78
  onSkipLive,
79
79
  onSpeedButtonPress,
80
- onSleepButtonPress,
81
80
  onCloseVideoModal,
82
81
  playbackSpeedName,
83
- playbackSleepName,
84
82
  configuration,
85
83
  dimensions,
86
84
  playNextData,
87
- cancelSleepTimer,
88
- sleepTimerDate,
89
85
  title,
90
86
  summary,
91
87
  }: Props) {
@@ -196,10 +192,7 @@ export function MobileLayout({
196
192
  value={value}
197
193
  title={title}
198
194
  summary={summary}
199
- sleepTimerDate={sleepTimerDate}
200
- cancelSleepTimer={cancelSleepTimer}
201
195
  playbackSpeedName={playbackSpeedName}
202
- playbackSleepName={playbackSleepName}
203
196
  onPlaybackButtonPress={onPlaybackButtonPress}
204
197
  onRewindButtonPress={onRewindButtonPress}
205
198
  onForwardButtonPress={onForwardButtonPress}
@@ -207,7 +200,6 @@ export function MobileLayout({
207
200
  onSkipStart={onSkipStart}
208
201
  onSkipLive={onSkipLive}
209
202
  onSpeedButtonPress={onSpeedButtonPress}
210
- onSleepButtonPress={onSleepButtonPress}
211
203
  docked={docked}
212
204
  />
213
205
  </View>
@@ -1,11 +1,5 @@
1
1
  import React, { PropsWithChildren } from "react";
2
- import {
3
- Image,
4
- Animated,
5
- StyleSheet,
6
- ViewStyle,
7
- ImageStyle,
8
- } from "react-native";
2
+ import { Image, Animated, StyleSheet, ImageStyle } from "react-native";
9
3
 
10
4
  import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
11
5
  import {
@@ -14,6 +8,9 @@ import {
14
8
  DURATION_TO_MINIMIZE,
15
9
  DURATION_TO_MAXIMIZE,
16
10
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
11
+ import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
12
+
13
+ import { useChangePlayerState } from "./hooks";
17
14
 
18
15
  const styles = StyleSheet.create({
19
16
  container: {
@@ -26,16 +23,11 @@ const styles = StyleSheet.create({
26
23
  type Props = PropsWithChildren<{
27
24
  entry: ZappEntry;
28
25
  style?: ImageStyle | ImageStyle[];
29
- wrapperViewStyle?: ViewStyle;
30
26
  imageWidth: number;
27
+ docked: boolean;
31
28
  }>;
32
29
 
33
- export const AnimatedImage = ({
34
- entry,
35
- style,
36
- wrapperViewStyle,
37
- imageWidth,
38
- }: Props) => {
30
+ export const AnimatedImage = ({ entry, style, imageWidth, docked }: Props) => {
39
31
  const heightAnim = React.useRef(new Animated.Value(imageWidth)).current;
40
32
 
41
33
  const source = React.useMemo(
@@ -43,27 +35,40 @@ export const AnimatedImage = ({
43
35
  [entry]
44
36
  );
45
37
 
46
- const { playerAnimationState, minimisedHeight } = useModalAnimationContext();
38
+ const { minimisedHeight } = useModalAnimationContext();
39
+
40
+ const changePlayerState$ = useChangePlayerState({ docked });
47
41
 
48
42
  React.useEffect(() => {
49
- if (playerAnimationState === PlayerAnimationStateEnum.minimize) {
50
- Animated.timing(heightAnim, {
51
- toValue: minimisedHeight,
52
- duration: DURATION_TO_MINIMIZE,
53
- useNativeDriver: false, // width/height can't use native driver
54
- }).start();
43
+ const subscription = changePlayerState$.subscribe({
44
+ next: (nextPlayerState: PlayerAnimationStateT) => {
45
+ if (nextPlayerState === PlayerAnimationStateEnum.minimize) {
46
+ Animated.timing(heightAnim, {
47
+ toValue: minimisedHeight,
48
+ duration: DURATION_TO_MINIMIZE,
49
+ useNativeDriver: false, // width/height can't use native driver
50
+ }).start();
51
+ }
52
+
53
+ if (nextPlayerState === PlayerAnimationStateEnum.maximize) {
54
+ Animated.timing(heightAnim, {
55
+ toValue: imageWidth,
56
+ duration: DURATION_TO_MAXIMIZE,
57
+ useNativeDriver: false, // width/height can't use native driver
58
+ }).start();
59
+ }
55
60
 
56
- return;
57
- }
61
+ if (nextPlayerState === PlayerAnimationStateEnum.appear_as_docked) {
62
+ // minimize image immediatly
63
+ heightAnim.setValue(minimisedHeight);
64
+ }
65
+ },
66
+ });
58
67
 
59
- if (playerAnimationState === PlayerAnimationStateEnum.maximize) {
60
- Animated.timing(heightAnim, {
61
- toValue: imageWidth,
62
- duration: DURATION_TO_MAXIMIZE,
63
- useNativeDriver: false, // width/height can't use native driver
64
- }).start();
65
- }
66
- }, [playerAnimationState, imageWidth]);
68
+ return () => {
69
+ subscription.unsubscribe();
70
+ };
71
+ }, [changePlayerState$, minimisedHeight, imageWidth]);
67
72
 
68
73
  if (!source) return null;
69
74
 
@@ -71,7 +76,6 @@ export const AnimatedImage = ({
71
76
  <Animated.View
72
77
  style={[
73
78
  styles.container,
74
- wrapperViewStyle,
75
79
  {
76
80
  width: heightAnim,
77
81
  },
@@ -0,0 +1 @@
1
+ export { useChangePlayerState } from "./useChangePlayerState";
@@ -0,0 +1,99 @@
1
+ import * as React from "react";
2
+ import { Subject, Observable } from "rxjs";
3
+
4
+ import { distinctUntilChanged } from "rxjs/operators";
5
+ import { isNil } from "@applicaster/zapp-react-native-utils/utils";
6
+ import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
7
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
8
+ import {
9
+ useModalAnimationContext,
10
+ PlayerAnimationStateEnum,
11
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
12
+ import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
13
+
14
+ const builder = () => {
15
+ const changePlayerState$ = new Subject<PlayerAnimationStateT>();
16
+
17
+ const emitMaximized = (): void => {
18
+ changePlayerState$.next(PlayerAnimationStateEnum.maximize);
19
+ };
20
+
21
+ const emitMinimized = (): void => {
22
+ changePlayerState$.next(PlayerAnimationStateEnum.minimize);
23
+ };
24
+
25
+ const emitAppearInDocked = (): void => {
26
+ // if audio_player was presented in docked mode
27
+ changePlayerState$.next(PlayerAnimationStateEnum.appear_as_docked);
28
+ };
29
+
30
+ return {
31
+ emitMaximized,
32
+ emitMinimized,
33
+ emitAppearInDocked,
34
+ changePlayerState$: changePlayerState$.pipe(distinctUntilChanged()),
35
+ };
36
+ };
37
+
38
+ export const useChangePlayerState = ({
39
+ docked,
40
+ }): Observable<PlayerAnimationStateT> => {
41
+ const changePlayerStateRef =
42
+ useRefWithInitialValue<ReturnType<typeof builder>>(builder);
43
+
44
+ const { playerAnimationState } = useModalAnimationContext();
45
+
46
+ const previousPlayerAnimationState = usePrevious(playerAnimationState);
47
+
48
+ React.useEffect(() => {
49
+ if (
50
+ !docked &&
51
+ isNil(previousPlayerAnimationState) &&
52
+ playerAnimationState === PlayerAnimationStateEnum.minimize
53
+ ) {
54
+ changePlayerStateRef.current.emitMinimized();
55
+
56
+ return;
57
+ }
58
+
59
+ if (
60
+ !docked &&
61
+ isNil(previousPlayerAnimationState) &&
62
+ playerAnimationState === PlayerAnimationStateEnum.maximize
63
+ ) {
64
+ changePlayerStateRef.current.emitMaximized();
65
+
66
+ return;
67
+ }
68
+
69
+ if (
70
+ !docked &&
71
+ previousPlayerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
72
+ playerAnimationState === PlayerAnimationStateEnum.maximize
73
+ ) {
74
+ changePlayerStateRef.current.emitMaximized();
75
+
76
+ return;
77
+ }
78
+
79
+ if (
80
+ !docked &&
81
+ previousPlayerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
82
+ playerAnimationState === PlayerAnimationStateEnum.minimize
83
+ ) {
84
+ changePlayerStateRef.current.emitMinimized();
85
+
86
+ return;
87
+ }
88
+
89
+ if (
90
+ docked &&
91
+ isNil(previousPlayerAnimationState) &&
92
+ isNil(playerAnimationState)
93
+ ) {
94
+ changePlayerStateRef.current.emitAppearInDocked();
95
+ }
96
+ }, [previousPlayerAnimationState, playerAnimationState, docked]);
97
+
98
+ return changePlayerStateRef.current.changePlayerState$;
99
+ };
@@ -1,10 +1,13 @@
1
1
  import * as React from "react";
2
+ import { LayoutChangeEvent, View } from "react-native";
2
3
 
3
4
  import {
4
5
  AnimationComponent,
5
6
  ComponentAnimationType,
6
7
  useModalAnimationContext,
7
8
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
9
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
10
+ import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
8
11
 
9
12
  import { AnimatedImage } from "./AnimatedImage";
10
13
  import { FlexImage } from "./FlexImage";
@@ -12,7 +15,6 @@ import { FlexImage } from "./FlexImage";
12
15
  import { styles } from "./styles";
13
16
  import { calculateBorderRadius } from "../../utils";
14
17
  import { PlayNextData } from "../types";
15
- import { LayoutChangeEvent, View } from "react-native";
16
18
 
17
19
  type Props = {
18
20
  entry: ZappEntry;
@@ -29,7 +31,7 @@ type Props = {
29
31
  docked: boolean;
30
32
  imageWidth?: Option<number>;
31
33
  verticalOffset: number;
32
- onLayoutImage: (event: LayoutChangeEvent) => void;
34
+ onLayoutImage?: (event: LayoutChangeEvent) => void;
33
35
  };
34
36
 
35
37
  export const PlayerImage = (props: Props) => {
@@ -40,7 +42,7 @@ export const PlayerImage = (props: Props) => {
40
42
  verticalOffset,
41
43
  imageWidth,
42
44
  configuration,
43
- onLayoutImage,
45
+ onLayoutImage = noop,
44
46
  } = props;
45
47
 
46
48
  const {
@@ -50,8 +52,9 @@ export const PlayerImage = (props: Props) => {
50
52
  minimisedHeight,
51
53
  } = useModalAnimationContext();
52
54
 
53
- const isShadowEnabled =
54
- configuration?.audio_player_artwork_shadow_enabled ?? true;
55
+ const isShadowEnabled = toBooleanWithDefaultTrue(
56
+ configuration?.audio_player_artwork_shadow_enabled
57
+ );
55
58
 
56
59
  const shouldShowShadow = !docked && isShadowEnabled;
57
60
 
@@ -99,6 +102,7 @@ export const PlayerImage = (props: Props) => {
99
102
  entry={entry}
100
103
  style={[styles.backgroundImageContainer, { borderRadius }]}
101
104
  imageWidth={imageWidth}
105
+ docked={docked}
102
106
  />
103
107
  )}
104
108
  </View>
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable react-native/no-color-literals */
2
2
  import { StyleSheet } from "react-native";
3
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
3
4
 
4
5
  const SHADOW_COLOR = "rgba(0, 0, 0, 1)";
5
6
 
@@ -25,7 +26,10 @@ export const styles = StyleSheet.create({
25
26
  borderColor: "transparent",
26
27
  shadowColor: SHADOW_COLOR,
27
28
  shadowOffset: { width: 0, height: 24 },
28
- shadowOpacity: 0.2,
29
+ shadowOpacity: platformSelect({
30
+ ios: 0.35,
31
+ default: 0.2,
32
+ }),
29
33
  shadowRadius: 32,
30
34
  elevation: 12,
31
35
  },
@@ -98,15 +98,11 @@ export function TabletLandscapeLayout({
98
98
  onSkipStart,
99
99
  onSkipLive,
100
100
  onSpeedButtonPress,
101
- onSleepButtonPress,
102
101
  onCloseVideoModal,
103
102
  playbackSpeedName,
104
- playbackSleepName,
105
103
  configuration,
106
104
  dimensions,
107
105
  playNextData,
108
- cancelSleepTimer,
109
- sleepTimerDate,
110
106
  title,
111
107
  summary,
112
108
  }: Props) {
@@ -184,10 +180,7 @@ export function TabletLandscapeLayout({
184
180
  value={value}
185
181
  title={title}
186
182
  summary={summary}
187
- sleepTimerDate={sleepTimerDate}
188
- cancelSleepTimer={cancelSleepTimer}
189
183
  playbackSpeedName={playbackSpeedName}
190
- playbackSleepName={playbackSleepName}
191
184
  onPlaybackButtonPress={onPlaybackButtonPress}
192
185
  onRewindButtonPress={onRewindButtonPress}
193
186
  onForwardButtonPress={onForwardButtonPress}
@@ -195,7 +188,6 @@ export function TabletLandscapeLayout({
195
188
  onSkipStart={onSkipStart}
196
189
  onSkipLive={onSkipLive}
197
190
  onSpeedButtonPress={onSpeedButtonPress}
198
- onSleepButtonPress={onSleepButtonPress}
199
191
  docked={docked}
200
192
  />
201
193
  </View>
@@ -82,15 +82,11 @@ export function TabletPortraitLayout({
82
82
  onSkipStart,
83
83
  onSkipLive,
84
84
  onSpeedButtonPress,
85
- onSleepButtonPress,
86
85
  onCloseVideoModal,
87
86
  playbackSpeedName,
88
- playbackSleepName,
89
87
  configuration,
90
88
  dimensions,
91
89
  playNextData,
92
- cancelSleepTimer,
93
- sleepTimerDate,
94
90
  title,
95
91
  summary,
96
92
  }: Props) {
@@ -187,10 +183,7 @@ export function TabletPortraitLayout({
187
183
  value={value}
188
184
  title={title}
189
185
  summary={summary}
190
- sleepTimerDate={sleepTimerDate}
191
- cancelSleepTimer={cancelSleepTimer}
192
186
  playbackSpeedName={playbackSpeedName}
193
- playbackSleepName={playbackSleepName}
194
187
  onPlaybackButtonPress={onPlaybackButtonPress}
195
188
  onRewindButtonPress={onRewindButtonPress}
196
189
  onForwardButtonPress={onForwardButtonPress}
@@ -198,7 +191,6 @@ export function TabletPortraitLayout({
198
191
  onSkipStart={onSkipStart}
199
192
  onSkipLive={onSkipLive}
200
193
  onSpeedButtonPress={onSpeedButtonPress}
201
- onSleepButtonPress={onSleepButtonPress}
202
194
  docked={docked}
203
195
  />
204
196
  </View>
@@ -30,6 +30,7 @@ export class SleepTimerState {
30
30
  const SLEEP_PREFERENCE_KEY = "sleep";
31
31
 
32
32
  export class SleepTimerController {
33
+ // TODO cancel timer on reach time
33
34
  private sleepTimerStateSubject = new BehaviorSubject<SleepTimerState>(
34
35
  new SleepTimerState(SLEEP_TIMER_OFF, 0)
35
36
  );
@@ -55,18 +56,23 @@ export class SleepTimerController {
55
56
  }
56
57
  }
57
58
 
58
- async setSleepTimer(item: SleepTimer) {
59
+ async setSleepTimer(item: SleepTimer, prevTimerSleepDate?: number) {
59
60
  // User has selected Off state
60
61
  if (item.value === 0) {
61
62
  log_debug("Cancelling sleep timer");
63
+
64
+ // Removing scheduled pause on timer expiration
62
65
  this.player.cancelSleepTimer?.();
63
66
 
64
67
  return this.onTimerExpired();
65
68
  }
66
69
 
67
- log_debug(`Starting sleep timer in ${item.value} minutes`);
70
+ const futureSleepDate =
71
+ prevTimerSleepDate || Date.now() + item.value * 60000;
68
72
 
69
- const futureSleepDate = Date.now() + item.value * 60000;
73
+ log_debug(
74
+ `Starting sleep timer in ${(futureSleepDate - Date.now()) / 1000}s`
75
+ );
70
76
 
71
77
  if (await this.scheduleSleep(futureSleepDate)) {
72
78
  await localStorage.setItem(
@@ -82,17 +88,26 @@ export class SleepTimerController {
82
88
  }
83
89
 
84
90
  async onTimerExpired() {
85
- await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
91
+ this.removeSleepTimerFromLocalStorage().catch((error) => {
92
+ log_error("Failed to remove sleep timer from local storage", {
93
+ error: error.message,
94
+ });
95
+ });
86
96
 
97
+ // Update UI state
87
98
  this.sleepTimerStateSubject.next(new SleepTimerState(SLEEP_TIMER_OFF, 0));
88
99
  }
89
100
 
101
+ async removeSleepTimerFromLocalStorage() {
102
+ await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
103
+ }
104
+
90
105
  async loadSleep() {
91
106
  const sleep = await localStorage.getItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
92
107
 
93
108
  if (!sleep) return;
94
109
 
95
- const { futureSleepDate, ...rest } = sleep;
110
+ const { futureSleepDate, ...item } = sleep;
96
111
 
97
112
  if (!futureSleepDate || hasDatePassed(futureSleepDate)) {
98
113
  log_debug("Sleep date has already passed");
@@ -101,13 +116,9 @@ export class SleepTimerController {
101
116
  return;
102
117
  }
103
118
 
104
- if (await this.scheduleSleep(futureSleepDate)) {
105
- log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
119
+ log_debug("Sleep state restored");
106
120
 
107
- this.sleepTimerStateSubject.next(
108
- new SleepTimerState(rest as SleepTimer, futureSleepDate)
109
- );
110
- }
121
+ this.setSleepTimer(item as SleepTimer, futureSleepDate);
111
122
  }
112
123
 
113
124
  // Will be called if user closed the player explicitly.
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ import { ModalHeader } from "@applicaster/zapp-react-native-ui-components/Components/ModalComponent/Header";
3
+ import { isNil } from "lodash";
4
+
5
+ const formatTime = (milliseconds: number) => {
6
+ const totalSeconds = Math.floor(milliseconds / 1000);
7
+ const hours = Math.floor(totalSeconds / 3600);
8
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
9
+ const seconds = totalSeconds % 60;
10
+
11
+ if (milliseconds < 3600000) {
12
+ return `${minutes.toString().padStart(2, "0")}:${seconds
13
+ .toString()
14
+ .padStart(2, "0")}`;
15
+ }
16
+
17
+ return `${hours.toString().padStart(2, "0")}:${minutes
18
+ .toString()
19
+ .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
20
+ };
21
+
22
+ export const SleepTimerModalHeader = ({
23
+ summary,
24
+ ...props
25
+ }: {
26
+ summary?: string;
27
+ }) => {
28
+ const sleepTimerDate = +summary;
29
+
30
+ const [timeLeft, setTimeLeft] = React.useState(
31
+ sleepTimerDate !== 0 ? sleepTimerDate - Date.now() : 0
32
+ );
33
+
34
+ const showCounter = React.useMemo(() => sleepTimerDate > 0, [sleepTimerDate]);
35
+
36
+ React.useLayoutEffect(() => {
37
+ if (showCounter && sleepTimerDate !== 0) {
38
+ setTimeLeft(sleepTimerDate - Date.now());
39
+
40
+ const intervalId = setInterval(() => {
41
+ setTimeLeft(() => {
42
+ const newTimeLeft = sleepTimerDate - Date.now();
43
+
44
+ if (newTimeLeft <= 0) {
45
+ clearInterval(intervalId);
46
+
47
+ return 0;
48
+ } else {
49
+ return newTimeLeft;
50
+ }
51
+ });
52
+ }, 1000);
53
+
54
+ return () => clearInterval(intervalId);
55
+ } else {
56
+ setTimeLeft(0);
57
+ }
58
+ }, [sleepTimerDate, showCounter]);
59
+
60
+ return (
61
+ <ModalHeader
62
+ summary={!isNil(summary) ? formatTime(timeLeft) : summary}
63
+ {...(props as any)}
64
+ />
65
+ );
66
+ };
@@ -0,0 +1 @@
1
+ export * from "./SleepTimerModalHeader";
@@ -0,0 +1,109 @@
1
+ import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import {
4
+ SLEEP_TIMER_OFF,
5
+ SleepTimerController,
6
+ SleepTimerState,
7
+ } from "./SleepTimerManager";
8
+
9
+ import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
10
+
11
+ import { SleepTimerModalHeader } from "./components/SleepTimerModalHeader";
12
+ import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
13
+ import {
14
+ getBottomSheetModalLabelProps,
15
+ getBottomSheetModalIconProps,
16
+ getBottomSheetModalItemProps,
17
+ getBottomSheetModalSelectedItemIcon,
18
+ selectSleepTitle,
19
+ selectSleepOffLabel,
20
+ } from "../Utils";
21
+
22
+ export const useSleepTimerModal = (player, configuration) => {
23
+ const playerState = usePlayerState("audio-player-sleep-timer-modal");
24
+
25
+ const title = configuration(selectSleepTitle);
26
+ const offLabel = configuration(selectSleepOffLabel);
27
+
28
+ const [sleepTimerManager, setSleepTimerManager] =
29
+ useState<SleepTimerController | null>(null);
30
+
31
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
32
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
33
+ );
34
+
35
+ useEffect(() => {
36
+ if (player) {
37
+ setSleepTimerManager(new SleepTimerController(player));
38
+ } else {
39
+ setSleepTimerManager(null);
40
+ }
41
+ }, [player]);
42
+
43
+ useEffect(() => {
44
+ if (playerState?.isLive) {
45
+ // Resetting a sleep timer when audio content is live (Timer not supported on live video)
46
+ sleepTimerManager.setSleepTimer(SLEEP_TIMER_OFF).catch();
47
+ }
48
+ }, [playerState?.isLive]);
49
+
50
+ useEffect(() => {
51
+ if (!playerState.isReadyToPlay) {
52
+ return;
53
+ }
54
+
55
+ void sleepTimerManager?.loadSleep();
56
+ }, [playerState.isReadyToPlay, sleepTimerManager]);
57
+
58
+ useEffect(() => {
59
+ const sleepSubscription =
60
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
61
+
62
+ return () => {
63
+ sleepSubscription?.unsubscribe();
64
+ };
65
+ }, [sleepTimerManager]);
66
+
67
+ const onSleepSelect = useCallback(
68
+ async (item) => {
69
+ await sleepTimerManager?.setSleepTimer(item);
70
+ },
71
+ [sleepTimerManager]
72
+ );
73
+
74
+ const onSleepButtonPress = useCallback(() => {
75
+ const timerDate = sleepTimerState.timerDate;
76
+
77
+ const timerRemaining =
78
+ timerDate !== 0 && +timerDate > Date.now() ? timerDate : null;
79
+
80
+ openBottomSheetModal({
81
+ modalBottomSheetContentProps: {
82
+ headerComponent: SleepTimerModalHeader,
83
+ items: getSleepTimerOptions(offLabel, timerRemaining),
84
+ onPress: onSleepSelect,
85
+ title,
86
+ summary: timerRemaining,
87
+ itemProps: getBottomSheetModalItemProps,
88
+ iconProps: getBottomSheetModalIconProps,
89
+ labelProps: getBottomSheetModalLabelProps,
90
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
91
+ },
92
+ options: {
93
+ supportedOrientations: [
94
+ "portrait",
95
+ "landscape",
96
+ "portrait-upside-down",
97
+ ],
98
+ },
99
+ });
100
+ }, [sleepTimerState.timerDate, sleepTimerState.timer, title, onSleepSelect]);
101
+
102
+ return useMemo(
103
+ () => ({
104
+ onSleepButtonPress,
105
+ sleepTimerState,
106
+ }),
107
+ [onSleepButtonPress, sleepTimerState]
108
+ );
109
+ };
@@ -28,12 +28,14 @@ const createTimerOptions = (value) => {
28
28
  };
29
29
  };
30
30
 
31
- export const getSleepTimerOptions = (configuration) => {
31
+ export const getSleepTimerOptions = (offLabel, sleepTimerValue) => {
32
32
  return [
33
- {
34
- label: configuration["playback_sleep_off_label"],
35
- value: 0,
36
- },
33
+ sleepTimerValue
34
+ ? {
35
+ label: offLabel,
36
+ value: 0,
37
+ }
38
+ : null,
37
39
  ...[5, 10, 15, 30, 45, 60, 120].map(createTimerOptions),
38
40
  ];
39
41
  };
@@ -116,3 +116,9 @@ export const getBottomSheetModalSelectedItemIcon = (theme) =>
116
116
 
117
117
  export const getBottomSheetModalDefaultItemIcon = (theme) =>
118
118
  theme["audio_modal_bottom_sheet_item_default_icon"] || null;
119
+
120
+ export const selectSleepTitle = (configuration) =>
121
+ configuration.playback_sleep_title;
122
+
123
+ export const selectSleepOffLabel = (configuration) =>
124
+ configuration.playback_sleep_off_label;
@@ -4,6 +4,8 @@
4
4
  import React from "react";
5
5
  import * as R from "ramda";
6
6
 
7
+ import { flatten } from "@applicaster/zapp-react-native-utils/utils";
8
+
7
9
  import { findNodeHandle, StyleSheet, View } from "react-native";
8
10
  import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
9
11
  import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
@@ -31,7 +33,6 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
31
33
  import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/configurationUtils";
32
34
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
33
35
  import { getTargetScreenDataFromEntry } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData";
34
- import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
35
36
  import {
36
37
  AnimationComponent,
37
38
  ComponentAnimationType,
@@ -40,6 +41,10 @@ import { PlayerAnalyticsManager } from "@applicaster/zapp-react-native-utils/ana
40
41
  import { PlayerAnalyticsTrackerI } from "@applicaster/zapp-react-native-utils/analyticsUtils/playerAnalyticsTracker";
41
42
 
42
43
  import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
44
+ import {
45
+ isAudioItem as isAudioItemUtil,
46
+ isInlineTV as isInlineTVUtil,
47
+ } from "@applicaster/zapp-react-native-utils/playerUtils";
43
48
 
44
49
  const styles = StyleSheet.create({
45
50
  container: { flex: 1 },
@@ -50,6 +55,7 @@ const styles = StyleSheet.create({
50
55
  position: "absolute",
51
56
  zIndex: 1,
52
57
  },
58
+ audioPlayerSize: { width: "100%", height: "100%" },
53
59
  playerSize: { width: "100%", height: "100%" },
54
60
  });
55
61
 
@@ -58,6 +64,10 @@ const logger = createLogger({
58
64
  subsystem: Subsystem,
59
65
  });
60
66
 
67
+ const isTvOS = isTvOSPlatform();
68
+
69
+ const AUDIO_PLAYER_NATIVE_ID = "audio-player-view";
70
+
61
71
  export default class VideoPlayer extends React.Component<
62
72
  QuickBrickPlayer.PlayerProps,
63
73
  QuickBrickPlayer.NativePlayerState
@@ -93,6 +103,8 @@ export default class VideoPlayer extends React.Component<
93
103
  // Player state
94
104
  currentTime: 0,
95
105
  duration: 0,
106
+
107
+ nativePlayerIsReady: false,
96
108
  };
97
109
 
98
110
  private _tracker: PlayerAnalyticsTrackerI;
@@ -488,6 +500,9 @@ export default class VideoPlayer extends React.Component<
488
500
 
489
501
  onVideoFullscreenPlayerDidPresent = () => {
490
502
  this.props.listener?.onVideoFullscreenPlayerDidPresent?.();
503
+
504
+ // we have to have a signal that player is ready, in order to show audio_player(for example)
505
+ this.setState({ nativePlayerIsReady: true });
491
506
  };
492
507
 
493
508
  onVideoFullscreenPlayerWillDismiss = () => {
@@ -673,30 +688,51 @@ export default class VideoPlayer extends React.Component<
673
688
  }
674
689
  };
675
690
 
676
- childrenWithPlayNext = () => {
677
- if (!isTvOSPlatform() || !this.getPlayNextData()) {
678
- return this.props.children;
691
+ childrenWithExtensions = () => {
692
+ const { docked, entry, children } = this.props;
693
+
694
+ if (!isTvOS) {
695
+ return children;
679
696
  }
680
697
 
681
- // tvos only
682
- const { docked, entry } = this.props;
683
-
684
- const children = R.pathOr([], "children", this.props);
685
-
686
- return [
687
- ...children,
688
- <PlayNextOverlay
689
- key="play-next-overlay"
690
- item={entry}
691
- playerIsDocked={docked}
692
- pluginConfiguration={this.getPluginConfiguration()}
693
- playerId={this.props.playerId}
694
- playNextData={this.getPlayNextData()}
695
- setNextVideoPreloadThresholdPercentage={
696
- this.props.setNextVideoPreloadThresholdPercentage
697
- }
698
- />,
699
- ];
698
+ const output = [];
699
+
700
+ // audio-player
701
+ if (isAudioItemUtil(entry)) {
702
+ const { nativePlayerIsReady } = this.state;
703
+
704
+ // we have to wait ready status of native player view, before showing audio_player
705
+ if (nativePlayerIsReady) {
706
+ output.push(
707
+ <View nativeID={AUDIO_PLAYER_NATIVE_ID} key={AUDIO_PLAYER_NATIVE_ID}>
708
+ {children}
709
+ </View>
710
+ );
711
+ }
712
+ } else {
713
+ output.push(children);
714
+ }
715
+ // audio-player
716
+
717
+ // play-next
718
+ if (this.getPlayNextData()) {
719
+ output.push(
720
+ <PlayNextOverlay
721
+ key="play-next-overlay"
722
+ item={entry}
723
+ playerIsDocked={docked}
724
+ pluginConfiguration={this.getPluginConfiguration()}
725
+ playerId={this.props.playerId}
726
+ playNextData={this.getPlayNextData()}
727
+ setNextVideoPreloadThresholdPercentage={
728
+ this.props.setNextVideoPreloadThresholdPercentage
729
+ }
730
+ />
731
+ );
732
+ }
733
+ // play-next
734
+
735
+ return flatten(output); // prevent possible case with nested array
700
736
  };
701
737
 
702
738
  get supportNativeCast() {
@@ -721,9 +757,7 @@ export default class VideoPlayer extends React.Component<
721
757
  const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
722
758
  this.state;
723
759
 
724
- const isAudioItem =
725
- entry?.content?.type?.includes?.("audio") ||
726
- entry?.type?.value?.includes?.("audio");
760
+ const isAudioItem = isAudioItemUtil(entry);
727
761
 
728
762
  const getStyle = (dimensions) => {
729
763
  const generalStyles = isAudioItem ? styles.audio : styles.video;
@@ -767,7 +801,7 @@ export default class VideoPlayer extends React.Component<
767
801
  // TODO: Get screen data more reliable way useCurrentScreenData
768
802
  const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
769
803
 
770
- const isInlineTV = screenData?.ui_components?.length > 0 && isTV();
804
+ const isInlineTV = isInlineTVUtil(screenData);
771
805
 
772
806
  let platformSpecificProps: any = isApplePlatform()
773
807
  ? {
@@ -778,7 +812,7 @@ export default class VideoPlayer extends React.Component<
778
812
  const supportsNativeControls =
779
813
  !!this.props.controller?.supportsNativeControls?.();
780
814
 
781
- const controls = isAppleTV()
815
+ const controls = isTvOS
782
816
  ? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
783
817
  : supportsNativeControls && !this.getPlayNextData();
784
818
 
@@ -825,7 +859,7 @@ export default class VideoPlayer extends React.Component<
825
859
  ),
826
860
  };
827
861
 
828
- if (!isAppleTV()) {
862
+ if (!isTvOS) {
829
863
  // TODO: Move to RN method and remove from props.
830
864
  // Causes tvOS bug: native player passes rate on fast seek,
831
865
  // RN returns it and it breaks playback.
@@ -1078,7 +1112,7 @@ export default class VideoPlayer extends React.Component<
1078
1112
  pluginConfiguration={this.getPluginConfiguration()}
1079
1113
  controller={this.props.controller}
1080
1114
  >
1081
- {this.childrenWithPlayNext()}
1115
+ {this.props.children}
1082
1116
  </this.props.PlayerComponent>
1083
1117
  </AnimationComponent>
1084
1118
  ) : null}
@@ -1107,6 +1141,14 @@ export default class VideoPlayer extends React.Component<
1107
1141
  const renderTVPlayer = () => {
1108
1142
  return (
1109
1143
  <View style={this.takeParentStyle} testID={"player-view"}>
1144
+ {isAudioItem && (isInlineTV || !isTvOS) ? (
1145
+ <AudioPlayer
1146
+ style={getStyle(styles.audioPlayerSize)}
1147
+ audio_item={entry}
1148
+ plugin_configuration={this.getPluginConfiguration()}
1149
+ />
1150
+ ) : null}
1151
+
1110
1152
  <this.props.PlayerComponent
1111
1153
  ref={this._assignRoot}
1112
1154
  {...nativeEvents}
@@ -1116,11 +1158,11 @@ export default class VideoPlayer extends React.Component<
1116
1158
  pluginConfiguration={this.getPluginConfiguration()}
1117
1159
  controller={this.props.controller}
1118
1160
  >
1119
- {this.childrenWithPlayNext()}
1161
+ {!isInlineTV && this.childrenWithExtensions()}
1120
1162
  </this.props.PlayerComponent>
1121
1163
 
1122
1164
  {renderTransportControls(this.takeParentStyle)}
1123
- {!isTvOSPlatform() && (
1165
+ {!isTvOS && (
1124
1166
  <PlayNextOverlay
1125
1167
  item={entry}
1126
1168
  playerIsDocked={docked}