@applicaster/quick-brick-player 13.0.0-rc.6 → 13.0.0-rc.60
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 +27 -11
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +103 -120
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +15 -13
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +16 -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 +19 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-player",
|
|
3
|
-
"version": "13.0.0-rc.
|
|
3
|
+
"version": "13.0.0-rc.60",
|
|
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": "
|
|
41
|
-
"@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.
|
|
42
|
-
"@applicaster/zapp-react-native-ui-components": "13.0.0-rc.
|
|
43
|
-
"@applicaster/zapp-react-native-utils": "13.0.0-rc.
|
|
39
|
+
"@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.55",
|
|
40
|
+
"@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
|
|
41
|
+
"@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.60",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-rc.60",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-rc.60",
|
|
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,28 @@
|
|
|
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": "*",
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
58
|
-
"react": "
|
|
59
|
-
"react-native": "
|
|
60
|
-
"
|
|
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": "*",
|
|
64
|
+
"immer": "*",
|
|
65
|
+
"react": "*",
|
|
66
|
+
"react-native": "*",
|
|
67
|
+
"react-native-fs": "*",
|
|
68
|
+
"react-native-gesture-handler": "*",
|
|
69
|
+
"react-native-linear-gradient": "*",
|
|
70
|
+
"react-native-safe-area-context": "*",
|
|
71
|
+
"react-native-svg": "*",
|
|
72
|
+
"react-native-web": "*",
|
|
73
|
+
"react-native-webview": "*",
|
|
74
|
+
"uglify-js": "*",
|
|
75
|
+
"validate-color": "*",
|
|
76
|
+
"zustand": "*"
|
|
61
77
|
}
|
|
62
78
|
}
|
|
@@ -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";
|
|
@@ -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,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,82 +212,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
189
212
|
if (!defaultDimensions) setDefaultDimensions({ width, height });
|
|
190
213
|
};
|
|
191
214
|
|
|
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
215
|
useEffect(() => {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (playerState.isReadyToPlay && speed) {
|
|
249
|
-
setPlaybackSpeed(speed);
|
|
250
|
-
player?.setPlaybackRate(speed.value);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
216
|
+
const sleepSubscription =
|
|
217
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
253
218
|
|
|
254
|
-
const
|
|
255
|
-
|
|
219
|
+
const speedSubscription =
|
|
220
|
+
speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
|
|
256
221
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
|
|
261
|
-
setSleepTimerDate(sleepTimerDate);
|
|
262
|
-
}
|
|
222
|
+
return () => {
|
|
223
|
+
sleepSubscription?.unsubscribe();
|
|
224
|
+
speedSubscription?.unsubscribe();
|
|
263
225
|
};
|
|
226
|
+
}, [sleepTimerManager, speedManager]);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!playerState.isReadyToPlay) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
264
232
|
|
|
265
|
-
loadSpeed()
|
|
266
|
-
loadSleep()
|
|
267
|
-
}, [playerState.isReadyToPlay]);
|
|
233
|
+
void speedManager?.loadSpeed();
|
|
234
|
+
void sleepTimerManager?.loadSleep();
|
|
235
|
+
}, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
|
|
268
236
|
|
|
269
237
|
const getValue = (key: string, stylesObj: {}) =>
|
|
270
238
|
R.propOr(null, key, stylesObj);
|
|
@@ -292,7 +260,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
292
260
|
|
|
293
261
|
const onPlaybackButtonPress = React.useCallback(() => {
|
|
294
262
|
if (playerState.isBuffering) return;
|
|
295
|
-
playerState.isPaused ? player?.play() : player
|
|
263
|
+
playerState.isPaused ? player?.play() : player?.pause();
|
|
296
264
|
}, [playerState, player]);
|
|
297
265
|
|
|
298
266
|
const seekDuration = toNumberWithDefault(10, value("seek_duration"));
|
|
@@ -307,26 +275,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
307
275
|
|
|
308
276
|
const onSpeedSelect = React.useCallback(
|
|
309
277
|
async (item) => {
|
|
310
|
-
setPlaybackSpeed(item);
|
|
311
|
-
player.setPlaybackRate(item.value);
|
|
312
|
-
await localStorage.setItem("speed", item, NAMESPACE);
|
|
278
|
+
await speedManager?.setPlaybackSpeed(item);
|
|
313
279
|
},
|
|
314
|
-
[
|
|
280
|
+
[speedManager]
|
|
315
281
|
);
|
|
316
282
|
|
|
317
283
|
const onSleepSelect = React.useCallback(
|
|
318
284
|
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
|
-
);
|
|
285
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
328
286
|
},
|
|
329
|
-
[
|
|
287
|
+
[sleepTimerManager]
|
|
330
288
|
);
|
|
331
289
|
|
|
332
290
|
const onSpeedButtonPress = React.useCallback(() => {
|
|
@@ -341,15 +299,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
341
299
|
supportedOrientations: ["portrait"],
|
|
342
300
|
},
|
|
343
301
|
});
|
|
344
|
-
}, [
|
|
302
|
+
}, [
|
|
303
|
+
playbackSpeed,
|
|
304
|
+
setPlaybackSpeed,
|
|
305
|
+
navigator,
|
|
306
|
+
onSpeedSelect,
|
|
307
|
+
configuration["playback_speed_title"],
|
|
308
|
+
]);
|
|
345
309
|
|
|
346
310
|
const onSleepButtonPress = React.useCallback(() => {
|
|
347
311
|
navigator.openModalBottomSheet({
|
|
348
312
|
modalBottomSheetContentProps: {
|
|
349
313
|
items: [
|
|
350
|
-
{
|
|
314
|
+
{
|
|
315
|
+
label: configuration["playback_sleep_off_label"],
|
|
316
|
+
value: 0,
|
|
317
|
+
},
|
|
351
318
|
].concat(sleeps),
|
|
352
|
-
current_selection:
|
|
319
|
+
current_selection: sleepTimerState.timer,
|
|
353
320
|
onPress: onSleepSelect,
|
|
354
321
|
title: configuration["playback_sleep_title"],
|
|
355
322
|
},
|
|
@@ -357,7 +324,21 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
357
324
|
supportedOrientations: ["portrait"],
|
|
358
325
|
},
|
|
359
326
|
});
|
|
360
|
-
}, [
|
|
327
|
+
}, [
|
|
328
|
+
navigator,
|
|
329
|
+
sleepTimerState.timer,
|
|
330
|
+
configuration["playback_sleep_title"],
|
|
331
|
+
configuration["playback_sleep_off_label"],
|
|
332
|
+
onSleepSelect,
|
|
333
|
+
]);
|
|
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]);
|
|
361
342
|
|
|
362
343
|
const onCloseVideoModal = React.useCallback(() => {
|
|
363
344
|
playerManager.close();
|
|
@@ -406,9 +387,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
406
387
|
onSleepButtonPress={onSleepButtonPress}
|
|
407
388
|
playbackSpeedName={formatSpeed(playbackSpeed.value)}
|
|
408
389
|
playbackSleepName={
|
|
409
|
-
|
|
390
|
+
sleepTimerState.timer.value === 0
|
|
391
|
+
? "sleep_timer"
|
|
392
|
+
: "sleep_timer_active"
|
|
410
393
|
}
|
|
411
|
-
sleepTimerDate={
|
|
394
|
+
sleepTimerDate={sleepTimerState.timerDate}
|
|
412
395
|
cancelSleepTimer={cancelSleepTimer}
|
|
413
396
|
onSkipLive={onSkipLive}
|
|
414
397
|
onCloseVideoModal={onCloseVideoModal}
|
|
@@ -458,12 +441,12 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
458
441
|
playNextData,
|
|
459
442
|
onSkipStart,
|
|
460
443
|
onSkipLive,
|
|
461
|
-
|
|
462
|
-
|
|
444
|
+
sleepTimerState.timer.value,
|
|
445
|
+
sleepTimerState.timerDate,
|
|
463
446
|
]);
|
|
464
447
|
|
|
465
448
|
if (configuration.magic_background) {
|
|
466
|
-
const baseUrl = entry
|
|
449
|
+
const baseUrl = imageSrcFromMediaItem(entry, null);
|
|
467
450
|
|
|
468
451
|
const queryParamName =
|
|
469
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";
|
|
@@ -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: {
|
|
@@ -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,9 +406,10 @@ 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
|
|
412
|
+
name={"audio-default-layout"}
|
|
411
413
|
content={entry}
|
|
412
414
|
value={value}
|
|
413
415
|
visible={true}
|
|
@@ -422,17 +424,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
422
424
|
onSkipLive={onSkipLive}
|
|
423
425
|
/>
|
|
424
426
|
|
|
425
|
-
{showCounter && timeLeft >= 0
|
|
427
|
+
{showCounter && timeLeft >= 0 ? (
|
|
426
428
|
<CountDown
|
|
427
429
|
timeLeft={timeLeft}
|
|
428
430
|
value={value}
|
|
429
431
|
title={sleepTitle}
|
|
430
432
|
/>
|
|
431
|
-
)}
|
|
433
|
+
) : null}
|
|
432
434
|
|
|
433
435
|
<Separator />
|
|
434
436
|
</View>
|
|
435
|
-
)}
|
|
437
|
+
) : null}
|
|
436
438
|
<Controls
|
|
437
439
|
value={value}
|
|
438
440
|
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,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
|
|
@@ -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,14 +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}
|
|
1003
1004
|
listener={this.props.listener}
|
|
1004
1005
|
/>
|
|
1005
|
-
)}
|
|
1006
|
+
) : null}
|
|
1006
1007
|
<AudioPlayerWrapper {...sharedWrapperViewProps} />
|
|
1007
1008
|
</View>
|
|
1008
1009
|
) : (
|
|
@@ -1017,7 +1018,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1017
1018
|
return (
|
|
1018
1019
|
<View style={styles.container}>
|
|
1019
1020
|
<View style={[playerContainerStyles, playerDimensions]}>
|
|
1020
|
-
{isAudioItem
|
|
1021
|
+
{isAudioItem ? (
|
|
1021
1022
|
<AnimationComponent
|
|
1022
1023
|
animationType={ComponentAnimationType.player}
|
|
1023
1024
|
additionalData={{
|
|
@@ -1036,18 +1037,18 @@ export default class VideoPlayer extends React.Component<
|
|
|
1036
1037
|
plugin_configuration={this.getPluginConfiguration()}
|
|
1037
1038
|
/>
|
|
1038
1039
|
</AnimationComponent>
|
|
1039
|
-
)}
|
|
1040
|
+
) : null}
|
|
1040
1041
|
|
|
1041
|
-
{shouldRenderBackground
|
|
1042
|
+
{shouldRenderBackground ? (
|
|
1042
1043
|
<PlayerImageBackground
|
|
1043
1044
|
imageKey={backgroundImageKey}
|
|
1044
1045
|
configuration={this.getPluginConfiguration()}
|
|
1045
1046
|
entry={entry}
|
|
1046
1047
|
style={getBackgroundImageStyle(playerDimensions)}
|
|
1047
1048
|
/>
|
|
1048
|
-
)}
|
|
1049
|
+
) : null}
|
|
1049
1050
|
|
|
1050
|
-
{
|
|
1051
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
1051
1052
|
<AnimationComponent
|
|
1052
1053
|
animationType={ComponentAnimationType.player}
|
|
1053
1054
|
additionalData={playerDimensions}
|
|
@@ -1069,7 +1070,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1069
1070
|
{this.childrenWithPlayNext()}
|
|
1070
1071
|
</this.props.PlayerComponent>
|
|
1071
1072
|
</AnimationComponent>
|
|
1072
|
-
)}
|
|
1073
|
+
) : null}
|
|
1073
1074
|
</View>
|
|
1074
1075
|
|
|
1075
1076
|
{renderTransportControls(playerDimensions)}
|