@applicaster/quick-brick-player 13.0.0-alpha.3962361544 → 13.0.0-alpha.4012243505

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +6 -11
  2. package/src/Player/AudioLayer/AudioDescription.tsx +5 -7
  3. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +60 -88
  4. package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +27 -0
  5. package/src/Player/AudioLayer/Layout/Controls/index.tsx +316 -0
  6. package/src/Player/AudioLayer/Layout/{defaultLayoutsConts.ts → Controls/utils.ts} +5 -23
  7. package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
  8. package/src/Player/AudioLayer/Layout/{DockedLayout.tsx → DockedControls/index.tsx} +30 -53
  9. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +231 -0
  10. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +83 -0
  11. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +25 -0
  12. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +101 -0
  13. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +26 -0
  14. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +216 -0
  15. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +224 -0
  16. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +114 -0
  17. package/src/Player/AudioLayer/Layout/VerticalGutter/index.tsx +8 -0
  18. package/src/Player/AudioLayer/Layout/const.ts +22 -0
  19. package/src/Player/AudioLayer/Layout/debug.ts +6 -0
  20. package/src/Player/AudioLayer/Layout/hooks/index.ts +5 -0
  21. package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
  22. package/src/Player/AudioLayer/Layout/hooks/useTabletLayoutImageSize.ts +105 -0
  23. package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +54 -0
  24. package/src/Player/AudioLayer/Layout/index.tsx +21 -48
  25. package/src/Player/AudioLayer/Layout/types.ts +7 -0
  26. package/src/Player/AudioLayer/MagicBG/index.tsx +1 -1
  27. package/src/Player/AudioLayer/utils.ts +27 -0
  28. package/src/Player/Const/sleepTimerOptions.ts +39 -0
  29. package/src/Player/Const/speedOptions.ts +4 -0
  30. package/src/Player/Utils/Utils.ts +0 -5
  31. package/src/Player/Utils/index.tsx +117 -0
  32. package/src/Player/index.tsx +13 -7
  33. package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -453
  34. package/src/Player/Helpers/VideoResizeMode.ts +0 -7
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "13.0.0-alpha.3962361544",
3
+ "version": "13.0.0-alpha.4012243505",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
7
7
  "scripts": {
8
- "test": "jest -w=2",
9
8
  "build": "echo 'skipping ts build for core packages'"
10
9
  },
11
10
  "files": [
@@ -36,12 +35,11 @@
36
35
  },
37
36
  "homepage": "https://github.com/applicaster/quickbrick#readme",
38
37
  "dependencies": {
39
- "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.46",
40
- "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.46",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.3962361544",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.3962361544",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.3962361544",
44
- "keymirror": "^0.1.1",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.99",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.4012243505",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.4012243505",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.4012243505",
45
43
  "query-string": "7.1.3",
46
44
  "react-native-web-linear-gradient": "1.1.2",
47
45
  "shaka-player": "4.3.5",
@@ -61,11 +59,8 @@
61
59
  "@applicaster/zapp-react-native-tvos-ui-components": "*",
62
60
  "@babel/runtime": "*",
63
61
  "@react-native-community/netinfo": "*",
64
- "@types/node": "*",
65
- "@types/react-native": "0.69.6",
66
62
  "immer": "*",
67
63
  "react": "*",
68
- "react-hot-loader": "*",
69
64
  "react-native": "*",
70
65
  "react-native-fs": "*",
71
66
  "react-native-gesture-handler": "*",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Text, TextStyle, View } from "react-native";
2
+ import { Text, TextStyle } from "react-native";
3
3
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
4
4
 
