@applicaster/quick-brick-player 13.0.0-alpha.7019824418 → 13.0.0-alpha.7048001189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "13.0.0-alpha.7019824418",
3
+ "version": "13.0.0-alpha.7048001189",
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.46",
40
- "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.46",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.7019824418",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.7019824418",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.7019824418",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.88",
39
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.7048001189",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.7048001189",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.7048001189",
44
43
  "keymirror": "^0.1.1",
45
44
  "query-string": "7.1.3",
46
45
  "react-native-web-linear-gradient": "1.1.2",
@@ -61,11 +60,8 @@
61
60
  "@applicaster/zapp-react-native-tvos-ui-components": "*",
62
61
  "@babel/runtime": "*",
63
62
  "@react-native-community/netinfo": "*",
64
- "@types/node": "*",
65
- "@types/react-native": "0.69.6",
66
63
  "immer": "*",
67
64
  "react": "*",
68
- "react-hot-loader": "*",
69
65
  "react-native": "*",
70
66
  "react-native-fs": "*",
71
67
  "react-native-gesture-handler": "*",
@@ -55,6 +55,11 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
55
55
  return;
56
56
  }
57
57
 
58
+ // initial state setup - show correct mute/unmute icon based on current state
59
+ if (player?.playerState?.isReadyToPlay) {
60
+ setImageUri(player.getIsMuted() ? asset.active : asset.inactive);
61
+ }
62
+
58
63
  // active action = muted, inactive = unmuted
59
64
  return player.addListener({
60
65
  id: liveImageButtonsListenerKey,
@@ -73,10 +78,17 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
73
78
  setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
74
79
  },
75
80
  onPlayerClose: () => {
76
- setImageUri(null);
81
+ if (isCurrentPlayer(playerId)) {
82
+ setImageUri(null);
83
+ }
77
84
  },
78
85
  onVideoEnd: () => {
79
- setImageUri(null);
86
+ if (isCurrentPlayer(playerId)) {
87
+ setImageUri(null);
88
+ }
89
+ },
90
+ onPlayerResume: () => {
91
+ setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
80
92
  },
81
93
  },
82
94
  });
@@ -93,7 +105,7 @@ const styles = StyleSheet.create({
93
105
  },
94
106
  });
95
107
 
