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