5
5
  type AudioDescriptionProps = {
@@ -13,8 +13,6 @@ type AudioDescriptionProps = {
13
13
  value: GetValue;
14
14
  };
15
15
 
16
- const containerStyles = { paddingTop: 32, paddingBottom: 12 };
17
-
18
16
  const titleStyles = (value: GetValue, prefix: string): TextStyle => ({
19
17
  textAlign: "left",
20
18
  color: value(`${prefix}title_color`),
@@ -83,13 +81,13 @@ export const AudioDescription = (props: AudioDescriptionProps) => {
83
81
  const prefix = layoutState.docked ? "docked_player_" : "";
84
82
 
85
83
  return (
86
- <View style={containerStyles}>
87
- <Text style={titleStyles(value, prefix)} numberOfLines={1}>
84
+ <>
85
+ <Text style={titleStyles(value, prefix)} numberOfLines={2}>
88
86
  {title}
89
87
  </Text>
90
- <Text style={summaryStyles(value, prefix)} numberOfLines={2}>
88
+ <Text style={summaryStyles(value, prefix)} numberOfLines={1}>
91
89
  {summary}
92
90
  </Text>
93
- </View>
91
+ </>
94
92
  );
95
93
  };
@@ -1,5 +1,10 @@
1
1
  import * as React from "react";
2
- import { ViewStyle, View, Dimensions as ScreenDimensions } 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";
@@ -18,22 +23,22 @@ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
23
  import { useEffect, useState } from "react";
19
24
  import { MagicBG } from "./MagicBG";
20
25
  import { formatSpeed } from "./utils";
21
- import { addQueryParam, getLiveUrl } from "../Utils";
26
+ import {
27
+ addQueryParam,
28
+ getBottomSheetModalDefaultItemIcon,
29
+ getBottomSheetModalIconProps,
30
+ getBottomSheetModalItemProps,
31
+ getBottomSheetModalLabelProps,
32
+ getBottomSheetModalSelectedItemIcon,
33
+ } from "../Utils";
22
34
  import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
23
35
  import {
24
36
  AnimationComponent,
25
37
  ComponentAnimationType,
26
38
  AnimatedScrollModal,
27
39
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
28
- import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
40
+ import { PADDING_HORIZONTAL } from "./Layout/const";
29
41
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
30
- import {
31
- speed_0_8,
32
- speed_1,
33
- speed_1_2,
34
- speed_1_5,
35
- speed_2,
36
- } from "@applicaster/quick-brick-mobile-transport-controls";
37
42
  import {
38
43
  SleepTimerController,
39
44
  SleepTimerState,
@@ -41,6 +46,10 @@ import {
41
46
  } from "./SleepTimerManager";
42
47
  import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
43
48
  import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
49
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
50
+ import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
51
+ import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
52
+ import { speedOptions } from "../Const/speedOptions";
44
53
 
45
54
  const { log_debug } = createLogger({
46
55
  subsystem: "UIComponent",
@@ -58,6 +67,7 @@ type Configuration = {
58
67
  [key: string]: any;
59
68
  magic_background?: boolean;
60
69
  audio_player_background_image_query?: string;
70
+ audio_player_background_image_default_color?: string;
61
71
  tablet_landscape_sidebar_width?: string;
62
72
  tablet_landscape_player_container_background_color?: string;
63
73
  };
@@ -81,55 +91,24 @@ type Props = {
81
91
 
82
92
  const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
83
93
 
84
- const Styles = {
85
- mainContainer: (style: ViewStyle) => {
86
- const combinedStyle: ViewStyle = {
87
- flex: 1,
88
- };
89
-
90
- return {
91
- ...combinedStyle,
92
- ...style,
93
- };
94
- },
94
+ const Styles = StyleSheet.create({
95
95
  playerContainer: {
96
96
  flex: 1,
97
97
  },
98
98
  paddingHorizontal: { paddingTop: 0, paddingHorizontal: PADDING_HORIZONTAL },
99
-
100
- controlsMobile: {
101
- height: 50,
102
- marginHorizontal: 24,
103
- },
104
- controlsTablet: {
105
- height: 96,
106
- marginHorizontal: 24,
107
- },
108
99
  playerDetails: {
109
100
  marginTop: 60,
110
101
  alignSelf: "center",
111
102
  width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
112
103
  },
104
+ });
113
105
 
114
- linearGradient: {
115
- flex: 1,
116
- },
117
- };
118
-
119
- const sleeps = [
120
- // { label: "End of episode", value: "episode" },
121
- { label: "5 minutes", value: 5 },
122
- { label: "10 minutes", value: 10 },
123
- { label: "15 minutes", value: 15 },
124
- { label: "30 minutes", value: 30 },
125
- { label: "45 minutes", value: 45 },
126
- { label: "1 hour", value: 60 },
127
- { label: "2 hours", value: 120 },
128
- ];
106
+ const edges: Edge[] = ["right", "left", "top"];
129
107
 
130
108
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
131
109
  const player = usePlayer();
132
110
  const playerState = usePlayerState("audio-player-wrapper");
111
+ const theme = useTheme();
133
112
 
134
113
  const {
135
114
  docked,
@@ -142,24 +121,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
142
121
  isTabletPortrait,
143
122
  } = props;
144
123
 
145
- const speeds = [
146
- { asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
147
- { asset: configuration["speed_1"] || speed_1, value: 1.0 },
148
- { asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
149
- { asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
150
- { asset: configuration["speed_2"] || speed_2, value: 2.0 },
151
- ];
152
-
153
124
  const isTablet = useIsTablet();
154
125
 
155
126
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
156
127
 
157
- // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
158
-
159
- const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
160
-
161
128
  const { title, summary } = useTitleSummaryOverlay(
162
- feedUrl,
163
129
  entry,
164
130
  playerState?.isLive,
165
131
  configuration?.audio_live_player_update_interval
@@ -197,8 +163,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
197
163
  height: 0,
198
164
  });
199
165
 
200
- const navigator = useNavigation();
201
-
202
166
  const { maximiseVideoModal } = useNavigation();
203
167
 
204
168
  const onLayout = (event: any) => {
@@ -288,44 +252,51 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
288
252
  );
289
253
 
290
254
  const onSpeedButtonPress = React.useCallback(() => {
291
- navigator.openModalBottomSheet({
255
+ openBottomSheetModal({
292
256
  modalBottomSheetContentProps: {
293
- items: speeds,
257
+ items: speedOptions,
294
258
  current_selection: playbackSpeed,
295
259
  onPress: onSpeedSelect,
296
260
  title: configuration["playback_speed_title"],
261
+ itemProps: getBottomSheetModalItemProps,
262
+ iconProps: getBottomSheetModalIconProps,
263
+ labelProps: getBottomSheetModalLabelProps,
264
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
265
+ getDefaultItemIcon: getBottomSheetModalDefaultItemIcon,
266
+ iconPlacement: "right",
297
267
  },
298
268
  options: {
299
- supportedOrientations: ["portrait"],
269
+ supportedOrientations: [
270
+ "portrait",
271
+ "landscape",
272
+ "portrait-upside-down",
273
+ ],
300
274
  },
301
275
  });
302
- }, [
303
- playbackSpeed,
304
- setPlaybackSpeed,
305
- navigator,
306
- onSpeedSelect,
307
- configuration["playback_speed_title"],
308
- ]);
276
+ }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
309
277
 
310
278
  const onSleepButtonPress = React.useCallback(() => {
311
- navigator.openModalBottomSheet({
279
+ openBottomSheetModal({
312
280
  modalBottomSheetContentProps: {
313
- items: [
314
- {
315
- label: configuration["playback_sleep_off_label"],
316
- value: 0,
317
- },
318
- ].concat(sleeps),
281
+ items: getSleepTimerOptions(configuration),
319
282
  current_selection: sleepTimerState.timer,
320
283
  onPress: onSleepSelect,
321
284
  title: configuration["playback_sleep_title"],
285
+ itemProps: getBottomSheetModalItemProps,
286
+ iconProps: getBottomSheetModalIconProps,
287
+ labelProps: getBottomSheetModalLabelProps,
288
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
289
+ iconPlacement: "right",
322
290
  },
323
291
  options: {
324
- supportedOrientations: ["portrait"],
292
+ supportedOrientations: [
293
+ "portrait",
294
+ "landscape",
295
+ "portrait-upside-down",
296
+ ],
325
297
  },
326
298
  });
327
299
  }, [
328
- navigator,
329
300
  sleepTimerState.timer,
330
301
  configuration["playback_sleep_title"],
331
302
  configuration["playback_sleep_off_label"],
@@ -359,13 +330,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
359
330
  player?.seekTo(0);
360
331
  }, [player]);
361
332
 
362
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
363
-
364
- const ContainerView = SafeAreaView;
365
-
366
333
  const MainContent = React.useMemo(() => {
367
334
  return (
368
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
335
+ <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
369
336
  <AnimationComponent
370
337
  animationType={ComponentAnimationType.moveUpComponent}
371
338
  additionalData={{ saveArea: true }}
@@ -409,18 +376,18 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
409
376
  animationType={ComponentAnimationType.componentFade}
410
377
  >
411
378
  <PlayerDetails
379
+ isAudioPlayer
412
380
  configuration={configuration}
413
381
  style={Styles.playerDetails}
414
382
  entry={entry}
415
383
  isTabletLandscape={isTabletLandscape}
416
- isAudioPlayer={true}
417
384
  />
418
385
  </AnimationComponent>
419
386
  </View>
420
387
  </View>
421
388
  </AnimatedScrollModal>
422
389
  </AnimationComponent>
423
- </ContainerView>
390
+ </SafeAreaView>
424
391
  );
425
392
  }, [
426
393
  docked,
@@ -451,16 +418,21 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
451
418
  const queryParamName =
452
419
  configuration.audio_player_background_image_query || "width";
453
420
 
421
+ const defaultBackgroundColor =
422
+ configuration.audio_player_background_image_default_color ||
423
+ theme?.app_background_color;
424
+
454
425
  const queryParamValue = 15;
455
426
 
456
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
427
+ const url =
428
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
457
429
 
458
430
  return (
459
431
  <MagicBG
460
432
  image={url}
461
433
  fallbackUrl={baseUrl}
462
434
  colors={entry?.extensions?.backgroundColors}
463
- defaultColor="#000000"
435
+ defaultColor={defaultBackgroundColor}
464
436
  >
465
437
  {MainContent}
466
438
  </MagicBG>
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+
4
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+
6
+ import { TABLET_VERTICAL_SEPARATOR_HEIGHT } from "../const";
7
+
8
+ import { withDebug } from "../debug";
9
+
10
+ const styles = StyleSheet.create({
11
+ flexingHeight: {
12
+ flex: 1,
13
+
14
+ ...withDebug({ backgroundColor: "red" }),
15
+ },
16
+ fixedHeight: {
17
+ height: TABLET_VERTICAL_SEPARATOR_HEIGHT,
18
+
19
+ ...withDebug({ backgroundColor: "red" }),
20
+ },
21
+ });
22
+
23
+ export const VerticalGutter = () => {
24
+ const isTablet = useIsTablet();
25
+
26
+ return <View style={isTablet ? styles.fixedHeight : styles.flexingHeight} />;
27
+ };
@@ -0,0 +1,316 @@
1
+ import * as React from "react";
2
+ import { StyleSheet } from "react-native";
3
+
4
+ import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
5
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
6
+ import {
7
+ Controls as MobileTransportControls,
8
+ ProgressBar,
9
+ } from "@applicaster/quick-brick-mobile-transport-controls";
10
+ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
11
+
12
+ import {
13
+ AnimationComponent,
14
+ ComponentAnimationType,
15
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
16
+
17
+ import { AudioDescription } from "../../AudioDescription";
18
+ import { isValidDate } from "../../../Utils";
19
+ import { VerticalGutter } from "./VerticalGutter";
20
+
21
+ import {
22
+ forwardButton,
23
+ playbackButton,
24
+ rewindButton,
25
+ skipToLiveButton,
26
+ skipToStartButton,
27
+ sleepButton,
28
+ speedButton,
29
+ } from "./utils";
30
+
31
+ const styles = StyleSheet.create({
32
+ flex: {
33
+ flex: 1,
34
+ },
35
+ nonFlex: {
36
+ flex: 0,
37
+ },
38
+ progressBar: {
39
+ flex: 0,
40
+ },
41
+ progressBarTrackStyles: {
42
+ borderRadius: 3,
43
+ height: 6,
44
+ },
45
+ progressBarThumbStyles: {
46
+ width: 14,
47
+ height: 14,
48
+ borderRadius: 7,
49
+ },
50
+ progressBarThumbStyles_pressed: {
51
+ width: 20,
52
+ height: 20,
53
+ borderRadius: 10,
54
+ },
55
+ });
56
+
57
+ const getControlJustifyContent = (countOfButtons: number) =>
58
+ countOfButtons <= 1 ? "center" : "space-between";
59
+
60
+ type Props = {
61
+ entry: ZappEntry;
62
+ layoutState: QuickBrickPlayer.LayoutState;
63
+ playerState: QuickBrickPlayer.PlayerState;
64
+ value: GetValue;
65
+ title: string | number;
66
+ summary: string | number;
67
+ sleepTimerDate: number;
68
+ cancelSleepTimer: Callback;
69
+
70
+ playbackSpeedName: string;
71
+ playbackSleepName: string;
72
+
73
+ onPlaybackButtonPress: Callback;
74
+ onRewindButtonPress: Callback;
75
+ onForwardButtonPress: Callback;
76
+ onPlayerSeek: (targetTime: number) => void;
77
+ onSkipStart: Callback;
78
+ onSkipLive: Callback;
79
+ onSpeedButtonPress: Callback;
80
+ onSleepButtonPress: Callback;
81
+
82
+ docked: boolean;
83
+ };
84
+
85
+ export const Controls = (props: Props) => {
86
+ const {
87
+ entry,
88
+ layoutState,
89
+ playerState,
90
+ value,
91
+ title,
92
+ summary,
93
+ sleepTimerDate,
94
+ cancelSleepTimer,
95
+
96
+ playbackSpeedName,
97
+ playbackSleepName,
98
+
99
+ onPlaybackButtonPress,
100
+ onRewindButtonPress,
101
+ onForwardButtonPress,
102
+ onPlayerSeek,
103
+ onSkipStart,
104
+ onSkipLive,
105
+ onSpeedButtonPress,
106
+ onSleepButtonPress,
107
+
108
+ docked,
109
+ } = props;
110
+
111
+ const isTablet = useIsTablet();
112
+ const controller = playerManager.getActivePlayer();
113
+ let showFullControls = !controller?.isAd();
114
+ const liveButton = Boolean(value("live_badge_enabled"));
115
+
116
+ const isLiveContent = playerState.isLive;
117
+
118
+ if (isLiveContent) {
119
+ const seekEnabled = Boolean(value("liveSeekingEnabled"));
120
+
121
+ showFullControls = !!(controller?.hasSeekableDuration?.() && seekEnabled);
122
+ }
123
+
124
+ const [timeLeft, setTimeLeft] = React.useState(
125
+ sleepTimerDate !== 0 ? sleepTimerDate - Date.now() : 0
126
+ );
127
+
128
+ const showCounter = React.useMemo(() => sleepTimerDate > 0, [sleepTimerDate]);
129
+
130
+ const countDownOptions = React.useMemo(() => {
131
+ return {
132
+ showCounter: showCounter && timeLeft >= 0,
133
+ timeLeft,
134
+ };
135
+ }, [showCounter, timeLeft]);
136
+
137
+ React.useEffect(() => {
138
+ if (showCounter && sleepTimerDate !== 0) {
139
+ setTimeLeft(sleepTimerDate - Date.now());
140
+
141
+ const intervalId = setInterval(() => {
142
+ setTimeLeft(() => {
143
+ const newTimeLeft = sleepTimerDate - Date.now();
144
+
145
+ if (newTimeLeft <= 0) {
146
+ clearInterval(intervalId);
147
+
148
+ cancelSleepTimer();
149
+
150
+ return 0;
151
+ } else {
152
+ return newTimeLeft;
153
+ }
154
+ });
155
+ }, 1000);
156
+
157
+ return () => clearInterval(intervalId);
158
+ } else {
159
+ setTimeLeft(0);
160
+ }
161
+ }, [sleepTimerDate, showCounter, cancelSleepTimer]);
162
+
163
+ const startTime = entry.extensions?.live?.start_time;
164
+ const endTime = entry.extensions?.live?.end_time;
165
+
166
+ const hasValidStartTime = isValidDate(startTime);
167
+ const hasValidEndTime = isValidDate(endTime);
168
+
169
+ const isDateBasedLiveContent =
170
+ isLiveContent && hasValidStartTime && hasValidEndTime;
171
+
172
+ const needsToShowProgressBar =
173
+ (isLiveContent &&
174
+ (Boolean(value("liveSeekingEnabled")) || isDateBasedLiveContent)) ||
175
+ !isLiveContent;
176
+
177
+ const startTimestamp = hasValidStartTime
178
+ ? dayjs.utc(startTime!).valueOf()
179
+ : undefined;
180
+
181
+ const endTimestamp = hasValidEndTime
182
+ ? dayjs.utc(endTime!).valueOf()
183
+ : undefined;
184
+
185
+ const isLiveShowFullControls = showFullControls && isLiveContent;
186
+
187
+ const controlButtons = React.useCallback((): Array<ControlsButton> => {
188
+ const play = playbackButton({
189
+ playerState,
190
+ liveButton,
191
+ isTablet,
192
+ isLiveShowFullControls,
193
+ onPress: onPlaybackButtonPress,
194
+ });
195
+
196
+ if (!showFullControls) {
197
+ return [play];
198
+ }
199
+
200
+ const rewind = rewindButton({
201
+ isTablet,
202
+ onPress: controller?.hasSeekableDuration?.() ? onRewindButtonPress : null,
203
+ });
204
+
205
+ const forward = forwardButton({
206
+ isTablet,
207
+ onPress: controller?.hasSeekableDuration?.()
208
+ ? onForwardButtonPress
209
+ : null,
210
+ });
211
+
212
+ const speed = speedButton({
213
+ isTablet,
214
+ onPress: onSpeedButtonPress,
215
+ name: playbackSpeedName,
216
+ });
217
+
218
+ const sleep = sleepButton({
219
+ isTablet,
220
+ onPress: onSleepButtonPress,
221
+ name: playbackSleepName,
222
+ });
223
+
224
+ if (isLiveContent) {
225
+ const skipToStart = skipToStartButton({
226
+ isTablet,
227
+ onPress: onSkipStart,
228
+ });
229
+
230
+ const skipToLive = skipToLiveButton({
231
+ isTablet,
232
+ onPress: onSkipLive,
233
+ });
234
+
235
+ return [skipToStart, rewind, play, forward, skipToLive];
236
+ }
237
+
238
+ return [speed, rewind, play, forward, sleep];
239
+ }, [
240
+ playerState,
241
+ liveButton,
242
+ isTablet,
243
+ onPlaybackButtonPress,
244
+ onRewindButtonPress,
245
+ onForwardButtonPress,
246
+ onSpeedButtonPress,
247
+ showFullControls,
248
+ onSkipStart,
249
+ onSkipLive,
250
+ isLiveContent,
251
+ playbackSpeedName,
252
+ playbackSleepName,
253
+ controller,
254
+ isLiveShowFullControls,
255
+ ]);
256
+
257
+ if (docked) {
258
+ return null;
259
+ }
260
+
261
+ return (
262
+ <AnimationComponent
263
+ animationType={ComponentAnimationType.componentFade}
264
+ style={isTablet ? styles.nonFlex : styles.flex}
265
+ >
266
+ <VerticalGutter />
267
+
268
+ <AudioDescription
269
+ title={title}
270
+ summary={summary}
271
+ value={value}
272
+ layoutState={layoutState}
273
+ />
274
+
275
+ <VerticalGutter />
276
+
277
+ {needsToShowProgressBar ? (
278
+ <>
279
+ <ProgressBar
280
+ name={"audio-default-layout"}
281
+ content={entry}
282
+ value={value}
283
+ visible
284
+ layoutState={layoutState}
285
+ onPlayerSeek={onPlayerSeek}
286
+ playerState={playerState}
287
+ disableFullscreen
288
+ sliderStyle={2}
289
+ startTimestamp={startTimestamp}
290
+ endTimestamp={endTimestamp}
291
+ isDateBasedLiveContent={isDateBasedLiveContent}
292
+ onSkipLive={onSkipLive}
293
+ progressBarStyles={styles.progressBar}
294
+ countDownOptions={countDownOptions}
295
+ thumbStyles={styles.progressBarThumbStyles}
296
+ thumbStylesPressed={styles.progressBarThumbStyles_pressed}
297
+ trackStyles={styles.progressBarTrackStyles}
298
+ />
299
+
300
+ <VerticalGutter />
301
+ </>
302
+ ) : null}
303
+
304
+ <MobileTransportControls
305
+ value={value}
306
+ visible={true}
307
+ keepAlive={null}
308
+ buttons={controlButtons}
309
+ style={{
310
+ justifyContent: getControlJustifyContent(controlButtons().length),
311
+ }}
312
+ playerState={playerState}
313
+ />
314
+ </AnimationComponent>
315
+ );
316
+ };