@applicaster/quick-brick-player 13.0.0-rc.12 → 13.0.0-rc.121
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 +27 -14
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioDescription.tsx +107 -68
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +205 -281
- package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +45 -0
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +320 -0
- package/src/Player/AudioLayer/Layout/{defaultLayoutsConts.ts → Controls/utils.ts} +7 -25
- package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
- package/src/Player/AudioLayer/Layout/{DockedLayout.tsx → DockedControls/index.tsx} +77 -81
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +230 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +70 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +20 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +108 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +36 -0
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +220 -0
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +223 -0
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +128 -0
- package/src/Player/AudioLayer/Layout/VerticalGutter/index.tsx +8 -0
- package/src/Player/AudioLayer/Layout/const.ts +26 -0
- package/src/Player/AudioLayer/Layout/debug.ts +6 -0
- package/src/Player/AudioLayer/Layout/hooks/index.ts +5 -0
- package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
- package/src/Player/AudioLayer/Layout/hooks/useTabletLayoutImageSize.ts +105 -0
- package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +54 -0
- package/src/Player/AudioLayer/Layout/index.tsx +21 -48
- package/src/Player/AudioLayer/Layout/types.ts +7 -0
- package/src/Player/AudioLayer/Layout/utils.ts +7 -0
- package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
- package/src/Player/AudioLayer/SpeedManager.ts +52 -0
- package/src/Player/AudioLayer/utils.ts +27 -0
- package/src/Player/Const/sleepTimerOptions.ts +39 -0
- package/src/Player/Const/speedOptions.ts +4 -0
- package/src/Player/Utils/Utils.ts +0 -5
- package/src/Player/Utils/index.tsx +117 -0
- package/src/Player/hooks/useIsCasting.tsx +57 -0
- package/src/Player/index.tsx +39 -22
- package/src/QuickBrickPlayer.tsx +3 -0
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
- package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.ts +0 -3
- package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.web.ts +0 -3
- package/src/Player/AudioLayer/LinearGradient/index.ts +0 -1
- package/src/Player/AudioLayer/MagicBG/index.tsx +0 -155
- package/src/Player/Helpers/VideoResizeMode.ts +0 -7
- package/src/utils/utils.ts +0 -25
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const PADDING_HORIZONTAL = 15;
|
|
2
|
+
|
|
3
|
+
export const TABLET_VERTICAL_SEPARATOR_HEIGHT = 32;
|
|
4
|
+
|
|
5
|
+
export const TABLET_VERTICAL_SEPARATOR_HEIGHT_S = 16;
|
|
6
|
+
|
|
7
|
+
export const TABLET_LANDSCAPE = {
|
|
8
|
+
MARGIN_VERTICAL: 80, // gap between safe-area edge and audio-player container
|
|
9
|
+
MARGIN_VERTICAL_S: 60, // gap between safe-area edge and audio-player container
|
|
10
|
+
MAX_HEIGHT: 750,
|
|
11
|
+
MIN_WIDTH: 320, // Minimum width for the audio player container
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const TABLET_PORTRAIT = {
|
|
15
|
+
MARGIN_HORIZONTAL: 180,
|
|
16
|
+
MAX_WIDTH: 550,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const MOBILE = {
|
|
20
|
+
MARGIN_HORIZONTAL: 30,
|
|
21
|
+
MARGIN_BOTTOM: 48,
|
|
22
|
+
MARGIN_TOP: 10,
|
|
23
|
+
MIN_HEIGHT: 458,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const NAVBAR_HEIGHT = 60;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
|
|
2
|
+
import { MOBILE } from "../const";
|
|
3
|
+
|
|
4
|
+
type Return = {
|
|
5
|
+
imageSize: Option<number>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const useMobileLayoutImageSize = (): Return => {
|
|
9
|
+
const { width } = useDimensions("screen");
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
imageSize: width - 2 * MOBILE.MARGIN_HORIZONTAL,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { LayoutChangeEvent } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
5
|
+
import { Subject } from "rxjs";
|
|
6
|
+
import { first, debounceTime, filter } from "rxjs/operators";
|
|
7
|
+
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
|
|
8
|
+
|
|
9
|
+
const TIMEOUT = 200; // ms
|
|
10
|
+
|
|
11
|
+
type Layout = {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const builder = () => {
|
|
19
|
+
const layoutChange$ = new Subject<Layout>();
|
|
20
|
+
|
|
21
|
+
const emitLayoutChange = (layout: Layout): void => {
|
|
22
|
+
layoutChange$.next(layout);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
emitLayoutChange,
|
|
27
|
+
layoutChange$,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Props = {
|
|
32
|
+
isPortraitMode: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type Return = {
|
|
36
|
+
imageSize: Option<number>;
|
|
37
|
+
offsetY: Option<number>;
|
|
38
|
+
onLayout: (event: LayoutChangeEvent) => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const useTabletLayoutImageSize = ({ isPortraitMode }: Props): Return => {
|
|
42
|
+
const [imageSize, setImageSize] = React.useState(undefined);
|
|
43
|
+
const [offsetY, setOffsetY] = React.useState(undefined);
|
|
44
|
+
|
|
45
|
+
const layoutRef = useRefWithInitialValue<ReturnType<typeof builder>>(builder);
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
const subscription = layoutRef.current.layoutChange$
|
|
49
|
+
// for landscape layout we need to wait the second layout change in order to get proper size of image
|
|
50
|
+
.pipe(debounceTime(TIMEOUT), first())
|
|
51
|
+
.subscribe({
|
|
52
|
+
next: (layout: Layout) => {
|
|
53
|
+
const size = isPortraitMode
|
|
54
|
+
? Math.min(layout.height, layout.width)
|
|
55
|
+
: layout.height;
|
|
56
|
+
|
|
57
|
+
setImageSize(size);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
subscription.unsubscribe();
|
|
63
|
+
};
|
|
64
|
+
}, [isPortraitMode]);
|
|
65
|
+
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
const subscription = layoutRef.current.layoutChange$
|
|
68
|
+
.pipe(
|
|
69
|
+
filter((layout: Layout) => layout.y > 0),
|
|
70
|
+
debounceTime(TIMEOUT),
|
|
71
|
+
first()
|
|
72
|
+
)
|
|
73
|
+
.subscribe({
|
|
74
|
+
next: (layout: Layout) => {
|
|
75
|
+
setOffsetY(layout.y);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
subscription.unsubscribe();
|
|
81
|
+
};
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const handleLayout = React.useCallback((event: LayoutChangeEvent) => {
|
|
85
|
+
const height = toNumber(event?.nativeEvent?.layout?.height);
|
|
86
|
+
const width = toNumber(event?.nativeEvent?.layout?.width);
|
|
87
|
+
const y = toNumber(event?.nativeEvent?.layout?.y);
|
|
88
|
+
const x = toNumber(event?.nativeEvent?.layout?.x);
|
|
89
|
+
|
|
90
|
+
const layout: Layout = {
|
|
91
|
+
x,
|
|
92
|
+
y,
|
|
93
|
+
width,
|
|
94
|
+
height,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
layoutRef.current.emitLayoutChange(layout);
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
imageSize,
|
|
102
|
+
offsetY,
|
|
103
|
+
onLayout: handleLayout,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { LayoutChangeEvent } from "react-native";
|
|
3
|
+
import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
4
|
+
import { Subject } from "rxjs";
|
|
5
|
+
import { first } from "rxjs/operators";
|
|
6
|
+
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
|
|
7
|
+
|
|
8
|
+
const builder = () => {
|
|
9
|
+
const layoutChange$ = new Subject<number>();
|
|
10
|
+
|
|
11
|
+
const emitLayoutChange = (height: number): void => {
|
|
12
|
+
layoutChange$.next(height);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
emitLayoutChange,
|
|
17
|
+
layoutChange$,
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const useTransportInfoHeight = () => {
|
|
22
|
+
const [height, setHeight] = React.useState(0);
|
|
23
|
+
|
|
24
|
+
const layoutRef = useRefWithInitialValue<ReturnType<typeof builder>>(builder);
|
|
25
|
+
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
const subscription = layoutRef.current.layoutChange$
|
|
28
|
+
// skip first layout and take the second with proper size
|
|
29
|
+
// then ignore any layout update
|
|
30
|
+
.pipe(first())
|
|
31
|
+
.subscribe({
|
|
32
|
+
next: (size: number) => {
|
|
33
|
+
setHeight(size);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return () => {
|
|
38
|
+
subscription.unsubscribe();
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const handleLayout = React.useCallback((event: LayoutChangeEvent) => {
|
|
43
|
+
const height = toNumber(event?.nativeEvent?.layout?.height);
|
|
44
|
+
|
|
45
|
+
if (height) {
|
|
46
|
+
layoutRef.current.emitLayoutChange(height);
|
|
47
|
+
}
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
height,
|
|
52
|
+
onLayout: handleLayout,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -1,52 +1,25 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import DefaultLayout from "./DefaultLayout";
|
|
3
|
-
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
1
|
+
import * as React from "react";
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
height: number;
|
|
8
|
-
};
|
|
3
|
+
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
4
|
+
import { useIsTabletLandscape } from "@applicaster/zapp-react-native-utils/reactHooks/device/useMemoizedIsTablet";
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
inline: boolean;
|
|
14
|
-
docked: boolean;
|
|
15
|
-
isModal: boolean;
|
|
16
|
-
};
|
|
17
|
-
playerState: {
|
|
18
|
-
currentTime: number;
|
|
19
|
-
duration: number;
|
|
20
|
-
seekableDuration: number;
|
|
21
|
-
isLive: boolean;
|
|
22
|
-
isPaused: boolean;
|
|
23
|
-
isBuffering: boolean;
|
|
24
|
-
};
|
|
25
|
-
value: GetValue;
|
|
26
|
-
sleepTimerDate: number;
|
|
27
|
-
playbackSpeedName: string;
|
|
28
|
-
playbackSleepName: string;
|
|
29
|
-
configuration: { [key: string]: any };
|
|
30
|
-
onPlaybackButtonPress: () => void;
|
|
31
|
-
onRewindButtonPress: () => void;
|
|
32
|
-
onForwardButtonPress: () => void;
|
|
33
|
-
onSpeedButtonPress: () => void;
|
|
34
|
-
onSleepButtonPress: () => void;
|
|
35
|
-
onCloseVideoModal: () => void;
|
|
36
|
-
cancelSleepTimer: () => void;
|
|
37
|
-
onPlayerSeek: (targetTime: number) => void;
|
|
38
|
-
onSkipStart: () => void;
|
|
39
|
-
onSkipLive: () => void;
|
|
40
|
-
maximiseVideoModal: () => void;
|
|
41
|
-
dimensions: Dimensions;
|
|
42
|
-
defaultDimensions: Dimensions;
|
|
43
|
-
playNextData: PlayNextData;
|
|
44
|
-
title: string | number;
|
|
45
|
-
summary: string | number;
|
|
46
|
-
};
|
|
6
|
+
import { TabletPortraitLayout } from "./TabletPortraitLayout";
|
|
7
|
+
import { TabletLandscapeLayout } from "./TabletLandscapeLayout";
|
|
8
|
+
import { MobileLayout } from "./MobileLayout";
|
|
47
9
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
10
|
+
import { Props } from "./types";
|
|
11
|
+
|
|
12
|
+
export default function Layout(props: Props) {
|
|
13
|
+
const isTablet = useIsTablet();
|
|
14
|
+
const isTabletLandscape = useIsTabletLandscape();
|
|
15
|
+
|
|
16
|
+
if (!isTablet) {
|
|
17
|
+
return <MobileLayout {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isTabletLandscape) {
|
|
21
|
+
return <TabletLandscapeLayout {...props} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return <TabletPortraitLayout {...props} />;
|
|
52
25
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether the screen height is matches with our threshold for small tablets.
|
|
3
|
+
* @param height screen height
|
|
4
|
+
* @returns boolean indicating if the height is less than 800 pixels
|
|
5
|
+
*/
|
|
6
|
+
export const isSmallTabletHeight = (height: number): boolean =>
|
|
7
|
+
!(height >= 800);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
import { hasDatePassed, NAMESPACE } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { log_debug, log_error } = createLogger({
|
|
8
|
+
subsystem: "Player",
|
|
9
|
+
category: "SleepTimerManager",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type SleepTimer = {
|
|
13
|
+
label: string;
|
|
14
|
+
// interval in minutes for UI at the moment when timer was first started
|
|
15
|
+
value: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SLEEP_TIMER_OFF: SleepTimer = {
|
|
19
|
+
label: "playback_sleep_off_label",
|
|
20
|
+
value: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class SleepTimerState {
|
|
24
|
+
constructor(
|
|
25
|
+
public timer: SleepTimer,
|
|
26
|
+
public timerDate: number
|
|
27
|
+
) {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SLEEP_PREFERENCE_KEY = "sleep";
|
|
31
|
+
|
|
32
|
+
export class SleepTimerController {
|
|
33
|
+
private sleepTimerStateSubject = new BehaviorSubject<SleepTimerState>(
|
|
34
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
constructor(private player: Player) {}
|
|
38
|
+
|
|
39
|
+
get sleepTimerState$() {
|
|
40
|
+
return this.sleepTimerStateSubject.asObservable();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async scheduleSleep(sleepDate): Promise<boolean> {
|
|
44
|
+
log_debug(`Setting sleep interval to ${sleepDate}`);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.player.cancelSleepTimer?.();
|
|
48
|
+
this.player.startSleepTimer?.(sleepDate);
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
log_error("Failed to start sleep timer", { error: e.message });
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async setSleepTimer(item: SleepTimer) {
|
|
59
|
+
// User has selected Off state
|
|
60
|
+
if (item.value === 0) {
|
|
61
|
+
log_debug("Cancelling sleep timer");
|
|
62
|
+
this.player.cancelSleepTimer?.();
|
|
63
|
+
|
|
64
|
+
return this.onTimerExpired();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log_debug(`Starting sleep timer in ${item.value} minutes`);
|
|
68
|
+
|
|
69
|
+
const futureSleepDate = Date.now() + item.value * 60000;
|
|
70
|
+
|
|
71
|
+
if (await this.scheduleSleep(futureSleepDate)) {
|
|
72
|
+
await localStorage.setItem(
|
|
73
|
+
SLEEP_PREFERENCE_KEY,
|
|
74
|
+
{ ...item, futureSleepDate },
|
|
75
|
+
NAMESPACE
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this.sleepTimerStateSubject.next(
|
|
79
|
+
new SleepTimerState(item, futureSleepDate)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async onTimerExpired() {
|
|
85
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
86
|
+
|
|
87
|
+
this.sleepTimerStateSubject.next(new SleepTimerState(SLEEP_TIMER_OFF, 0));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async loadSleep() {
|
|
91
|
+
const sleep = await localStorage.getItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
92
|
+
|
|
93
|
+
if (!sleep) return;
|
|
94
|
+
|
|
95
|
+
const { futureSleepDate, ...rest } = sleep;
|
|
96
|
+
|
|
97
|
+
if (!futureSleepDate || hasDatePassed(futureSleepDate)) {
|
|
98
|
+
log_debug("Sleep date has already passed");
|
|
99
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
100
|
+
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (await this.scheduleSleep(futureSleepDate)) {
|
|
105
|
+
log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
|
|
106
|
+
|
|
107
|
+
this.sleepTimerStateSubject.next(
|
|
108
|
+
new SleepTimerState(rest as SleepTimer, futureSleepDate)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Will be called if user closed the player explicitly.
|
|
114
|
+
// TODO: check if it will be called on Android when we need.
|
|
115
|
+
// Probably we will have to handle it on native instead.
|
|
116
|
+
public static async onPlayerDestroy() {
|
|
117
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
import { NAMESPACE } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { log_debug } = createLogger({
|
|
8
|
+
subsystem: "Player",
|
|
9
|
+
category: "PlaybackSpeedManager",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type PlaybackSpeed = {
|
|
13
|
+
asset: string;
|
|
14
|
+
value: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SPEED_PREFERENCE_KEY = "speed";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_SPEED: PlaybackSpeed = {
|
|
20
|
+
asset: "speed_1",
|
|
21
|
+
value: 1.0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// todo: should not be applied to live streams
|
|
25
|
+
export class SpeedController {
|
|
26
|
+
private playbackSpeedSubject = new BehaviorSubject<PlaybackSpeed>(
|
|
27
|
+
DEFAULT_SPEED
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
constructor(private player: Player) {}
|
|
31
|
+
|
|
32
|
+
get playbackSpeed$() {
|
|
33
|
+
return this.playbackSpeedSubject.asObservable();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async loadSpeed() {
|
|
37
|
+
const speed = await localStorage.getItem(SPEED_PREFERENCE_KEY, NAMESPACE);
|
|
38
|
+
|
|
39
|
+
// checking for value in case we will pull empty or broken object
|
|
40
|
+
if (speed && speed.value) {
|
|
41
|
+
this.playbackSpeedSubject.next(speed);
|
|
42
|
+
this.player.setPlaybackRate?.(speed.value);
|
|
43
|
+
log_debug(`Speed loaded: ${speed}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async setPlaybackSpeed(speed: PlaybackSpeed) {
|
|
48
|
+
await localStorage.setItem(SPEED_PREFERENCE_KEY, speed, NAMESPACE);
|
|
49
|
+
this.playbackSpeedSubject.next(speed);
|
|
50
|
+
this.player.setPlaybackRate?.(speed.value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
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";
|
|
7
|
+
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
3
8
|
|
|
4
9
|
export const NAMESPACE = "audio-player";
|
|
5
10
|
|
|
@@ -31,3 +36,25 @@ export const formatSpeed = (value: number) => {
|
|
|
31
36
|
// Return the formatted string with "speed_" prefix
|
|
32
37
|
return `speed_${formattedValue}`;
|
|
33
38
|
};
|
|
39
|
+
|
|
40
|
+
export const calculateBorderRadius = (
|
|
41
|
+
docked: boolean,
|
|
42
|
+
startComponentsAnimation: boolean,
|
|
43
|
+
isActiveGesture: boolean,
|
|
44
|
+
playerAnimationState: PlayerAnimationStateT,
|
|
45
|
+
configuration: Record<string, any>
|
|
46
|
+
): number => {
|
|
47
|
+
const shouldRemoveBorderRadius =
|
|
48
|
+
(startComponentsAnimation &&
|
|
49
|
+
isActiveGesture &&
|
|
50
|
+
playerAnimationState !== PlayerAnimationStateEnum.maximize) ||
|
|
51
|
+
docked;
|
|
52
|
+
|
|
53
|
+
if (shouldRemoveBorderRadius) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return toNumberWithDefaultZero(
|
|
58
|
+
configuration.audio_player_artwork_border_radius
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { toDayJSLocaleMap } from "@applicaster/zapp-react-native-utils/appUtils/localizationsHelper";
|
|
2
|
+
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
3
|
+
import { getUILanguage } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
4
|
+
|
|
5
|
+
const createTimerOptions = (value) => {
|
|
6
|
+
let label = "";
|
|
7
|
+
|
|
8
|
+
const str = dayjs
|
|
9
|
+
.duration(value, "minute")
|
|
10
|
+
.locale(toDayJSLocaleMap(getUILanguage()))
|
|
11
|
+
.humanize();
|
|
12
|
+
|
|
13
|
+
if (value === 60) {
|
|
14
|
+
/** Skipping prefix "an"/"un"/"una" for 1 hour */
|
|
15
|
+
|
|
16
|
+
if (str.includes(" ")) {
|
|
17
|
+
label = `1 ${str.split(" ")[1]}`; // Combine "1" with second part of the string
|
|
18
|
+
} else {
|
|
19
|
+
label = `1 ${str}`; // Use dayjs provided translation
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
label = str;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
label,
|
|
27
|
+
value,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getSleepTimerOptions = (configuration) => {
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
label: configuration["playback_sleep_off_label"],
|
|
35
|
+
value: 0,
|
|
36
|
+
},
|
|
37
|
+
...[5, 10, 15, 30, 45, 60, 120].map(createTimerOptions),
|
|
38
|
+
];
|
|
39
|
+
};
|
|
@@ -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,
|