@applicaster/quick-brick-player 13.0.0-rc.123 → 13.0.0-rc.124
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 +5 -5
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +4 -81
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +15 -49
- package/src/Player/AudioLayer/Layout/Controls/utils.ts +2 -2
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +0 -8
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +0 -8
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +0 -8
- package/src/Player/AudioLayer/SleepTimerManager.ts +22 -11
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +66 -0
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/index.ts +1 -0
- package/src/Player/AudioLayer/useSleepTimerModal.ts +109 -0
- package/src/Player/Const/sleepTimerOptions.ts +7 -5
- package/src/Player/Utils/index.tsx +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-player",
|
|
3
|
-
"version": "13.0.0-rc.
|
|
3
|
+
"version": "13.0.0-rc.124",
|
|
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-rc.
|
|
38
|
+
"@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.124",
|
|
39
39
|
"@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
|
|
40
|
-
"@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.
|
|
41
|
-
"@applicaster/zapp-react-native-ui-components": "13.0.0-rc.
|
|
42
|
-
"@applicaster/zapp-react-native-utils": "13.0.0-rc.
|
|
40
|
+
"@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.124",
|
|
41
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-rc.124",
|
|
42
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-rc.124",
|
|
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
|
-
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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 {
|
|
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,21 @@ 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 playbackSleepName =
|
|
118
|
+
sleepTimerState.timer.value === 0 ? "sleep_timer" : "sleep_timer_active";
|
|
119
|
+
|
|
114
120
|
const isTablet = useIsTablet();
|
|
115
121
|
const controller = playerManager.getActivePlayer();
|
|
116
122
|
let showFullControls = !controller?.isAd();
|
|
@@ -124,45 +130,6 @@ export const Controls = (props: Props) => {
|
|
|
124
130
|
showFullControls = !!(controller?.hasSeekableDuration?.() && seekEnabled);
|
|
125
131
|
}
|
|
126
132
|
|
|
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
133
|
const startTime = entry.extensions?.live?.start_time;
|
|
167
134
|
const endTime = entry.extensions?.live?.end_time;
|
|
168
135
|
|
|
@@ -295,7 +262,6 @@ export const Controls = (props: Props) => {
|
|
|
295
262
|
isDateBasedLiveContent={isDateBasedLiveContent}
|
|
296
263
|
onSkipLive={onSkipLive}
|
|
297
264
|
progressBarStyles={styles.progressBar}
|
|
298
|
-
countDownOptions={countDownOptions}
|
|
299
265
|
thumbStyles={styles.progressBarThumbStyles}
|
|
300
266
|
thumbStylesPressed={styles.progressBarThumbStyles_pressed}
|
|
301
267
|
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(
|
|
102
|
+
speedButtonByName(props);
|
|
103
103
|
|
|
104
104
|
export const sleepButton = (props: ButtonsProps): ControlsButton =>
|
|
105
|
-
sleepButtonByName(
|
|
105
|
+
sleepButtonByName(props);
|
|
@@ -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>
|
|
@@ -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
|
-
|
|
70
|
+
const futureSleepDate =
|
|
71
|
+
prevTimerSleepDate || Date.now() + item.value * 60000;
|
|
68
72
|
|
|
69
|
-
|
|
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
|
-
|
|
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, ...
|
|
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
|
-
|
|
105
|
-
log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
|
|
119
|
+
log_debug("Sleep state restored");
|
|
106
120
|
|
|
107
|
-
|
|
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, sleepTimerState.timer.value),
|
|
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 = (
|
|
31
|
+
export const getSleepTimerOptions = (offLabel, sleepTimerValue) => {
|
|
32
32
|
return [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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;
|