@applicaster/quick-brick-player 13.0.0-alpha.6076935807 → 13.0.0-alpha.6179233127
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 +8 -11
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +86 -125
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +85 -42
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +17 -16
- package/src/Player/AudioLayer/Layout/defaultLayoutsConts.ts +1 -1
- package/src/Player/AudioLayer/MagicBG/index.tsx +1 -1
- package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
- package/src/Player/AudioLayer/SpeedManager.ts +52 -0
- package/src/Player/AudioLayer/utils.ts +24 -0
- package/src/Player/index.tsx +17 -11
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.6179233127",
|
|
4
4
|
"description": "Quick Brick Player",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.
|
|
40
|
-
"@applicaster/quick-brick-tv-transport-controls": "13.0.0-
|
|
41
|
-
"@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.
|
|
42
|
-
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.
|
|
43
|
-
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.
|
|
39
|
+
"@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.66",
|
|
40
|
+
"@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
|
|
41
|
+
"@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.6179233127",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.6179233127",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.6179233127",
|
|
44
44
|
"keymirror": "^0.1.1",
|
|
45
45
|
"query-string": "7.1.3",
|
|
46
46
|
"react-native-web-linear-gradient": "1.1.2",
|
|
@@ -61,12 +61,9 @@
|
|
|
61
61
|
"@applicaster/zapp-react-native-tvos-ui-components": "*",
|
|
62
62
|
"@babel/runtime": "*",
|
|
63
63
|
"@react-native-community/netinfo": "*",
|
|
64
|
-
"@types/node": "*",
|
|
65
|
-
"@types/react-native": "0.69.6",
|
|
66
64
|
"immer": "*",
|
|
67
|
-
"react": "
|
|
68
|
-
"react-
|
|
69
|
-
"react-native": "0.68.6",
|
|
65
|
+
"react": "*",
|
|
66
|
+
"react-native": "*",
|
|
70
67
|
"react-native-fs": "*",
|
|
71
68
|
"react-native-gesture-handler": "*",
|
|
72
69
|
"react-native-linear-gradient": "*",
|
|
@@ -55,6 +55,11 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// initial state setup - show correct mute/unmute icon based on current state
|
|
59
|
+
if (player?.playerState?.isReadyToPlay) {
|
|
60
|
+
setImageUri(player.getIsMuted() ? asset.active : asset.inactive);
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
// active action = muted, inactive = unmuted
|
|
59
64
|
return player.addListener({
|
|
60
65
|
id: liveImageButtonsListenerKey,
|
|
@@ -73,10 +78,17 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
|
|
|
73
78
|
setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
|
|
74
79
|
},
|
|
75
80
|
onPlayerClose: () => {
|
|
76
|
-
|
|
81
|
+
if (isCurrentPlayer(playerId)) {
|
|
82
|
+
setImageUri(null);
|
|
83
|
+
}
|
|
77
84
|
},
|
|
78
85
|
onVideoEnd: () => {
|
|
79
|
-
|
|
86
|
+
if (isCurrentPlayer(playerId)) {
|
|
87
|
+
setImageUri(null);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
onPlayerResume: () => {
|
|
91
|
+
setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
|
|
80
92
|
},
|
|
81
93
|
},
|
|
82
94
|
});
|
|
@@ -93,7 +105,7 @@ const styles = StyleSheet.create({
|
|
|
93
105
|
},
|
|
94
106
|
});
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
function AssetImage(props: CellActionAssetComponentProps) {
|
|
97
109
|
const { cellUUID: playerId, asset } = props;
|
|
98
110
|
const [imageUri, setImageUri] = useState<Asset>(null);
|
|
99
111
|
|
|
@@ -108,3 +120,5 @@ export function AssetComponent(props: CellActionAssetComponentProps) {
|
|
|
108
120
|
|
|
109
121
|
return <View />;
|
|
110
122
|
}
|
|
123
|
+
|
|
124
|
+
export const AssetComponent = React.memo(AssetImage);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { ViewStyle, View } from "react-native";
|
|
2
|
+
import { ViewStyle, View, Dimensions as ScreenDimensions } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { SafeAreaView, Edge } from "react-native-safe-area-context";
|
|
5
5
|
import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
|
|
@@ -15,15 +15,9 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
15
15
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
16
16
|
import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/PlayerDetails";
|
|
17
17
|
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
18
|
-
import {
|
|
18
|
+
import { useEffect, useState } from "react";
|
|
19
19
|
import { MagicBG } from "./MagicBG";
|
|
20
|
-
import {
|
|
21
|
-
formatSpeed,
|
|
22
|
-
hasDatePassed,
|
|
23
|
-
loadItemsFromStorage,
|
|
24
|
-
NAMESPACE,
|
|
25
|
-
} from "./utils";
|
|
26
|
-
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
20
|
+
import { formatSpeed } from "./utils";
|
|
27
21
|
import { addQueryParam, getLiveUrl } from "../Utils";
|
|
28
22
|
import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
|
|
29
23
|
import {
|
|
@@ -40,6 +34,14 @@ import {
|
|
|
40
34
|
speed_1_5,
|
|
41
35
|
speed_2,
|
|
42
36
|
} from "@applicaster/quick-brick-mobile-transport-controls";
|
|
37
|
+
import {
|
|
38
|
+
SleepTimerController,
|
|
39
|
+
SleepTimerState,
|
|
40
|
+
SLEEP_TIMER_OFF,
|
|
41
|
+
} from "./SleepTimerManager";
|
|
42
|
+
import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
|
|
43
|
+
import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
|
|
44
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
43
45
|
|
|
44
46
|
const { log_debug } = createLogger({
|
|
45
47
|
subsystem: "UIComponent",
|
|
@@ -57,6 +59,7 @@ type Configuration = {
|
|
|
57
59
|
[key: string]: any;
|
|
58
60
|
magic_background?: boolean;
|
|
59
61
|
audio_player_background_image_query?: string;
|
|
62
|
+
audio_player_background_image_default_color?: string;
|
|
60
63
|
tablet_landscape_sidebar_width?: string;
|
|
61
64
|
tablet_landscape_player_container_background_color?: string;
|
|
62
65
|
};
|
|
@@ -78,8 +81,7 @@ type Props = {
|
|
|
78
81
|
isTabletPortrait?: boolean;
|
|
79
82
|
};
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
(height / 9) * 16;
|
|
84
|
+
const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
|
|
83
85
|
|
|
84
86
|
const Styles = {
|
|
85
87
|
mainContainer: (style: ViewStyle) => {
|
|
@@ -105,7 +107,11 @@ const Styles = {
|
|
|
105
107
|
height: 96,
|
|
106
108
|
marginHorizontal: 24,
|
|
107
109
|
},
|
|
108
|
-
playerDetails: {
|
|
110
|
+
playerDetails: {
|
|
111
|
+
marginTop: 60,
|
|
112
|
+
alignSelf: "center",
|
|
113
|
+
width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
|
|
114
|
+
},
|
|
109
115
|
|
|
110
116
|
linearGradient: {
|
|
111
117
|
flex: 1,
|
|
@@ -123,9 +129,12 @@ const sleeps = [
|
|
|
123
129
|
{ label: "2 hours", value: 120 },
|
|
124
130
|
];
|
|
125
131
|
|
|
132
|
+
const edges: Edge[] = ["right", "left", "top"];
|
|
133
|
+
|
|
126
134
|
export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
127
135
|
const player = usePlayer();
|
|
128
136
|
const playerState = usePlayerState("audio-player-wrapper");
|
|
137
|
+
const theme = useTheme();
|
|
129
138
|
|
|
130
139
|
const {
|
|
131
140
|
docked,
|
|
@@ -161,17 +170,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
161
170
|
configuration?.audio_live_player_update_interval
|
|
162
171
|
);
|
|
163
172
|
|
|
164
|
-
const [
|
|
165
|
-
|
|
166
|
-
value: 1.0,
|
|
167
|
-
});
|
|
173
|
+
const [sleepTimerManager, setSleepTimerManager] =
|
|
174
|
+
useState<SleepTimerController | null>(null);
|
|
168
175
|
|
|
169
|
-
const [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
const [speedManager, setSpeedManager] = useState<SpeedController | null>(
|
|
177
|
+
null
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (player) {
|
|
182
|
+
setSleepTimerManager(new SleepTimerController(player));
|
|
183
|
+
setSpeedManager(new SpeedController(player));
|
|
184
|
+
} else {
|
|
185
|
+
setSleepTimerManager(null);
|
|
186
|
+
setSpeedManager(null);
|
|
187
|
+
}
|
|
188
|
+
}, [player]);
|
|
189
|
+
|
|
190
|
+
const [playbackSpeed, setPlaybackSpeed] =
|
|
191
|
+
useState<PlaybackSpeed>(DEFAULT_SPEED);
|
|
173
192
|
|
|
174
|
-
const [
|
|
193
|
+
const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
|
|
194
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
195
|
+
);
|
|
175
196
|
|
|
176
197
|
const [defaultDimensions, setDefaultDimensions] =
|
|
177
198
|
React.useState<Dimensions>(undefined);
|
|
@@ -196,90 +217,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
196
217
|
if (!defaultDimensions) setDefaultDimensions({ width, height });
|
|
197
218
|
};
|
|
198
219
|
|
|
199
|
-
const cancelSleepTimer = useCallback(async () => {
|
|
200
|
-
player?.cancelSleepTimer();
|
|
201
|
-
|
|
202
|
-
setSleepTimer({
|
|
203
|
-
label: "playback_sleep_off_label",
|
|
204
|
-
value: 0,
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
setSleepTimerDate(0);
|
|
208
|
-
|
|
209
|
-
await localStorage.removeItem("sleep", NAMESPACE);
|
|
210
|
-
}, [player, setSleepTimer, setSleepTimerDate]);
|
|
211
|
-
|
|
212
|
-
const startSleepTimer = useCallback(
|
|
213
|
-
(timerValue: number) => {
|
|
214
|
-
try {
|
|
215
|
-
const futureSleepDate = Date.now() + timerValue * 60000;
|
|
216
|
-
|
|
217
|
-
player?.cancelSleepTimer?.();
|
|
218
|
-
|
|
219
|
-
player?.startSleepTimer?.(futureSleepDate);
|
|
220
|
-
|
|
221
|
-
return futureSleepDate;
|
|
222
|
-
} catch (e) {
|
|
223
|
-
return 0;
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
[player]
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
const handleSleep = useCallback(
|
|
230
|
-
async (timerValue: number, currentFutureSleepDate = null) => {
|
|
231
|
-
if (currentFutureSleepDate) {
|
|
232
|
-
if (hasDatePassed(currentFutureSleepDate)) {
|
|
233
|
-
await cancelSleepTimer();
|
|
234
|
-
|
|
235
|
-
return 0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return currentFutureSleepDate;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (timerValue === 0) {
|
|
242
|
-
await cancelSleepTimer();
|
|
243
|
-
|
|
244
|
-
return timerValue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return startSleepTimer(timerValue);
|
|
248
|
-
},
|
|
249
|
-
[startSleepTimer, cancelSleepTimer]
|
|
250
|
-
);
|
|
251
|
-
|
|
252
220
|
useEffect(() => {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (playerState.isReadyToPlay && speed) {
|
|
257
|
-
setPlaybackSpeed(speed);
|
|
258
|
-
player?.setPlaybackRate(speed.value);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
221
|
+
const sleepSubscription =
|
|
222
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
261
223
|
|
|
262
|
-
const
|
|
263
|
-
|
|
224
|
+
const speedSubscription =
|
|
225
|
+
speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
|
|
264
226
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
|
|
269
|
-
setSleepTimerDate(sleepTimerDate);
|
|
270
|
-
}
|
|
227
|
+
return () => {
|
|
228
|
+
sleepSubscription?.unsubscribe();
|
|
229
|
+
speedSubscription?.unsubscribe();
|
|
271
230
|
};
|
|
231
|
+
}, [sleepTimerManager, speedManager]);
|
|
272
232
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
player,
|
|
282
|
-
]);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!playerState.isReadyToPlay) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void speedManager?.loadSpeed();
|
|
239
|
+
void sleepTimerManager?.loadSleep();
|
|
240
|
+
}, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
|
|
283
241
|
|
|
284
242
|
const getValue = (key: string, stylesObj: {}) =>
|
|
285
243
|
R.propOr(null, key, stylesObj);
|
|
@@ -322,26 +280,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
322
280
|
|
|
323
281
|
const onSpeedSelect = React.useCallback(
|
|
324
282
|
async (item) => {
|
|
325
|
-
setPlaybackSpeed(item);
|
|
326
|
-
player?.setPlaybackRate(item.value);
|
|
327
|
-
await localStorage.setItem("speed", item, NAMESPACE);
|
|
283
|
+
await speedManager?.setPlaybackSpeed(item);
|
|
328
284
|
},
|
|
329
|
-
[
|
|
285
|
+
[speedManager]
|
|
330
286
|
);
|
|
331
287
|
|
|
332
288
|
const onSleepSelect = React.useCallback(
|
|
333
289
|
async (item) => {
|
|
334
|
-
setSleepTimer(item);
|
|
335
|
-
const futureSleepDate = await handleSleep(item.value);
|
|
336
|
-
setSleepTimerDate(futureSleepDate);
|
|
337
|
-
|
|
338
|
-
await localStorage.setItem(
|
|
339
|
-
"sleep",
|
|
340
|
-
{ ...item, futureSleepDate },
|
|
341
|
-
NAMESPACE
|
|
342
|
-
);
|
|
290
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
343
291
|
},
|
|
344
|
-
[
|
|
292
|
+
[sleepTimerManager]
|
|
345
293
|
);
|
|
346
294
|
|
|
347
295
|
const onSpeedButtonPress = React.useCallback(() => {
|
|
@@ -373,7 +321,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
373
321
|
value: 0,
|
|
374
322
|
},
|
|
375
323
|
].concat(sleeps),
|
|
376
|
-
current_selection:
|
|
324
|
+
current_selection: sleepTimerState.timer,
|
|
377
325
|
onPress: onSleepSelect,
|
|
378
326
|
title: configuration["playback_sleep_title"],
|
|
379
327
|
},
|
|
@@ -383,12 +331,20 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
383
331
|
});
|
|
384
332
|
}, [
|
|
385
333
|
navigator,
|
|
386
|
-
|
|
334
|
+
sleepTimerState.timer,
|
|
387
335
|
configuration["playback_sleep_title"],
|
|
388
336
|
configuration["playback_sleep_off_label"],
|
|
389
337
|
onSleepSelect,
|
|
390
338
|
]);
|
|
391
339
|
|
|
340
|
+
// This method does not actually cancel the sleep timer
|
|
341
|
+
// (and should not to avoid race condition),
|
|
342
|
+
// it just performs the cleanup when timer expires
|
|
343
|
+
// TODO: rename it in code above
|
|
344
|
+
const cancelSleepTimer = React.useCallback(() => {
|
|
345
|
+
sleepTimerManager?.onTimerExpired();
|
|
346
|
+
}, [sleepTimerManager]);
|
|
347
|
+
|
|
392
348
|
const onCloseVideoModal = React.useCallback(() => {
|
|
393
349
|
playerManager.close();
|
|
394
350
|
}, [playerManager]);
|
|
@@ -408,8 +364,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
408
364
|
player?.seekTo(0);
|
|
409
365
|
}, [player]);
|
|
410
366
|
|
|
411
|
-
const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
|
|
412
|
-
|
|
413
367
|
const ContainerView = SafeAreaView;
|
|
414
368
|
|
|
415
369
|
const MainContent = React.useMemo(() => {
|
|
@@ -436,9 +390,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
436
390
|
onSleepButtonPress={onSleepButtonPress}
|
|
437
391
|
playbackSpeedName={formatSpeed(playbackSpeed.value)}
|
|
438
392
|
playbackSleepName={
|
|
439
|
-
|
|
393
|
+
sleepTimerState.timer.value === 0
|
|
394
|
+
? "sleep_timer"
|
|
395
|
+
: "sleep_timer_active"
|
|
440
396
|
}
|
|
441
|
-
sleepTimerDate={
|
|
397
|
+
sleepTimerDate={sleepTimerState.timerDate}
|
|
442
398
|
cancelSleepTimer={cancelSleepTimer}
|
|
443
399
|
onSkipLive={onSkipLive}
|
|
444
400
|
onCloseVideoModal={onCloseVideoModal}
|
|
@@ -488,26 +444,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
488
444
|
playNextData,
|
|
489
445
|
onSkipStart,
|
|
490
446
|
onSkipLive,
|
|
491
|
-
|
|
492
|
-
|
|
447
|
+
sleepTimerState.timer.value,
|
|
448
|
+
sleepTimerState.timerDate,
|
|
493
449
|
]);
|
|
494
450
|
|
|
495
451
|
if (configuration.magic_background) {
|
|
496
|
-
const baseUrl = entry
|
|
452
|
+
const baseUrl = imageSrcFromMediaItem(entry, null);
|
|
497
453
|
|
|
498
454
|
const queryParamName =
|
|
499
455
|
configuration.audio_player_background_image_query || "width";
|
|
500
456
|
|
|
457
|
+
const defaultBackgroundColor =
|
|
458
|
+
configuration.audio_player_background_image_default_color ||
|
|
459
|
+
theme?.app_background_color;
|
|
460
|
+
|
|
501
461
|
const queryParamValue = 15;
|
|
502
462
|
|
|
503
|
-
const url =
|
|
463
|
+
const url =
|
|
464
|
+
baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
|
|
504
465
|
|
|
505
466
|
return (
|
|
506
467
|
<MagicBG
|
|
507
468
|
image={url}
|
|
508
469
|
fallbackUrl={baseUrl}
|
|
509
470
|
colors={entry?.extensions?.backgroundColors}
|
|
510
|
-
defaultColor=
|
|
471
|
+
defaultColor={defaultBackgroundColor}
|
|
511
472
|
>
|
|
512
473
|
{MainContent}
|
|
513
474
|
</MagicBG>
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
3
|
import * as R from "ramda";
|
|
3
|
-
import { View } from "react-native";
|
|
4
|
+
import { View, Dimensions as ScreenDimensions } from "react-native";
|
|
4
5
|
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
|
-
ProgressBar,
|
|
8
8
|
Controls,
|
|
9
|
-
TransportInfo,
|
|
10
9
|
CountDown,
|
|
10
|
+
ProgressBar,
|
|
11
|
+
TransportInfo,
|
|
11
12
|
} from "@applicaster/quick-brick-mobile-transport-controls";
|
|
12
13
|
|
|
13
14
|
import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
@@ -15,26 +16,30 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
|
15
16
|
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
16
17
|
import {
|
|
17
18
|
forwardButton,
|
|
19
|
+
PADDING_HORIZONTAL,
|
|
18
20
|
playbackButton,
|
|
19
21
|
rewindButton,
|
|
20
|
-
skipToStartButton,
|
|
21
22
|
skipToLiveButton,
|
|
22
|
-
|
|
23
|
-
speedButton,
|
|
23
|
+
skipToStartButton,
|
|
24
24
|
sleepButton,
|
|
25
|
+
speedButton,
|
|
25
26
|
} from "./defaultLayoutsConts";
|
|
26
27
|
import { AudioDescription } from "../AudioDescription";
|
|
27
28
|
import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
|
|
28
29
|
import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
|
|
29
30
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
30
|
-
import { useEffect, useMemo, useState } from "react";
|
|
31
31
|
import {
|
|
32
|
-
useModalAnimationContext,
|
|
33
32
|
AnimationComponent,
|
|
34
33
|
ComponentAnimationType,
|
|
34
|
+
useModalAnimationContext,
|
|
35
35
|
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
36
|
+
import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
|
|
36
37
|
import DockedLayout from "./DockedLayout";
|
|
37
38
|
import { isValidDate } from "../../Utils";
|
|
39
|
+
import { calculateBorderRadius } from "../utils";
|
|
40
|
+
|
|
41
|
+
const getAudioPlayerWidthForTablet = (isPortraitMode: boolean) =>
|
|
42
|
+
isPortraitMode ? 463 : 350;
|
|
38
43
|
|
|
39
44
|
type Dimensions = {
|
|
40
45
|
width: number;
|
|
@@ -79,6 +84,9 @@ type LayoutProps = {
|
|
|
79
84
|
defaultDimensions: Dimensions;
|
|
80
85
|
};
|
|
81
86
|
|
|
87
|
+
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } =
|
|
88
|
+
ScreenDimensions.get("screen");
|
|
89
|
+
|
|
82
90
|
const Styles = {
|
|
83
91
|
container: { flex: 1 },
|
|
84
92
|
tabletContainer: (isPortraitMode: boolean) => ({
|
|
@@ -86,42 +94,63 @@ const Styles = {
|
|
|
86
94
|
alignItems: "center",
|
|
87
95
|
justifyContent: isPortraitMode ? "center" : "flex-start",
|
|
88
96
|
}),
|
|
89
|
-
playerImageWrapper: {
|
|
97
|
+
playerImageWrapper: (docked: boolean) => ({
|
|
90
98
|
width: "100%",
|
|
91
99
|
maxHeight: 750,
|
|
92
|
-
marginTop: 80,
|
|
100
|
+
marginTop: docked ? 80 : 20,
|
|
101
|
+
}),
|
|
102
|
+
playerInfoWrapper: {
|
|
103
|
+
flex: 1,
|
|
104
|
+
alignItems: "center",
|
|
105
|
+
justifyContent: "space-between",
|
|
93
106
|
},
|
|
94
|
-
tabletLayout: {
|
|
95
|
-
width:
|
|
107
|
+
tabletLayout: (isPortraitMode: boolean) => ({
|
|
108
|
+
width: getAudioPlayerWidthForTablet(isPortraitMode),
|
|
96
109
|
marginBottom: 80,
|
|
97
|
-
|
|
110
|
+
paddingHorizontal: undefined, // override paddingHorizontal
|
|
111
|
+
}),
|
|
98
112
|
controls: (isTablet: boolean, isLiveShowFullControls: boolean) => ({
|
|
99
113
|
height: isTablet ? 64 : 50,
|
|
100
|
-
marginHorizontal: 10,
|
|
114
|
+
marginHorizontal: isTablet ? undefined : 10,
|
|
101
115
|
justifyContent: isLiveShowFullControls ? "space-between" : "space-evenly",
|
|
102
116
|
}),
|
|
103
|
-
backgroundImageContainer: (
|
|
104
|
-
|
|
117
|
+
backgroundImageContainer: (
|
|
118
|
+
dimensions: Dimensions,
|
|
119
|
+
isTablet: boolean,
|
|
120
|
+
isPortraitMode: boolean
|
|
121
|
+
) => {
|
|
122
|
+
if (isTablet) {
|
|
123
|
+
const size = getAudioPlayerWidthForTablet(isPortraitMode);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
width: size,
|
|
127
|
+
height: size,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const mobileValue = dimensions?.width
|
|
105
132
|
? dimensions.width - PADDING_HORIZONTAL * 2
|
|
106
|
-
:
|
|
133
|
+
: SCREEN_WIDTH - PADDING_HORIZONTAL * 2;
|
|
107
134
|
|
|
108
135
|
return {
|
|
109
|
-
width:
|
|
110
|
-
height:
|
|
136
|
+
width: mobileValue,
|
|
137
|
+
height: mobileValue,
|
|
111
138
|
};
|
|
112
139
|
},
|
|
113
140
|
backgroundImageContainerImageStyle: (
|
|
114
141
|
isTablet: boolean,
|
|
115
142
|
docked: boolean,
|
|
116
143
|
startComponentsAnimation: boolean,
|
|
117
|
-
isActiveGesture: boolean
|
|
144
|
+
isActiveGesture: boolean,
|
|
145
|
+
playerAnimationState: PlayerAnimationStateT
|
|
118
146
|
) => ({
|
|
119
|
-
borderRadius:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
147
|
+
borderRadius: calculateBorderRadius(
|
|
148
|
+
isTablet,
|
|
149
|
+
docked,
|
|
150
|
+
startComponentsAnimation,
|
|
151
|
+
isActiveGesture,
|
|
152
|
+
playerAnimationState
|
|
153
|
+
),
|
|
125
154
|
}),
|
|
126
155
|
|
|
127
156
|
paddingHorizontal: {
|
|
@@ -173,8 +202,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
173
202
|
} = props;
|
|
174
203
|
|
|
175
204
|
const isTablet = useIsTablet();
|
|
176
|
-
const
|
|
177
|
-
const isPortraitMode = width < height;
|
|
205
|
+
const isPortraitMode = SCREEN_WIDTH < SCREEN_HEIGHT;
|
|
178
206
|
const isLiveContent = playerState.isLive;
|
|
179
207
|
const docked = layoutState.docked;
|
|
180
208
|
|
|
@@ -330,12 +358,15 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
330
358
|
|
|
331
359
|
const defaultImageDimensions = React.useMemo(
|
|
332
360
|
() =>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
361
|
+
Styles.backgroundImageContainer(
|
|
362
|
+
defaultDimensions,
|
|
363
|
+
isTablet,
|
|
364
|
+
isPortraitMode
|
|
365
|
+
),
|
|
366
|
+
[defaultDimensions, isTablet, isPortraitMode]
|
|
336
367
|
);
|
|
337
368
|
|
|
338
|
-
const { isActiveGesture, startComponentsAnimation } =
|
|
369
|
+
const { isActiveGesture, startComponentsAnimation, playerAnimationState } =
|
|
339
370
|
useModalAnimationContext();
|
|
340
371
|
|
|
341
372
|
return (
|
|
@@ -344,7 +375,10 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
344
375
|
isTablet ? Styles.tabletContainer(isPortraitMode) : Styles.container
|
|
345
376
|
}
|
|
346
377
|
>
|
|
347
|
-
<AnimationComponent
|
|
378
|
+
<AnimationComponent
|
|
379
|
+
animationType={ComponentAnimationType.componentFade}
|
|
380
|
+
style={isTablet ? Styles.playerInfoWrapper : undefined}
|
|
381
|
+
>
|
|
348
382
|
<TransportInfo
|
|
349
383
|
content={entry}
|
|
350
384
|
value={value}
|
|
@@ -356,13 +390,16 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
356
390
|
</AnimationComponent>
|
|
357
391
|
<AnimationComponent
|
|
358
392
|
animationType={ComponentAnimationType.moveUpComponent}
|
|
359
|
-
style={isTablet ? Styles.playerImageWrapper : undefined}
|
|
393
|
+
style={isTablet ? Styles.playerImageWrapper(docked) : undefined}
|
|
360
394
|
>
|
|
361
395
|
<AnimationComponent
|
|
362
396
|
animationType={ComponentAnimationType.moveComponentHorizontal}
|
|
363
397
|
additionalData={{
|
|
364
398
|
disableAnimatedComponent: !isTablet,
|
|
365
|
-
moveValue:
|
|
399
|
+
moveValue:
|
|
400
|
+
(dimensions.width -
|
|
401
|
+
getAudioPlayerWidthForTablet(isPortraitMode)) /
|
|
402
|
+
2,
|
|
366
403
|
extraAnimation: !isTablet
|
|
367
404
|
? {
|
|
368
405
|
[ComponentAnimationType.audioPlayerPaddingHorizontal]: {
|
|
@@ -375,12 +412,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
375
412
|
>
|
|
376
413
|
<PlayerImageBackground
|
|
377
414
|
entry={entry}
|
|
378
|
-
style={Styles.backgroundImageContainer(
|
|
415
|
+
style={Styles.backgroundImageContainer(
|
|
416
|
+
dimensions,
|
|
417
|
+
isTablet,
|
|
418
|
+
isPortraitMode
|
|
419
|
+
)}
|
|
379
420
|
imageStyle={Styles.backgroundImageContainerImageStyle(
|
|
380
421
|
isTablet,
|
|
381
422
|
docked,
|
|
382
423
|
startComponentsAnimation,
|
|
383
|
-
isActiveGesture
|
|
424
|
+
isActiveGesture,
|
|
425
|
+
playerAnimationState
|
|
384
426
|
)}
|
|
385
427
|
defaultImageDimensions={defaultImageDimensions}
|
|
386
428
|
/>
|
|
@@ -391,7 +433,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
391
433
|
<View
|
|
392
434
|
style={[
|
|
393
435
|
Styles.paddingHorizontal,
|
|
394
|
-
isTablet ? Styles.tabletLayout : undefined,
|
|
436
|
+
isTablet ? Styles.tabletLayout(isPortraitMode) : undefined,
|
|
395
437
|
]}
|
|
396
438
|
>
|
|
397
439
|
{!docked && (
|
|
@@ -405,9 +447,10 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
405
447
|
layoutState={layoutState}
|
|
406
448
|
/>
|
|
407
449
|
<Separator />
|
|
408
|
-
{needsToShowProgressBar
|
|
450
|
+
{needsToShowProgressBar ? (
|
|
409
451
|
<View>
|
|
410
452
|
<ProgressBar
|
|
453
|
+
name={"audio-default-layout"}
|
|
411
454
|
content={entry}
|
|
412
455
|
value={value}
|
|
413
456
|
visible={true}
|
|
@@ -422,17 +465,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
422
465
|
onSkipLive={onSkipLive}
|
|
423
466
|
/>
|
|
424
467
|
|
|
425
|
-
{showCounter && timeLeft >= 0
|
|
468
|
+
{showCounter && timeLeft >= 0 ? (
|
|
426
469
|
<CountDown
|
|
427
470
|
timeLeft={timeLeft}
|
|
428
471
|
value={value}
|
|
429
472
|
title={sleepTitle}
|
|
430
473
|
/>
|
|
431
|
-
)}
|
|
474
|
+
) : null}
|
|
432
475
|
|
|
433
476
|
<Separator />
|
|
434
477
|
</View>
|
|
435
|
-
)}
|
|
478
|
+
) : null}
|
|
436
479
|
<Controls
|
|
437
480
|
value={value}
|
|
438
481
|
visible={true}
|
|
@@ -9,18 +9,18 @@ import * as R from "ramda";
|
|
|
9
9
|
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
Button,
|
|
12
13
|
ContentInfo,
|
|
13
|
-
ProgressBar,
|
|
14
14
|
Controls,
|
|
15
|
-
|
|
15
|
+
ProgressBar,
|
|
16
16
|
} from "@applicaster/quick-brick-mobile-transport-controls";
|
|
17
17
|
|
|
18
18
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
19
19
|
import {
|
|
20
|
-
useModalAnimationContext,
|
|
21
20
|
AnimationComponent,
|
|
22
21
|
ComponentAnimationType,
|
|
23
22
|
PlayerAnimationStateEnum,
|
|
23
|
+
useModalAnimationContext,
|
|
24
24
|
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
25
25
|
|
|
26
26
|
type Dimensions = {
|
|
@@ -230,7 +230,7 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
230
230
|
} = useModalAnimationContext();
|
|
231
231
|
|
|
232
232
|
const maximazeVideoModal = React.useCallback(
|
|
233
|
-
() => setPlayerAnimationState(PlayerAnimationStateEnum.
|
|
233
|
+
() => setPlayerAnimationState(PlayerAnimationStateEnum.maximize),
|
|
234
234
|
[]
|
|
235
235
|
);
|
|
236
236
|
|
|
@@ -245,18 +245,19 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
245
245
|
]}
|
|
246
246
|
>
|
|
247
247
|
{!playerState.isLive &&
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
248
|
+
(layoutState.docked || startComponentsAnimation) ? (
|
|
249
|
+
<ProgressBar
|
|
250
|
+
name={"audio-docked-layout"}
|
|
251
|
+
content={entry}
|
|
252
|
+
value={value}
|
|
253
|
+
visible={visible}
|
|
254
|
+
layoutState={layoutState}
|
|
255
|
+
onPlayerSeek={onPlayerSeek}
|
|
256
|
+
playerState={playerState}
|
|
257
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
258
|
+
docked
|
|
259
|
+
/>
|
|
260
|
+
) : null}
|
|
260
261
|
|
|
261
262
|
<View style={styles.content}>
|
|
262
263
|
<View
|
|
@@ -17,7 +17,7 @@ const playbackButtonMargins = (
|
|
|
17
17
|
isTablet: boolean,
|
|
18
18
|
isLiveShowFullControls: boolean
|
|
19
19
|
): number =>
|
|
20
|
-
isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ?
|
|
20
|
+
isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ? undefined : 40;
|
|
21
21
|
|
|
22
22
|
export const PADDING_HORIZONTAL = 15;
|
|
23
23
|
|
|
@@ -47,7 +47,7 @@ const fetchPaletteAndDominantColor = async (imageUrl: string) => {
|
|
|
47
47
|
const dominant = await getDominantColor(imageUrl);
|
|
48
48
|
const dominantColor = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
|
|
49
49
|
|
|
50
|
-
return { gradientColors: colors.slice(0, 2), dominantColor };
|
|
50
|
+
return { gradientColors: colors.slice(0, 2).reverse(), dominantColor };
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
export const MagicBG: React.FC<MagicBGProps> = ({
|
|
@@ -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,4 +1,8 @@
|
|
|
1
1
|
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
2
|
+
import {
|
|
3
|
+
PlayerAnimationStateEnum,
|
|
4
|
+
PlayerAnimationStateT,
|
|
5
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
|
|
2
6
|
import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
3
7
|
|
|
4
8
|
export const NAMESPACE = "audio-player";
|
|
@@ -31,3 +35,23 @@ export const formatSpeed = (value: number) => {
|
|
|
31
35
|
// Return the formatted string with "speed_" prefix
|
|
32
36
|
return `speed_${formattedValue}`;
|
|
33
37
|
};
|
|
38
|
+
|
|
39
|
+
export const calculateBorderRadius = (
|
|
40
|
+
isTablet: boolean,
|
|
41
|
+
docked: boolean,
|
|
42
|
+
startComponentsAnimation: boolean,
|
|
43
|
+
isActiveGesture: boolean,
|
|
44
|
+
playerAnimationState: PlayerAnimationStateT
|
|
45
|
+
): number => {
|
|
46
|
+
const shouldRemoveBorderRadius =
|
|
47
|
+
(startComponentsAnimation &&
|
|
48
|
+
isActiveGesture &&
|
|
49
|
+
playerAnimationState !== PlayerAnimationStateEnum.maximize) ||
|
|
50
|
+
docked;
|
|
51
|
+
|
|
52
|
+
if (shouldRemoveBorderRadius) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return isTablet ? 24 : 10;
|
|
57
|
+
};
|
package/src/Player/index.tsx
CHANGED
|
@@ -721,7 +721,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
721
721
|
|
|
722
722
|
const isAudioItem =
|
|
723
723
|
entry?.content?.type?.includes?.("audio") ||
|
|
724
|
-
entry?.type?.value
|
|
724
|
+
entry?.type?.value?.includes?.("audio");
|
|
725
725
|
|
|
726
726
|
const getStyle = (dimensions) => {
|
|
727
727
|
const generalStyles = isAudioItem ? styles.audio : styles.video;
|
|
@@ -806,7 +806,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
806
806
|
nowPlayingEnabled,
|
|
807
807
|
paused,
|
|
808
808
|
seek,
|
|
809
|
-
rate,
|
|
810
809
|
textTrackId,
|
|
811
810
|
liveSeekingEnabled,
|
|
812
811
|
liveCatchUpEnabled,
|
|
@@ -817,7 +816,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
817
816
|
accessibilityProps: getAllAccessibilityProps(
|
|
818
817
|
this.getPluginConfiguration()
|
|
819
818
|
),
|
|
820
|
-
};
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
if (!isAppleTV()) {
|
|
822
|
+
// TODO: Move to RN method and remove from props.
|
|
823
|
+
// Causes tvOS bug: native player passes rate on fast seek,
|
|
824
|
+
// RN returns it and it breaks playback.
|
|
825
|
+
nativeProps.rate = rate;
|
|
826
|
+
}
|
|
821
827
|
|
|
822
828
|
if (
|
|
823
829
|
this.isReactNativeMethodsSupported() &&
|
|
@@ -990,14 +996,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
990
996
|
const renderMobilePlayer = () => {
|
|
991
997
|
return isFullScreenAudioPlayer && isAudioItem ? (
|
|
992
998
|
<View style={containerStyle}>
|
|
993
|
-
{
|
|
999
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
994
1000
|
<this.props.PlayerComponent
|
|
995
1001
|
ref={this._assignRoot}
|
|
996
1002
|
{...nativeEvents}
|
|
997
1003
|
{...nativeProps}
|
|
998
1004
|
listener={this.props.listener}
|
|
999
1005
|
/>
|
|
1000
|
-
)}
|
|
1006
|
+
) : null}
|
|
1001
1007
|
<AudioPlayerWrapper {...sharedWrapperViewProps} />
|
|
1002
1008
|
</View>
|
|
1003
1009
|
) : (
|
|
@@ -1012,7 +1018,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1012
1018
|
return (
|
|
1013
1019
|
<View style={styles.container}>
|
|
1014
1020
|
<View style={[playerContainerStyles, playerDimensions]}>
|
|
1015
|
-
{isAudioItem
|
|
1021
|
+
{isAudioItem ? (
|
|
1016
1022
|
<AnimationComponent
|
|
1017
1023
|
animationType={ComponentAnimationType.player}
|
|
1018
1024
|
additionalData={{
|
|
@@ -1031,18 +1037,18 @@ export default class VideoPlayer extends React.Component<
|
|
|
1031
1037
|
plugin_configuration={this.getPluginConfiguration()}
|
|
1032
1038
|
/>
|
|
1033
1039
|
</AnimationComponent>
|
|
1034
|
-
)}
|
|
1040
|
+
) : null}
|
|
1035
1041
|
|
|
1036
|
-
{shouldRenderBackground
|
|
1042
|
+
{shouldRenderBackground ? (
|
|
1037
1043
|
<PlayerImageBackground
|
|
1038
1044
|
imageKey={backgroundImageKey}
|
|
1039
1045
|
configuration={this.getPluginConfiguration()}
|
|
1040
1046
|
entry={entry}
|
|
1041
1047
|
style={getBackgroundImageStyle(playerDimensions)}
|
|
1042
1048
|
/>
|
|
1043
|
-
)}
|
|
1049
|
+
) : null}
|
|
1044
1050
|
|
|
1045
|
-
{
|
|
1051
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
1046
1052
|
<AnimationComponent
|
|
1047
1053
|
animationType={ComponentAnimationType.player}
|
|
1048
1054
|
additionalData={playerDimensions}
|
|
@@ -1064,7 +1070,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1064
1070
|
{this.childrenWithPlayNext()}
|
|
1065
1071
|
</this.props.PlayerComponent>
|
|
1066
1072
|
</AnimationComponent>
|
|
1067
|
-
)}
|
|
1073
|
+
) : null}
|
|
1068
1074
|
</View>
|
|
1069
1075
|
|
|
1070
1076
|
{renderTransportControls(playerDimensions)}
|