@applicaster/quick-brick-player 13.0.0-alpha.3013227535 → 13.0.0-alpha.3099523645
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 -8
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +90 -127
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +12 -11
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +15 -15
- package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
- package/src/Player/AudioLayer/SpeedManager.ts +52 -0
- package/src/Player/index.tsx +16 -10
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.3099523645",
|
|
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-rc.
|
|
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.46",
|
|
40
|
+
"@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.46",
|
|
41
|
+
"@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.3099523645",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.3099523645",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.3099523645",
|
|
44
44
|
"keymirror": "^0.1.1",
|
|
45
45
|
"query-string": "7.1.3",
|
|
46
46
|
"react-native-web-linear-gradient": "1.1.2",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"@types/node": "*",
|
|
65
65
|
"@types/react-native": "0.69.6",
|
|
66
66
|
"immer": "*",
|
|
67
|
-
"react": "
|
|
67
|
+
"react": "*",
|
|
68
68
|
"react-hot-loader": "*",
|
|
69
|
-
"react-native": "
|
|
69
|
+
"react-native": "*",
|
|
70
70
|
"react-native-fs": "*",
|
|
71
71
|
"react-native-gesture-handler": "*",
|
|
72
72
|
"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 {
|
|
@@ -33,6 +27,20 @@ import {
|
|
|
33
27
|
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
34
28
|
import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
|
|
35
29
|
import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
30
|
+
import {
|
|
31
|
+
speed_0_8,
|
|
32
|
+
speed_1,
|
|
33
|
+
speed_1_2,
|
|
34
|
+
speed_1_5,
|
|
35
|
+
speed_2,
|
|
36
|
+
} from "@applicaster/quick-brick-mobile-transport-controls";
|
|
37
|
+
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";
|
|
36
44
|
|
|
37
45
|
const { log_debug } = createLogger({
|
|
38
46
|
subsystem: "UIComponent",
|
|
@@ -71,8 +79,7 @@ type Props = {
|
|
|
71
79
|
isTabletPortrait?: boolean;
|
|
72
80
|
};
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
(height / 9) * 16;
|
|
82
|
+
const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
|
|
76
83
|
|
|
77
84
|
const Styles = {
|
|
78
85
|
mainContainer: (style: ViewStyle) => {
|
|
@@ -98,7 +105,11 @@ const Styles = {
|
|
|
98
105
|
height: 96,
|
|
99
106
|
marginHorizontal: 24,
|
|
100
107
|
},
|
|
101
|
-
playerDetails: {
|
|
108
|
+
playerDetails: {
|
|
109
|
+
marginTop: 60,
|
|
110
|
+
alignSelf: "center",
|
|
111
|
+
width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
|
|
112
|
+
},
|
|
102
113
|
|
|
103
114
|
linearGradient: {
|
|
104
115
|
flex: 1,
|
|
@@ -132,11 +143,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
132
143
|
} = props;
|
|
133
144
|
|
|
134
145
|
const speeds = [
|
|
135
|
-
{ asset: configuration["speed_0_8"], value: 0.8 },
|
|
136
|
-
{ asset: configuration["speed_1"], value: 1.0 },
|
|
137
|
-
{ asset: configuration["speed_1_2"], value: 1.2 },
|
|
138
|
-
{ asset: configuration["speed_1_5"], value: 1.5 },
|
|
139
|
-
{ asset: configuration["speed_2"], value: 2.0 },
|
|
146
|
+
{ asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
|
|
147
|
+
{ asset: configuration["speed_1"] || speed_1, value: 1.0 },
|
|
148
|
+
{ asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
|
|
149
|
+
{ asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
|
|
150
|
+
{ asset: configuration["speed_2"] || speed_2, value: 2.0 },
|
|
140
151
|
];
|
|
141
152
|
|
|
142
153
|
const isTablet = useIsTablet();
|
|
@@ -154,17 +165,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
154
165
|
configuration?.audio_live_player_update_interval
|
|
155
166
|
);
|
|
156
167
|
|
|
157
|
-
const [
|
|
158
|
-
|
|
159
|
-
value: 1.0,
|
|
160
|
-
});
|
|
168
|
+
const [sleepTimerManager, setSleepTimerManager] =
|
|
169
|
+
useState<SleepTimerController | null>(null);
|
|
161
170
|
|
|
162
|
-
const [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
const [speedManager, setSpeedManager] = useState<SpeedController | null>(
|
|
172
|
+
null
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (player) {
|
|
177
|
+
setSleepTimerManager(new SleepTimerController(player));
|
|
178
|
+
setSpeedManager(new SpeedController(player));
|
|
179
|
+
} else {
|
|
180
|
+
setSleepTimerManager(null);
|
|
181
|
+
setSpeedManager(null);
|
|
182
|
+
}
|
|
183
|
+
}, [player]);
|
|
184
|
+
|
|
185
|
+
const [playbackSpeed, setPlaybackSpeed] =
|
|
186
|
+
useState<PlaybackSpeed>(DEFAULT_SPEED);
|
|
166
187
|
|
|
167
|
-
const [
|
|
188
|
+
const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
|
|
189
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
190
|
+
);
|
|
168
191
|
|
|
169
192
|
const [defaultDimensions, setDefaultDimensions] =
|
|
170
193
|
React.useState<Dimensions>(undefined);
|
|
@@ -189,90 +212,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
189
212
|
if (!defaultDimensions) setDefaultDimensions({ width, height });
|
|
190
213
|
};
|
|
191
214
|
|
|
192
|
-
const cancelSleepTimer = useCallback(async () => {
|
|
193
|
-
player?.cancelSleepTimer();
|
|
194
|
-
|
|
195
|
-
setSleepTimer({
|
|
196
|
-
label: "playback_sleep_off_label",
|
|
197
|
-
value: 0,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
setSleepTimerDate(0);
|
|
201
|
-
|
|
202
|
-
await localStorage.removeItem("sleep", NAMESPACE);
|
|
203
|
-
}, [player, setSleepTimer, setSleepTimerDate]);
|
|
204
|
-
|
|
205
|
-
const startSleepTimer = useCallback(
|
|
206
|
-
(timerValue: number) => {
|
|
207
|
-
try {
|
|
208
|
-
const futureSleepDate = Date.now() + timerValue * 60000;
|
|
209
|
-
|
|
210
|
-
player?.cancelSleepTimer?.();
|
|
211
|
-
|
|
212
|
-
player?.startSleepTimer?.(futureSleepDate);
|
|
213
|
-
|
|
214
|
-
return futureSleepDate;
|
|
215
|
-
} catch (e) {
|
|
216
|
-
return 0;
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
[player]
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const handleSleep = useCallback(
|
|
223
|
-
async (timerValue: number, currentFutureSleepDate = null) => {
|
|
224
|
-
if (currentFutureSleepDate) {
|
|
225
|
-
if (hasDatePassed(currentFutureSleepDate)) {
|
|
226
|
-
await cancelSleepTimer();
|
|
227
|
-
|
|
228
|
-
return 0;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return currentFutureSleepDate;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (timerValue === 0) {
|
|
235
|
-
await cancelSleepTimer();
|
|
236
|
-
|
|
237
|
-
return timerValue;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return startSleepTimer(timerValue);
|
|
241
|
-
},
|
|
242
|
-
[startSleepTimer, cancelSleepTimer]
|
|
243
|
-
);
|
|
244
|
-
|
|
245
215
|
useEffect(() => {
|
|
246
|
-
const
|
|
247
|
-
|
|
216
|
+
const sleepSubscription =
|
|
217
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
248
218
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
player?.setPlaybackRate(speed.value);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const loadSleep = async () => {
|
|
256
|
-
const sleep = await loadItemsFromStorage("sleep");
|
|
219
|
+
const speedSubscription =
|
|
220
|
+
speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
|
|
257
221
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
|
|
262
|
-
setSleepTimerDate(sleepTimerDate);
|
|
263
|
-
}
|
|
222
|
+
return () => {
|
|
223
|
+
sleepSubscription?.unsubscribe();
|
|
224
|
+
speedSubscription?.unsubscribe();
|
|
264
225
|
};
|
|
226
|
+
}, [sleepTimerManager, speedManager]);
|
|
265
227
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
player,
|
|
275
|
-
]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!playerState.isReadyToPlay) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void speedManager?.loadSpeed();
|
|
234
|
+
void sleepTimerManager?.loadSleep();
|
|
235
|
+
}, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
|
|
276
236
|
|
|
277
237
|
const getValue = (key: string, stylesObj: {}) =>
|
|
278
238
|
R.propOr(null, key, stylesObj);
|
|
@@ -315,26 +275,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
315
275
|
|
|
316
276
|
const onSpeedSelect = React.useCallback(
|
|
317
277
|
async (item) => {
|
|
318
|
-
setPlaybackSpeed(item);
|
|
319
|
-
player?.setPlaybackRate(item.value);
|
|
320
|
-
await localStorage.setItem("speed", item, NAMESPACE);
|
|
278
|
+
await speedManager?.setPlaybackSpeed(item);
|
|
321
279
|
},
|
|
322
|
-
[
|
|
280
|
+
[speedManager]
|
|
323
281
|
);
|
|
324
282
|
|
|
325
283
|
const onSleepSelect = React.useCallback(
|
|
326
284
|
async (item) => {
|
|
327
|
-
setSleepTimer(item);
|
|
328
|
-
const futureSleepDate = await handleSleep(item.value);
|
|
329
|
-
setSleepTimerDate(futureSleepDate);
|
|
330
|
-
|
|
331
|
-
await localStorage.setItem(
|
|
332
|
-
"sleep",
|
|
333
|
-
{ ...item, futureSleepDate },
|
|
334
|
-
NAMESPACE
|
|
335
|
-
);
|
|
285
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
336
286
|
},
|
|
337
|
-
[
|
|
287
|
+
[sleepTimerManager]
|
|
338
288
|
);
|
|
339
289
|
|
|
340
290
|
const onSpeedButtonPress = React.useCallback(() => {
|
|
@@ -361,9 +311,12 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
361
311
|
navigator.openModalBottomSheet({
|
|
362
312
|
modalBottomSheetContentProps: {
|
|
363
313
|
items: [
|
|
364
|
-
{
|
|
314
|
+
{
|
|
315
|
+
label: configuration["playback_sleep_off_label"],
|
|
316
|
+
value: 0,
|
|
317
|
+
},
|
|
365
318
|
].concat(sleeps),
|
|
366
|
-
current_selection:
|
|
319
|
+
current_selection: sleepTimerState.timer,
|
|
367
320
|
onPress: onSleepSelect,
|
|
368
321
|
title: configuration["playback_sleep_title"],
|
|
369
322
|
},
|
|
@@ -373,12 +326,20 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
373
326
|
});
|
|
374
327
|
}, [
|
|
375
328
|
navigator,
|
|
376
|
-
|
|
329
|
+
sleepTimerState.timer,
|
|
377
330
|
configuration["playback_sleep_title"],
|
|
378
331
|
configuration["playback_sleep_off_label"],
|
|
379
332
|
onSleepSelect,
|
|
380
333
|
]);
|
|
381
334
|
|
|
335
|
+
// This method does not actually cancel the sleep timer
|
|
336
|
+
// (and should not to avoid race condition),
|
|
337
|
+
// it just performs the cleanup when timer expires
|
|
338
|
+
// TODO: rename it in code above
|
|
339
|
+
const cancelSleepTimer = React.useCallback(() => {
|
|
340
|
+
sleepTimerManager?.onTimerExpired();
|
|
341
|
+
}, [sleepTimerManager]);
|
|
342
|
+
|
|
382
343
|
const onCloseVideoModal = React.useCallback(() => {
|
|
383
344
|
playerManager.close();
|
|
384
345
|
}, [playerManager]);
|
|
@@ -426,9 +387,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
426
387
|
onSleepButtonPress={onSleepButtonPress}
|
|
427
388
|
playbackSpeedName={formatSpeed(playbackSpeed.value)}
|
|
428
389
|
playbackSleepName={
|
|
429
|
-
|
|
390
|
+
sleepTimerState.timer.value === 0
|
|
391
|
+
? "sleep_timer"
|
|
392
|
+
: "sleep_timer_active"
|
|
430
393
|
}
|
|
431
|
-
sleepTimerDate={
|
|
394
|
+
sleepTimerDate={sleepTimerState.timerDate}
|
|
432
395
|
cancelSleepTimer={cancelSleepTimer}
|
|
433
396
|
onSkipLive={onSkipLive}
|
|
434
397
|
onCloseVideoModal={onCloseVideoModal}
|
|
@@ -478,12 +441,12 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
478
441
|
playNextData,
|
|
479
442
|
onSkipStart,
|
|
480
443
|
onSkipLive,
|
|
481
|
-
|
|
482
|
-
|
|
444
|
+
sleepTimerState.timer.value,
|
|
445
|
+
sleepTimerState.timerDate,
|
|
483
446
|
]);
|
|
484
447
|
|
|
485
448
|
if (configuration.magic_background) {
|
|
486
|
-
const baseUrl = entry
|
|
449
|
+
const baseUrl = imageSrcFromMediaItem(entry, null);
|
|
487
450
|
|
|
488
451
|
const queryParamName =
|
|
489
452
|
configuration.audio_player_background_image_query || "width";
|
|
@@ -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
4
|
import { View } 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,23 +16,22 @@ 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
36
|
import DockedLayout from "./DockedLayout";
|
|
37
37
|
import { isValidDate } from "../../Utils";
|
|
@@ -375,6 +375,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
375
375
|
>
|
|
376
376
|
<PlayerImageBackground
|
|
377
377
|
entry={entry}
|
|
378
|
+
docked={docked}
|
|
378
379
|
style={Styles.backgroundImageContainer(dimensions, isTablet)}
|
|
379
380
|
imageStyle={Styles.backgroundImageContainerImageStyle(
|
|
380
381
|
isTablet,
|
|
@@ -405,7 +406,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
405
406
|
layoutState={layoutState}
|
|
406
407
|
/>
|
|
407
408
|
<Separator />
|
|
408
|
-
{needsToShowProgressBar
|
|
409
|
+
{needsToShowProgressBar ? (
|
|
409
410
|
<View>
|
|
410
411
|
<ProgressBar
|
|
411
412
|
content={entry}
|
|
@@ -422,17 +423,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
422
423
|
onSkipLive={onSkipLive}
|
|
423
424
|
/>
|
|
424
425
|
|
|
425
|
-
{showCounter && timeLeft >= 0
|
|
426
|
+
{showCounter && timeLeft >= 0 ? (
|
|
426
427
|
<CountDown
|
|
427
428
|
timeLeft={timeLeft}
|
|
428
429
|
value={value}
|
|
429
430
|
title={sleepTitle}
|
|
430
431
|
/>
|
|
431
|
-
)}
|
|
432
|
+
) : null}
|
|
432
433
|
|
|
433
434
|
<Separator />
|
|
434
435
|
</View>
|
|
435
|
-
)}
|
|
436
|
+
) : null}
|
|
436
437
|
<Controls
|
|
437
438
|
value={value}
|
|
438
439
|
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 = {
|
|
@@ -245,18 +245,18 @@ 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
|
+
content={entry}
|
|
251
|
+
value={value}
|
|
252
|
+
visible={visible}
|
|
253
|
+
layoutState={layoutState}
|
|
254
|
+
onPlayerSeek={onPlayerSeek}
|
|
255
|
+
playerState={playerState}
|
|
256
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
257
|
+
docked
|
|
258
|
+
/>
|
|
259
|
+
) : null}
|
|
260
260
|
|
|
261
261
|
<View style={styles.content}>
|
|
262
262
|
<View
|
|
@@ -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
|
+
}
|
package/src/Player/index.tsx
CHANGED
|
@@ -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)}
|