96
- export function AssetComponent(props: CellActionAssetComponentProps) {
108
+ function AssetImage(props: CellActionAssetComponentProps) {
97
109
  const { cellUUID: playerId, asset } = props;
98
110
  const [imageUri, setImageUri] = useState<Asset>(null);
99
111
 
@@ -108,3 +120,5 @@ export function AssetComponent(props: CellActionAssetComponentProps) {
108
120
 
109
121
  return <View />;
110
122
  }
123
+
124
+ export const AssetComponent = React.memo(AssetImage);
@@ -1,5 +1,10 @@
1
1
  import * as React from "react";
2
- import { ViewStyle, View } from "react-native";
2
+ import {
3
+ StyleSheet,
4
+ ViewStyle,
5
+ View,
6
+ Dimensions as ScreenDimensions,
7
+ } from "react-native";
3
8
 
4
9
  import { SafeAreaView, Edge } from "react-native-safe-area-context";
5
10
  import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
@@ -18,7 +23,14 @@ 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,
@@ -27,19 +39,17 @@ import {
27
39
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
28
40
  import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
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,
40
45
  SLEEP_TIMER_OFF,
41
46
  } from "./SleepTimerManager";
42
47
  import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
48
+ import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
49
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
50
+ import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
51
+ import { sleepTimerOptions } from "../Const/sleepTimerOptions";
52
+ import { speedOptions } from "../Const/speedOptions";
43
53
 
44
54
  const { log_debug } = createLogger({
45
55
  subsystem: "UIComponent",
@@ -57,6 +67,7 @@ type Configuration = {
57
67
  [key: string]: any;
58
68
  magic_background?: boolean;
59
69
  audio_player_background_image_query?: string;
70
+ audio_player_background_image_default_color?: string;
60
71
  tablet_landscape_sidebar_width?: string;
61
72
  tablet_landscape_player_container_background_color?: string;
62
73
  };
@@ -78,54 +89,26 @@ type Props = {
78
89
  isTabletPortrait?: boolean;
79
90
  };
80
91
 
81
- export const defaultAspectRatioWidth = (height: number): number =>
82
- (height / 9) * 16;
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
- playerDetails: { marginTop: 60 },
109
-
110
- linearGradient: {
111
- flex: 1,
99
+ playerDetails: {
100
+ marginTop: 60,
101
+ alignSelf: "center",
102
+ width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
112
103
  },
113
- };
104
+ });
114
105
 
115
- const sleeps = [
116
- // { label: "End of episode", value: "episode" },
117
- { label: "5 minutes", value: 5 },
118
- { label: "10 minutes", value: 10 },
119
- { label: "15 minutes", value: 15 },
120
- { label: "30 minutes", value: 30 },
121
- { label: "45 minutes", value: 45 },
122
- { label: "1 hour", value: 60 },
123
- { label: "2 hours", value: 120 },
124
- ];
106
+ const edges: Edge[] = ["right", "left", "top"];
125
107
 
126
108
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
127
109
  const player = usePlayer();
128
110
  const playerState = usePlayerState("audio-player-wrapper");
111
+ const theme = useTheme();
129
112
 
130
113
  const {
131
114
  docked,
@@ -138,24 +121,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
138
121
  isTabletPortrait,
139
122
  } = props;
140
123
 
141
- const speeds = [
142
- { asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
143
- { asset: configuration["speed_1"] || speed_1, value: 1.0 },
144
- { asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
145
- { asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
146
- { asset: configuration["speed_2"] || speed_2, value: 2.0 },
147
- ];
148
-
149
124
  const isTablet = useIsTablet();
150
125
 
151
126
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
152
127
 
153
- // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
154
-
155
- const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
156
-
157
128
  const { title, summary } = useTitleSummaryOverlay(
158
- feedUrl,
159
129
  entry,
160
130
  playerState?.isLive,
161
131
  configuration?.audio_live_player_update_interval
@@ -193,8 +163,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
193
163
  height: 0,
194
164
  });
195
165
 
196
- const navigator = useNavigation();
197
-
198
166
  const { maximiseVideoModal } = useNavigation();
199
167
 
200
168
  const onLayout = (event: any) => {
@@ -284,44 +252,48 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
284
252
  );
285
253
 
286
254
  const onSpeedButtonPress = React.useCallback(() => {
287
- navigator.openModalBottomSheet({
255
+ openBottomSheetModal({
288
256
  modalBottomSheetContentProps: {
289
- items: speeds,
257
+ items: speedOptions,
290
258
  current_selection: playbackSpeed,
291
259
  onPress: onSpeedSelect,
292
260
  title: configuration["playback_speed_title"],
261
+ itemProps: getBottomSheetModalItemProps,
262
+ iconProps: getBottomSheetModalIconProps,
263
+ labelProps: getBottomSheetModalLabelProps,
264
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
265
+ getDefaultItemIcon: getBottomSheetModalDefaultItemIcon,
266
+ iconPlacement: "right",
293
267
  },
294
268
  options: {
295
269
  supportedOrientations: ["portrait"],
296
270
  },
297
271
  });
298
- }, [
299
- playbackSpeed,
300
- setPlaybackSpeed,
301
- navigator,
302
- onSpeedSelect,
303
- configuration["playback_speed_title"],
304
- ]);
272
+ }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
305
273
 
306
274
  const onSleepButtonPress = React.useCallback(() => {
307
- navigator.openModalBottomSheet({
275
+ openBottomSheetModal({
308
276
  modalBottomSheetContentProps: {
309
277
  items: [
310
278
  {
311
279
  label: configuration["playback_sleep_off_label"],
312
280
  value: 0,
313
281
  },
314
- ].concat(sleeps),
282
+ ].concat(sleepTimerOptions),
315
283
  current_selection: sleepTimerState.timer,
316
284
  onPress: onSleepSelect,
317
285
  title: configuration["playback_sleep_title"],
286
+ itemProps: getBottomSheetModalItemProps,
287
+ iconProps: getBottomSheetModalIconProps,
288
+ labelProps: getBottomSheetModalLabelProps,
289
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
290
+ iconPlacement: "right",
318
291
  },
319
292
  options: {
320
293
  supportedOrientations: ["portrait"],
321
294
  },
322
295
  });
323
296
  }, [
324
- navigator,
325
297
  sleepTimerState.timer,
326
298
  configuration["playback_sleep_title"],
327
299
  configuration["playback_sleep_off_label"],
@@ -355,13 +327,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
355
327
  player?.seekTo(0);
356
328
  }, [player]);
357
329
 
358
- const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
359
-
360
330
  const ContainerView = SafeAreaView;
361
331
 
362
332
  const MainContent = React.useMemo(() => {
363
333
  return (
364
- <ContainerView style={Styles.mainContainer(style)} edges={edges}>
334
+ <ContainerView style={[style, Styles.playerContainer]} edges={edges}>
365
335
  <AnimationComponent
366
336
  animationType={ComponentAnimationType.moveUpComponent}
367
337
  additionalData={{ saveArea: true }}
@@ -405,11 +375,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
405
375
  animationType={ComponentAnimationType.componentFade}
406
376
  >
407
377
  <PlayerDetails
378
+ isAudioPlayer
408
379
  configuration={configuration}
409
380
  style={Styles.playerDetails}
410
381
  entry={entry}
411
382
  isTabletLandscape={isTabletLandscape}
412
- isAudioPlayer={true}
413
383
  />
414
384
  </AnimationComponent>
415
385
  </View>
@@ -442,21 +412,26 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
442
412
  ]);
443
413
 
444
414
  if (configuration.magic_background) {
445
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
415
+ const baseUrl = imageSrcFromMediaItem(entry, null);
446
416
 
447
417
  const queryParamName =
448
418
  configuration.audio_player_background_image_query || "width";
449
419
 
420
+ const defaultBackgroundColor =
421
+ configuration.audio_player_background_image_default_color ||
422
+ theme?.app_background_color;
423
+
450
424
  const queryParamValue = 15;
451
425
 
452
- const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
426
+ const url =
427
+ baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
453
428
 
454
429
  return (
455
430
  <MagicBG
456
431
  image={url}
457
432
  fallbackUrl={baseUrl}
458
433
  colors={entry?.extensions?.backgroundColors}
459
- defaultColor="#000000"
434
+ defaultColor={defaultBackgroundColor}
460
435
  >
461
436
  {MainContent}
462
437
  </MagicBG>
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
  import * as R from "ramda";
4
- import { View } from "react-native";
4
+ import { View, Dimensions as ScreenDimensions } from "react-native";
5
5
  import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
6
6
 
7
7
  import {
@@ -33,8 +33,13 @@ import {
33
33
  ComponentAnimationType,
34
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 && (
@@ -408,6 +450,7 @@ export default function DefaultLayout(props: LayoutProps) {
408
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}
@@ -19,6 +19,7 @@ import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Compo
19
19
  import {
20
20
  AnimationComponent,
21
21
  ComponentAnimationType,
22
+ defaultAspectRatioWidth,
22
23
  PlayerAnimationStateEnum,
23
24
  useModalAnimationContext,
24
25
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
@@ -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
 
@@ -247,6 +235,7 @@ export default function DockedLayout(props: LayoutProps) {
247
235
  {!playerState.isLive &&
248
236
  (layoutState.docked || startComponentsAnimation) ? (
249
237
  <ProgressBar
238
+ name={"audio-docked-layout"}
250
239
  content={entry}
251
240
  value={value}
252
241
  visible={visible}
@@ -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
 
@@ -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> = ({
@@ -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
+ };
@@ -0,0 +1,10 @@
1
+ export const sleepTimerOptions = [
2
+ // { label: "End of episode", value: "episode" },
3
+ { label: "5 minutes", value: 5 },
4
+ { label: "10 minutes", value: 10 },
5
+ { label: "15 minutes", value: 15 },
6
+ { label: "30 minutes", value: 30 },
7
+ { label: "45 minutes", value: 45 },
8
+ { label: "1 hour", value: 60 },
9
+ { label: "2 hours", value: 120 },
10
+ ];
@@ -0,0 +1,4 @@
1
+ export const speedOptions = [0.8, 1, 1.2, 1.5, 2].map((v) => ({
2
+ label: v + "x",
3
+ value: v,
4
+ }));
@@ -1,11 +1,6 @@
1
1
  import queryString from "query-string";
2
2
  import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
3
3
 
4
- export const getLiveUrl = (url: string) => {
5
- // Use the replace method with a regular expression to replace all instances of subClass with livePage
6
- return url.replace(new RegExp("subClass", "g"), "livePage");
7
- };
8
-
9
4
  export const addQueryParam = (
10
5
  baseUrl: string,
11
6
  paramName: string,
@@ -1 +1,118 @@
1
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
2
+
1
3
  export * from "./Utils";
4
+
5
+ export const getBottomSheetModalIconProps = (theme) => ({
6
+ height: theme["audio_modal_bottom_sheet_item_icon_height"],
7
+ width: theme["audio_modal_bottom_sheet_item_icon_width"],
8
+ marginBottom: theme["audio_modal_bottom_sheet_item_icon_margin_bottom"],
9
+ marginLeft: theme["audio_modal_bottom_sheet_item_icon_margin_left"],
10
+ marginRight: theme["audio_modal_bottom_sheet_item_icon_margin_right"],
11
+ marginTop: theme["audio_modal_bottom_sheet_item_icon_margin_top"],
12
+ flavour: theme["modal_bottom_sheet_item_icon_flavour"],
13
+ });
14
+
15
+ export const getBottomSheetModalLabelProps = (theme, width: number) => {
16
+ const {
17
+ audio_modal_bottom_sheet_item_label_android_letter_spacing,
18
+ audio_modal_bottom_sheet_item_label_ios_letter_spacing,
19
+ audio_modal_bottom_sheet_item_label_family_ios_font_selector,
20
+ audio_modal_bottom_sheet_item_label_family_android_font_selector,
21
+ audio_modal_bottom_sheet_item_label_focus_font_color,
22
+ audio_modal_bottom_sheet_item_label_font_color,
23
+ audio_modal_bottom_sheet_item_label_selected_font_color,
24
+ audio_modal_bottom_sheet_item_label_hover_selected_font_color,
25
+ audio_modal_bottom_sheet_item_label_font_size,
26
+ audio_modal_bottom_sheet_item_label_line_height,
27
+ audio_modal_bottom_sheet_item_label_margin_bottom,
28
+ audio_modal_bottom_sheet_item_label_margin_top,
29
+ audio_modal_bottom_sheet_item_label_margin_left,
30
+ audio_modal_bottom_sheet_item_label_margin_right,
31
+ audio_modal_bottom_sheet_item_label_transform,
32
+ audio_modal_bottom_sheet_item_icon_width,
33
+ audio_modal_bottom_sheet_item_icon_margin_left,
34
+ audio_modal_bottom_sheet_item_icon_margin_right,
35
+ audio_modal_bottom_sheet_item_margin_left,
36
+ audio_modal_bottom_sheet_item_margin_right,
37
+ audio_modal_bottom_sheet_item_padding_left,
38
+ audio_modal_bottom_sheet_item_padding_right,
39
+ } = theme;
40
+
41
+ const maxWidth =
42
+ width -
43
+ (audio_modal_bottom_sheet_item_icon_width +
44
+ audio_modal_bottom_sheet_item_icon_margin_left +
45
+ audio_modal_bottom_sheet_item_icon_margin_right +
46
+ audio_modal_bottom_sheet_item_margin_left +
47
+ audio_modal_bottom_sheet_item_margin_right +
48
+ audio_modal_bottom_sheet_item_padding_left +
49
+ audio_modal_bottom_sheet_item_padding_right);
50
+
51
+ return {
52
+ fontFamily: platformSelect({
53
+ ios: audio_modal_bottom_sheet_item_label_family_ios_font_selector,
54
+ android: audio_modal_bottom_sheet_item_label_family_android_font_selector,
55
+ }),
56
+ letterSpacing: platformSelect({
57
+ ios: audio_modal_bottom_sheet_item_label_ios_letter_spacing,
58
+ android: audio_modal_bottom_sheet_item_label_android_letter_spacing,
59
+ }),
60
+ fontSize: audio_modal_bottom_sheet_item_label_font_size,
61
+ color: audio_modal_bottom_sheet_item_label_font_color,
62
+ focusColor: audio_modal_bottom_sheet_item_label_focus_font_color,
63
+ hoverSelectedColor:
64
+ audio_modal_bottom_sheet_item_label_hover_selected_font_color,
65
+ selectedColor: audio_modal_bottom_sheet_item_label_selected_font_color,
66
+ lineHeight: audio_modal_bottom_sheet_item_label_line_height,
67
+ marginBottom: audio_modal_bottom_sheet_item_label_margin_bottom,
68
+ marginTop: audio_modal_bottom_sheet_item_label_margin_top,
69
+ marginLeft: audio_modal_bottom_sheet_item_label_margin_left,
70
+ marginRight: audio_modal_bottom_sheet_item_label_margin_right,
71
+ transform: audio_modal_bottom_sheet_item_label_transform,
72
+ maxWidth,
73
+ };
74
+ };
75
+
76
+ export const getBottomSheetModalItemProps = (theme, width: number) => {
77
+ const {
78
+ audio_modal_bottom_sheet_item_background_color,
79
+ audio_modal_bottom_sheet_item_focus_background_color,
80
+ audio_modal_bottom_sheet_item_selected_background_color,
81
+ audio_modal_bottom_sheet_item_focusected_background_color,
82
+ audio_modal_bottom_sheet_item_corner_radius,
83
+ audio_modal_bottom_sheet_item_margin_bottom,
84
+ audio_modal_bottom_sheet_item_margin_left,
85
+ audio_modal_bottom_sheet_item_margin_right,
86
+ audio_modal_bottom_sheet_item_margin_top,
87
+ audio_modal_bottom_sheet_item_padding_bottom,
88
+ audio_modal_bottom_sheet_item_padding_left,
89
+ audio_modal_bottom_sheet_item_padding_right,
90
+ audio_modal_bottom_sheet_item_padding_top,
91
+ } = theme;
92
+
93
+ return {
94
+ backgroundColor: audio_modal_bottom_sheet_item_background_color,
95
+ focusedBackgroundColor:
96
+ audio_modal_bottom_sheet_item_focus_background_color,
97
+ selectedBackgroundColor:
98
+ audio_modal_bottom_sheet_item_selected_background_color,
99
+ focusectedBackgroundColor:
100
+ audio_modal_bottom_sheet_item_focusected_background_color,
101
+ borderRadius: audio_modal_bottom_sheet_item_corner_radius,
102
+ marginBottom: audio_modal_bottom_sheet_item_margin_bottom,
103
+ marginTop: audio_modal_bottom_sheet_item_margin_top,
104
+ marginLeft: audio_modal_bottom_sheet_item_margin_left,
105
+ marginRight: audio_modal_bottom_sheet_item_margin_right,
106
+ paddingTop: audio_modal_bottom_sheet_item_padding_top,
107
+ paddingBottom: audio_modal_bottom_sheet_item_padding_bottom,
108
+ paddingRight: audio_modal_bottom_sheet_item_padding_right,
109
+ paddingLeft: audio_modal_bottom_sheet_item_padding_left,
110
+ maxWidth: width,
111
+ };
112
+ };
113
+
114
+ export const getBottomSheetModalSelectedItemIcon = (theme) =>
115
+ theme["audio_modal_bottom_sheet_item_selected_icon"] || null;
116
+
117
+ export const getBottomSheetModalDefaultItemIcon = (theme) =>
118
+ theme["audio_modal_bottom_sheet_item_default_icon"] || null;
@@ -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() &&
@@ -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,7 +997,8 @@ export default class VideoPlayer extends React.Component<
989
997
 
990
998
  const renderMobilePlayer = () => {
991
999
  return isFullScreenAudioPlayer && isAudioItem ? (
992
- <View style={containerStyle}>
1000
+ // Ensure the View remains in the native hierarchy for proper rendering and interaction
1001
+ <View collapsable={false} style={containerStyle}>
993
1002
  {!castAction?.state?.connected || supportNativeCast ? (
994
1003
  <this.props.PlayerComponent
995
1004
  ref={this._assignRoot}
@@ -1045,7 +1054,10 @@ export default class VideoPlayer extends React.Component<
1045
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