@applicaster/quick-brick-player 13.0.0-alpha.6525006318 → 13.0.0-alpha.6912563393
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 +28 -9
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +94 -115
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +13 -13
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +15 -15
- package/src/Player/AudioLayer/Layout/defaultLayoutsConts.ts +2 -2
- package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
- package/src/Player/AudioLayer/SpeedManager.ts +52 -0
- package/src/Player/index.tsx +20 -18
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.6912563393",
|
|
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": "
|
|
40
|
-
"@applicaster/quick-brick-tv-transport-controls": "
|
|
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.6912563393",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.6912563393",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.6912563393",
|
|
44
44
|
"keymirror": "^0.1.1",
|
|
45
45
|
"query-string": "7.1.3",
|
|
46
46
|
"react-native-web-linear-gradient": "1.1.2",
|
|
@@ -51,12 +51,31 @@
|
|
|
51
51
|
"videojs-contrib-quality-levels": "3.0.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
+
"@applicaster/applicaster-types": "*",
|
|
55
|
+
"@applicaster/blur": "*",
|
|
54
56
|
"@applicaster/quick-brick-xray": "*",
|
|
55
57
|
"@applicaster/react-native-color-thief": "*",
|
|
58
|
+
"@applicaster/zapp-pipes-v2-client": "*",
|
|
59
|
+
"@applicaster/zapp-react-dom-ui-components": "*",
|
|
60
|
+
"@applicaster/zapp-react-native-redux": "*",
|
|
61
|
+
"@applicaster/zapp-react-native-tvos-ui-components": "*",
|
|
62
|
+
"@babel/runtime": "*",
|
|
63
|
+
"@react-native-community/netinfo": "*",
|
|
56
64
|
"@types/node": "*",
|
|
57
65
|
"@types/react-native": "0.69.6",
|
|
58
|
-
"
|
|
59
|
-
"react
|
|
60
|
-
"react-
|
|
66
|
+
"immer": "*",
|
|
67
|
+
"react": "*",
|
|
68
|
+
"react-hot-loader": "*",
|
|
69
|
+
"react-native": "*",
|
|
70
|
+
"react-native-fs": "*",
|
|
71
|
+
"react-native-gesture-handler": "*",
|
|
72
|
+
"react-native-linear-gradient": "*",
|
|
73
|
+
"react-native-safe-area-context": "*",
|
|
74
|
+
"react-native-svg": "*",
|
|
75
|
+
"react-native-web": "*",
|
|
76
|
+
"react-native-webview": "*",
|
|
77
|
+
"uglify-js": "*",
|
|
78
|
+
"validate-color": "*",
|
|
79
|
+
"zustand": "*"
|
|
61
80
|
}
|
|
62
81
|
}
|
|
@@ -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);
|
|
@@ -17,13 +17,7 @@ import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Comp
|
|
|
17
17
|
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
18
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,19 @@ 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";
|
|
36
43
|
|
|
37
44
|
const { log_debug } = createLogger({
|
|
38
45
|
subsystem: "UIComponent",
|
|
@@ -132,11 +139,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
132
139
|
} = props;
|
|
133
140
|
|
|
134
141
|
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 },
|
|
142
|
+
{ asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
|
|
143
|
+
{ asset: configuration["speed_1"] || speed_1, value: 1.0 },
|
|
144
|
+
{ asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
|
|
145
|
+
{ asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
|
|
146
|
+
{ asset: configuration["speed_2"] || speed_2, value: 2.0 },
|
|
140
147
|
];
|
|
141
148
|
|
|
142
149
|
const isTablet = useIsTablet();
|
|
@@ -154,17 +161,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
154
161
|
configuration?.audio_live_player_update_interval
|
|
155
162
|
);
|
|
156
163
|
|
|
157
|
-
const [
|
|
158
|
-
|
|
159
|
-
value: 1.0,
|
|
160
|
-
});
|
|
164
|
+
const [sleepTimerManager, setSleepTimerManager] =
|
|
165
|
+
useState<SleepTimerController | null>(null);
|
|
161
166
|
|
|
162
|
-
const [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
167
|
+
const [speedManager, setSpeedManager] = useState<SpeedController | null>(
|
|
168
|
+
null
|
|
169
|
+
);
|
|
166
170
|
|
|
167
|
-
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (player) {
|
|
173
|
+
setSleepTimerManager(new SleepTimerController(player));
|
|
174
|
+
setSpeedManager(new SpeedController(player));
|
|
175
|
+
} else {
|
|
176
|
+
setSleepTimerManager(null);
|
|
177
|
+
setSpeedManager(null);
|
|
178
|
+
}
|
|
179
|
+
}, [player]);
|
|
180
|
+
|
|
181
|
+
const [playbackSpeed, setPlaybackSpeed] =
|
|
182
|
+
useState<PlaybackSpeed>(DEFAULT_SPEED);
|
|
183
|
+
|
|
184
|
+
const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
|
|
185
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
186
|
+
);
|
|
168
187
|
|
|
169
188
|
const [defaultDimensions, setDefaultDimensions] =
|
|
170
189
|
React.useState<Dimensions>(undefined);
|
|
@@ -189,82 +208,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
189
208
|
if (!defaultDimensions) setDefaultDimensions({ width, height });
|
|
190
209
|
};
|
|
191
210
|
|
|
192
|
-
const cancelSleepTimer = 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
|
-
};
|
|
204
|
-
|
|
205
|
-
const startSleepTimer = (timerValue: number) => {
|
|
206
|
-
try {
|
|
207
|
-
const currentDate = new Date();
|
|
208
|
-
const futureDate = new Date(currentDate.getTime() + timerValue * 60000);
|
|
209
|
-
const futureSleepDate = futureDate.getTime();
|
|
210
|
-
|
|
211
|
-
player?.cancelSleepTimer();
|
|
212
|
-
|
|
213
|
-
player?.startSleepTimer(futureSleepDate);
|
|
214
|
-
|
|
215
|
-
return futureSleepDate;
|
|
216
|
-
} catch (e) {
|
|
217
|
-
return 0;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const handleSleep = async (
|
|
222
|
-
timerValue: number,
|
|
223
|
-
currentFutureSleepDate = null
|
|
224
|
-
) => {
|
|
225
|
-
if (currentFutureSleepDate) {
|
|
226
|
-
if (hasDatePassed(currentFutureSleepDate)) {
|
|
227
|
-
await cancelSleepTimer();
|
|
228
|
-
|
|
229
|
-
return 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return currentFutureSleepDate;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (timerValue === 0) {
|
|
236
|
-
await cancelSleepTimer();
|
|
237
|
-
|
|
238
|
-
return timerValue;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return startSleepTimer(timerValue);
|
|
242
|
-
};
|
|
243
|
-
|
|
244
211
|
useEffect(() => {
|
|
245
|
-
const
|
|
246
|
-
|
|
212
|
+
const sleepSubscription =
|
|
213
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
247
214
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
player?.setPlaybackRate(speed.value);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
215
|
+
const speedSubscription =
|
|
216
|
+
speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
|
|
253
217
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (playerState.isReadyToPlay && sleep) {
|
|
258
|
-
const { futureSleepDate, ...rest } = sleep;
|
|
259
|
-
setSleepTimer(rest);
|
|
260
|
-
const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
|
|
261
|
-
setSleepTimerDate(sleepTimerDate);
|
|
262
|
-
}
|
|
218
|
+
return () => {
|
|
219
|
+
sleepSubscription?.unsubscribe();
|
|
220
|
+
speedSubscription?.unsubscribe();
|
|
263
221
|
};
|
|
222
|
+
}, [sleepTimerManager, speedManager]);
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
if (!playerState.isReadyToPlay) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
264
228
|
|
|
265
|
-
loadSpeed()
|
|
266
|
-
loadSleep()
|
|
267
|
-
}, [playerState.isReadyToPlay]);
|
|
229
|
+
void speedManager?.loadSpeed();
|
|
230
|
+
void sleepTimerManager?.loadSleep();
|
|
231
|
+
}, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
|
|
268
232
|
|
|
269
233
|
const getValue = (key: string, stylesObj: {}) =>
|
|
270
234
|
R.propOr(null, key, stylesObj);
|
|
@@ -292,7 +256,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
292
256
|
|
|
293
257
|
const onPlaybackButtonPress = React.useCallback(() => {
|
|
294
258
|
if (playerState.isBuffering) return;
|
|
295
|
-
playerState.isPaused ? player?.play() : player
|
|
259
|
+
playerState.isPaused ? player?.play() : player?.pause();
|
|
296
260
|
}, [playerState, player]);
|
|
297
261
|
|
|
298
262
|
const seekDuration = toNumberWithDefault(10, value("seek_duration"));
|
|
@@ -307,26 +271,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
307
271
|
|
|
308
272
|
const onSpeedSelect = React.useCallback(
|
|
309
273
|
async (item) => {
|
|
310
|
-
setPlaybackSpeed(item);
|
|
311
|
-
player.setPlaybackRate(item.value);
|
|
312
|
-
await localStorage.setItem("speed", item, NAMESPACE);
|
|
274
|
+
await speedManager?.setPlaybackSpeed(item);
|
|
313
275
|
},
|
|
314
|
-
[
|
|
276
|
+
[speedManager]
|
|
315
277
|
);
|
|
316
278
|
|
|
317
279
|
const onSleepSelect = React.useCallback(
|
|
318
280
|
async (item) => {
|
|
319
|
-
setSleepTimer(item);
|
|
320
|
-
const futureSleepDate = await handleSleep(item.value);
|
|
321
|
-
setSleepTimerDate(futureSleepDate);
|
|
322
|
-
|
|
323
|
-
await localStorage.setItem(
|
|
324
|
-
"sleep",
|
|
325
|
-
{ ...item, futureSleepDate },
|
|
326
|
-
NAMESPACE
|
|
327
|
-
);
|
|
281
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
328
282
|
},
|
|
329
|
-
[
|
|
283
|
+
[sleepTimerManager]
|
|
330
284
|
);
|
|
331
285
|
|
|
332
286
|
const onSpeedButtonPress = React.useCallback(() => {
|
|
@@ -341,15 +295,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
341
295
|
supportedOrientations: ["portrait"],
|
|
342
296
|
},
|
|
343
297
|
});
|
|
344
|
-
}, [
|
|
298
|
+
}, [
|
|
299
|
+
playbackSpeed,
|
|
300
|
+
setPlaybackSpeed,
|
|
301
|
+
navigator,
|
|
302
|
+
onSpeedSelect,
|
|
303
|
+
configuration["playback_speed_title"],
|
|
304
|
+
]);
|
|
345
305
|
|
|
346
306
|
const onSleepButtonPress = React.useCallback(() => {
|
|
347
307
|
navigator.openModalBottomSheet({
|
|
348
308
|
modalBottomSheetContentProps: {
|
|
349
309
|
items: [
|
|
350
|
-
{
|
|
310
|
+
{
|
|
311
|
+
label: configuration["playback_sleep_off_label"],
|
|
312
|
+
value: 0,
|
|
313
|
+
},
|
|
351
314
|
].concat(sleeps),
|
|
352
|
-
current_selection:
|
|
315
|
+
current_selection: sleepTimerState.timer,
|
|
353
316
|
onPress: onSleepSelect,
|
|
354
317
|
title: configuration["playback_sleep_title"],
|
|
355
318
|
},
|
|
@@ -357,7 +320,21 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
357
320
|
supportedOrientations: ["portrait"],
|
|
358
321
|
},
|
|
359
322
|
});
|
|
360
|
-
}, [
|
|
323
|
+
}, [
|
|
324
|
+
navigator,
|
|
325
|
+
sleepTimerState.timer,
|
|
326
|
+
configuration["playback_sleep_title"],
|
|
327
|
+
configuration["playback_sleep_off_label"],
|
|
328
|
+
onSleepSelect,
|
|
329
|
+
]);
|
|
330
|
+
|
|
331
|
+
// This method does not actually cancel the sleep timer
|
|
332
|
+
// (and should not to avoid race condition),
|
|
333
|
+
// it just performs the cleanup when timer expires
|
|
334
|
+
// TODO: rename it in code above
|
|
335
|
+
const cancelSleepTimer = React.useCallback(() => {
|
|
336
|
+
sleepTimerManager?.onTimerExpired();
|
|
337
|
+
}, [sleepTimerManager]);
|
|
361
338
|
|
|
362
339
|
const onCloseVideoModal = React.useCallback(() => {
|
|
363
340
|
playerManager.close();
|
|
@@ -406,9 +383,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
406
383
|
onSleepButtonPress={onSleepButtonPress}
|
|
407
384
|
playbackSpeedName={formatSpeed(playbackSpeed.value)}
|
|
408
385
|
playbackSleepName={
|
|
409
|
-
|
|
386
|
+
sleepTimerState.timer.value === 0
|
|
387
|
+
? "sleep_timer"
|
|
388
|
+
: "sleep_timer_active"
|
|
410
389
|
}
|
|
411
|
-
sleepTimerDate={
|
|
390
|
+
sleepTimerDate={sleepTimerState.timerDate}
|
|
412
391
|
cancelSleepTimer={cancelSleepTimer}
|
|
413
392
|
onSkipLive={onSkipLive}
|
|
414
393
|
onCloseVideoModal={onCloseVideoModal}
|
|
@@ -458,8 +437,8 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
458
437
|
playNextData,
|
|
459
438
|
onSkipStart,
|
|
460
439
|
onSkipLive,
|
|
461
|
-
|
|
462
|
-
|
|
440
|
+
sleepTimerState.timer.value,
|
|
441
|
+
sleepTimerState.timerDate,
|
|
463
442
|
]);
|
|
464
443
|
|
|
465
444
|
if (configuration.magic_background) {
|
|
@@ -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";
|
|
@@ -120,8 +120,8 @@ const Styles = {
|
|
|
120
120
|
(startComponentsAnimation && isActiveGesture) || docked
|
|
121
121
|
? 0
|
|
122
122
|
: isTablet
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
? 24
|
|
124
|
+
: 10,
|
|
125
125
|
}),
|
|
126
126
|
|
|
127
127
|
paddingHorizontal: {
|
|
@@ -405,7 +405,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
405
405
|
layoutState={layoutState}
|
|
406
406
|
/>
|
|
407
407
|
<Separator />
|
|
408
|
-
{needsToShowProgressBar
|
|
408
|
+
{needsToShowProgressBar ? (
|
|
409
409
|
<View>
|
|
410
410
|
<ProgressBar
|
|
411
411
|
content={entry}
|
|
@@ -422,17 +422,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
422
422
|
onSkipLive={onSkipLive}
|
|
423
423
|
/>
|
|
424
424
|
|
|
425
|
-
{showCounter && timeLeft >= 0
|
|
425
|
+
{showCounter && timeLeft >= 0 ? (
|
|
426
426
|
<CountDown
|
|
427
427
|
timeLeft={timeLeft}
|
|
428
428
|
value={value}
|
|
429
429
|
title={sleepTitle}
|
|
430
430
|
/>
|
|
431
|
-
)}
|
|
431
|
+
) : null}
|
|
432
432
|
|
|
433
433
|
<Separator />
|
|
434
434
|
</View>
|
|
435
|
-
)}
|
|
435
|
+
) : null}
|
|
436
436
|
<Controls
|
|
437
437
|
value={value}
|
|
438
438
|
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
|
@@ -253,12 +253,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
253
253
|
};
|
|
254
254
|
|
|
255
255
|
stopBackgroundPlayback = () => {
|
|
256
|
-
|
|
257
|
-
this.isReactNativeMethodsSupported() &&
|
|
258
|
-
RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback
|
|
259
|
-
) {
|
|
260
|
-
RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback();
|
|
261
|
-
}
|
|
256
|
+
RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback?.();
|
|
262
257
|
};
|
|
263
258
|
|
|
264
259
|
forward = (seekTime = SEEK_TIME) => {
|
|
@@ -811,7 +806,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
811
806
|
nowPlayingEnabled,
|
|
812
807
|
paused,
|
|
813
808
|
seek,
|
|
814
|
-
rate,
|
|
815
809
|
textTrackId,
|
|
816
810
|
liveSeekingEnabled,
|
|
817
811
|
liveCatchUpEnabled,
|
|
@@ -822,7 +816,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
822
816
|
accessibilityProps: getAllAccessibilityProps(
|
|
823
817
|
this.getPluginConfiguration()
|
|
824
818
|
),
|
|
825
|
-
};
|
|
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
|
+
}
|
|
826
827
|
|
|
827
828
|
if (
|
|
828
829
|
this.isReactNativeMethodsSupported() &&
|
|
@@ -923,8 +924,8 @@ export default class VideoPlayer extends React.Component<
|
|
|
923
924
|
const playerContainerStyles = isApplePlatform()
|
|
924
925
|
? styles.playerContainerBlack
|
|
925
926
|
: isTV() && isAudioItem
|
|
926
|
-
|
|
927
|
-
|
|
927
|
+
? styles.playerContainerTransparent
|
|
928
|
+
: styles.playerContainerBlack;
|
|
928
929
|
|
|
929
930
|
const isInline = () => {
|
|
930
931
|
if (isTV()) {
|
|
@@ -995,13 +996,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
995
996
|
const renderMobilePlayer = () => {
|
|
996
997
|
return isFullScreenAudioPlayer && isAudioItem ? (
|
|
997
998
|
<View style={containerStyle}>
|
|
998
|
-
{
|
|
999
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
999
1000
|
<this.props.PlayerComponent
|
|
1000
1001
|
ref={this._assignRoot}
|
|
1001
1002
|
{...nativeEvents}
|
|
1002
1003
|
{...nativeProps}
|
|
1004
|
+
listener={this.props.listener}
|
|
1003
1005
|
/>
|
|
1004
|
-
)}
|
|
1006
|
+
) : null}
|
|
1005
1007
|
<AudioPlayerWrapper {...sharedWrapperViewProps} />
|
|
1006
1008
|
</View>
|
|
1007
1009
|
) : (
|
|
@@ -1016,7 +1018,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1016
1018
|
return (
|
|
1017
1019
|
<View style={styles.container}>
|
|
1018
1020
|
<View style={[playerContainerStyles, playerDimensions]}>
|
|
1019
|
-
{isAudioItem
|
|
1021
|
+
{isAudioItem ? (
|
|
1020
1022
|
<AnimationComponent
|
|
1021
1023
|
animationType={ComponentAnimationType.player}
|
|
1022
1024
|
additionalData={{
|
|
@@ -1035,18 +1037,18 @@ export default class VideoPlayer extends React.Component<
|
|
|
1035
1037
|
plugin_configuration={this.getPluginConfiguration()}
|
|
1036
1038
|
/>
|
|
1037
1039
|
</AnimationComponent>
|
|
1038
|
-
)}
|
|
1040
|
+
) : null}
|
|
1039
1041
|
|
|
1040
|
-
{shouldRenderBackground
|
|
1042
|
+
{shouldRenderBackground ? (
|
|
1041
1043
|
<PlayerImageBackground
|
|
1042
1044
|
imageKey={backgroundImageKey}
|
|
1043
1045
|
configuration={this.getPluginConfiguration()}
|
|
1044
1046
|
entry={entry}
|
|
1045
1047
|
style={getBackgroundImageStyle(playerDimensions)}
|
|
1046
1048
|
/>
|
|
1047
|
-
)}
|
|
1049
|
+
) : null}
|
|
1048
1050
|
|
|
1049
|
-
{
|
|
1051
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
1050
1052
|
<AnimationComponent
|
|
1051
1053
|
animationType={ComponentAnimationType.player}
|
|
1052
1054
|
additionalData={playerDimensions}
|
|
@@ -1068,7 +1070,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1068
1070
|
{this.childrenWithPlayNext()}
|
|
1069
1071
|
</this.props.PlayerComponent>
|
|
1070
1072
|
</AnimationComponent>
|
|
1071
|
-
)}
|
|
1073
|
+
) : null}
|
|
1072
1074
|
</View>
|
|
1073
1075
|
|
|
1074
1076
|
{renderTransportControls(playerDimensions)}
|