@applicaster/quick-brick-player 13.0.0-alpha.6525006318 → 13.0.0-alpha.6651373134
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 -12
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +115 -124
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +85 -42
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +19 -31
- package/src/Player/AudioLayer/Layout/defaultLayoutsConts.ts +3 -3
- package/src/Player/AudioLayer/MagicBG/index.tsx +1 -1
- package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
- package/src/Player/AudioLayer/SpeedManager.ts +52 -0
- package/src/Player/AudioLayer/utils.ts +24 -0
- package/src/Player/index.tsx +29 -24
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-player",
|
|
3
|
-
"version": "13.0.0-alpha.
|
|
3
|
+
"version": "13.0.0-alpha.6651373134",
|
|
4
4
|
"description": "Quick Brick Player",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "jest -w=2",
|
|
9
8
|
"build": "echo 'skipping ts build for core packages'"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
@@ -36,11 +35,11 @@
|
|
|
36
35
|
},
|
|
37
36
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
38
37
|
"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.
|
|
38
|
+
"@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.79",
|
|
39
|
+
"@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
|
|
40
|
+
"@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.6651373134",
|
|
41
|
+
"@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.6651373134",
|
|
42
|
+
"@applicaster/zapp-react-native-utils": "13.0.0-alpha.6651373134",
|
|
44
43
|
"keymirror": "^0.1.1",
|
|
45
44
|
"query-string": "7.1.3",
|
|
46
45
|
"react-native-web-linear-gradient": "1.1.2",
|
|
@@ -51,12 +50,28 @@
|
|
|
51
50
|
"videojs-contrib-quality-levels": "3.0.0"
|
|
52
51
|
},
|
|
53
52
|
"peerDependencies": {
|
|
53
|
+
"@applicaster/applicaster-types": "*",
|
|
54
|
+
"@applicaster/blur": "*",
|
|
54
55
|
"@applicaster/quick-brick-xray": "*",
|
|
55
56
|
"@applicaster/react-native-color-thief": "*",
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
58
|
-
"react": "
|
|
59
|
-
"react-native": "
|
|
60
|
-
"
|
|
57
|
+
"@applicaster/zapp-pipes-v2-client": "*",
|
|
58
|
+
"@applicaster/zapp-react-dom-ui-components": "*",
|
|
59
|
+
"@applicaster/zapp-react-native-redux": "*",
|
|
60
|
+
"@applicaster/zapp-react-native-tvos-ui-components": "*",
|
|
61
|
+
"@babel/runtime": "*",
|
|
62
|
+
"@react-native-community/netinfo": "*",
|
|
63
|
+
"immer": "*",
|
|
64
|
+
"react": "*",
|
|
65
|
+
"react-native": "*",
|
|
66
|
+
"react-native-fs": "*",
|
|
67
|
+
"react-native-gesture-handler": "*",
|
|
68
|
+
"react-native-linear-gradient": "*",
|
|
69
|
+
"react-native-safe-area-context": "*",
|
|
70
|
+
"react-native-svg": "*",
|
|
71
|
+
"react-native-web": "*",
|
|
72
|
+
"react-native-webview": "*",
|
|
73
|
+
"uglify-js": "*",
|
|
74
|
+
"validate-color": "*",
|
|
75
|
+
"zustand": "*"
|
|
61
76
|
}
|
|
62
77
|
}
|
|
@@ -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,21 @@ 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";
|
|
44
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
36
45
|
|
|
37
46
|
const { log_debug } = createLogger({
|
|
38
47
|
subsystem: "UIComponent",
|
|
@@ -50,6 +59,7 @@ type Configuration = {
|
|
|
50
59
|
[key: string]: any;
|
|
51
60
|
magic_background?: boolean;
|
|
52
61
|
audio_player_background_image_query?: string;
|
|
62
|
+
audio_player_background_image_default_color?: string;
|
|
53
63
|
tablet_landscape_sidebar_width?: string;
|
|
54
64
|
tablet_landscape_player_container_background_color?: string;
|
|
55
65
|
};
|
|
@@ -71,8 +81,7 @@ type Props = {
|
|
|
71
81
|
isTabletPortrait?: boolean;
|
|
72
82
|
};
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
(height / 9) * 16;
|
|
84
|
+
const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
|
|
76
85
|
|
|
77
86
|
const Styles = {
|
|
78
87
|
mainContainer: (style: ViewStyle) => {
|
|
@@ -98,7 +107,11 @@ const Styles = {
|
|
|
98
107
|
height: 96,
|
|
99
108
|
marginHorizontal: 24,
|
|
100
109
|
},
|
|
101
|
-
playerDetails: {
|
|
110
|
+
playerDetails: {
|
|
111
|
+
marginTop: 60,
|
|
112
|
+
alignSelf: "center",
|
|
113
|
+
width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
|
|
114
|
+
},
|
|
102
115
|
|
|
103
116
|
linearGradient: {
|
|
104
117
|
flex: 1,
|
|
@@ -116,9 +129,12 @@ const sleeps = [
|
|
|
116
129
|
{ label: "2 hours", value: 120 },
|
|
117
130
|
];
|
|
118
131
|
|
|
132
|
+
const edges: Edge[] = ["right", "left", "top"];
|
|
133
|
+
|
|
119
134
|
export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
120
135
|
const player = usePlayer();
|
|
121
136
|
const playerState = usePlayerState("audio-player-wrapper");
|
|
137
|
+
const theme = useTheme();
|
|
122
138
|
|
|
123
139
|
const {
|
|
124
140
|
docked,
|
|
@@ -132,11 +148,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
132
148
|
} = props;
|
|
133
149
|
|
|
134
150
|
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 },
|
|
151
|
+
{ asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
|
|
152
|
+
{ asset: configuration["speed_1"] || speed_1, value: 1.0 },
|
|
153
|
+
{ asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
|
|
154
|
+
{ asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
|
|
155
|
+
{ asset: configuration["speed_2"] || speed_2, value: 2.0 },
|
|
140
156
|
];
|
|
141
157
|
|
|
142
158
|
const isTablet = useIsTablet();
|
|
@@ -154,17 +170,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
154
170
|
configuration?.audio_live_player_update_interval
|
|
155
171
|
);
|
|
156
172
|
|
|
157
|
-
const [
|
|
158
|
-
|
|
159
|
-
value: 1.0,
|
|
160
|
-
});
|
|
173
|
+
const [sleepTimerManager, setSleepTimerManager] =
|
|
174
|
+
useState<SleepTimerController | null>(null);
|
|
161
175
|
|
|
162
|
-
const [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
const [speedManager, setSpeedManager] = useState<SpeedController | null>(
|
|
177
|
+
null
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (player) {
|
|
182
|
+
setSleepTimerManager(new SleepTimerController(player));
|
|
183
|
+
setSpeedManager(new SpeedController(player));
|
|
184
|
+
} else {
|
|
185
|
+
setSleepTimerManager(null);
|
|
186
|
+
setSpeedManager(null);
|
|
187
|
+
}
|
|
188
|
+
}, [player]);
|
|
189
|
+
|
|
190
|
+
const [playbackSpeed, setPlaybackSpeed] =
|
|
191
|
+
useState<PlaybackSpeed>(DEFAULT_SPEED);
|
|
166
192
|
|
|
167
|
-
const [
|
|
193
|
+
const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
|
|
194
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
195
|
+
);
|
|
168
196
|
|
|
169
197
|
const [defaultDimensions, setDefaultDimensions] =
|
|
170
198
|
React.useState<Dimensions>(undefined);
|
|
@@ -189,82 +217,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
189
217
|
if (!defaultDimensions) setDefaultDimensions({ width, height });
|
|
190
218
|
};
|
|
191
219
|
|
|
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
220
|
useEffect(() => {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (playerState.isReadyToPlay && speed) {
|
|
249
|
-
setPlaybackSpeed(speed);
|
|
250
|
-
player?.setPlaybackRate(speed.value);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
221
|
+
const sleepSubscription =
|
|
222
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
253
223
|
|
|
254
|
-
const
|
|
255
|
-
|
|
224
|
+
const speedSubscription =
|
|
225
|
+
speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
|
|
256
226
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
|
|
261
|
-
setSleepTimerDate(sleepTimerDate);
|
|
262
|
-
}
|
|
227
|
+
return () => {
|
|
228
|
+
sleepSubscription?.unsubscribe();
|
|
229
|
+
speedSubscription?.unsubscribe();
|
|
263
230
|
};
|
|
231
|
+
}, [sleepTimerManager, speedManager]);
|
|
232
|
+
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!playerState.isReadyToPlay) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
264
237
|
|
|
265
|
-
loadSpeed()
|
|
266
|
-
loadSleep()
|
|
267
|
-
}, [playerState.isReadyToPlay]);
|
|
238
|
+
void speedManager?.loadSpeed();
|
|
239
|
+
void sleepTimerManager?.loadSleep();
|
|
240
|
+
}, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
|
|
268
241
|
|
|
269
242
|
const getValue = (key: string, stylesObj: {}) =>
|
|
270
243
|
R.propOr(null, key, stylesObj);
|
|
@@ -292,7 +265,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
292
265
|
|
|
293
266
|
const onPlaybackButtonPress = React.useCallback(() => {
|
|
294
267
|
if (playerState.isBuffering) return;
|
|
295
|
-
playerState.isPaused ? player?.play() : player
|
|
268
|
+
playerState.isPaused ? player?.play() : player?.pause();
|
|
296
269
|
}, [playerState, player]);
|
|
297
270
|
|
|
298
271
|
const seekDuration = toNumberWithDefault(10, value("seek_duration"));
|
|
@@ -307,26 +280,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
307
280
|
|
|
308
281
|
const onSpeedSelect = React.useCallback(
|
|
309
282
|
async (item) => {
|
|
310
|
-
setPlaybackSpeed(item);
|
|
311
|
-
player.setPlaybackRate(item.value);
|
|
312
|
-
await localStorage.setItem("speed", item, NAMESPACE);
|
|
283
|
+
await speedManager?.setPlaybackSpeed(item);
|
|
313
284
|
},
|
|
314
|
-
[
|
|
285
|
+
[speedManager]
|
|
315
286
|
);
|
|
316
287
|
|
|
317
288
|
const onSleepSelect = React.useCallback(
|
|
318
289
|
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
|
-
);
|
|
290
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
328
291
|
},
|
|
329
|
-
[
|
|
292
|
+
[sleepTimerManager]
|
|
330
293
|
);
|
|
331
294
|
|
|
332
295
|
const onSpeedButtonPress = React.useCallback(() => {
|
|
@@ -341,15 +304,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
341
304
|
supportedOrientations: ["portrait"],
|
|
342
305
|
},
|
|
343
306
|
});
|
|
344
|
-
}, [
|
|
307
|
+
}, [
|
|
308
|
+
playbackSpeed,
|
|
309
|
+
setPlaybackSpeed,
|
|
310
|
+
navigator,
|
|
311
|
+
onSpeedSelect,
|
|
312
|
+
configuration["playback_speed_title"],
|
|
313
|
+
]);
|
|
345
314
|
|
|
346
315
|
const onSleepButtonPress = React.useCallback(() => {
|
|
347
316
|
navigator.openModalBottomSheet({
|
|
348
317
|
modalBottomSheetContentProps: {
|
|
349
318
|
items: [
|
|
350
|
-
{
|
|
319
|
+
{
|
|
320
|
+
label: configuration["playback_sleep_off_label"],
|
|
321
|
+
value: 0,
|
|
322
|
+
},
|
|
351
323
|
].concat(sleeps),
|
|
352
|
-
current_selection:
|
|
324
|
+
current_selection: sleepTimerState.timer,
|
|
353
325
|
onPress: onSleepSelect,
|
|
354
326
|
title: configuration["playback_sleep_title"],
|
|
355
327
|
},
|
|
@@ -357,7 +329,21 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
357
329
|
supportedOrientations: ["portrait"],
|
|
358
330
|
},
|
|
359
331
|
});
|
|
360
|
-
}, [
|
|
332
|
+
}, [
|
|
333
|
+
navigator,
|
|
334
|
+
sleepTimerState.timer,
|
|
335
|
+
configuration["playback_sleep_title"],
|
|
336
|
+
configuration["playback_sleep_off_label"],
|
|
337
|
+
onSleepSelect,
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
// This method does not actually cancel the sleep timer
|
|
341
|
+
// (and should not to avoid race condition),
|
|
342
|
+
// it just performs the cleanup when timer expires
|
|
343
|
+
// TODO: rename it in code above
|
|
344
|
+
const cancelSleepTimer = React.useCallback(() => {
|
|
345
|
+
sleepTimerManager?.onTimerExpired();
|
|
346
|
+
}, [sleepTimerManager]);
|
|
361
347
|
|
|
362
348
|
const onCloseVideoModal = React.useCallback(() => {
|
|
363
349
|
playerManager.close();
|
|
@@ -378,8 +364,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
378
364
|
player?.seekTo(0);
|
|
379
365
|
}, [player]);
|
|
380
366
|
|
|
381
|
-
const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
|
|
382
|
-
|
|
383
367
|
const ContainerView = SafeAreaView;
|
|
384
368
|
|
|
385
369
|
const MainContent = React.useMemo(() => {
|
|
@@ -406,9 +390,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
406
390
|
onSleepButtonPress={onSleepButtonPress}
|
|
407
391
|
playbackSpeedName={formatSpeed(playbackSpeed.value)}
|
|
408
392
|
playbackSleepName={
|
|
409
|
-
|
|
393
|
+
sleepTimerState.timer.value === 0
|
|
394
|
+
? "sleep_timer"
|
|
395
|
+
: "sleep_timer_active"
|
|
410
396
|
}
|
|
411
|
-
sleepTimerDate={
|
|
397
|
+
sleepTimerDate={sleepTimerState.timerDate}
|
|
412
398
|
cancelSleepTimer={cancelSleepTimer}
|
|
413
399
|
onSkipLive={onSkipLive}
|
|
414
400
|
onCloseVideoModal={onCloseVideoModal}
|
|
@@ -458,26 +444,31 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
458
444
|
playNextData,
|
|
459
445
|
onSkipStart,
|
|
460
446
|
onSkipLive,
|
|
461
|
-
|
|
462
|
-
|
|
447
|
+
sleepTimerState.timer.value,
|
|
448
|
+
sleepTimerState.timerDate,
|
|
463
449
|
]);
|
|
464
450
|
|
|
465
451
|
if (configuration.magic_background) {
|
|
466
|
-
const baseUrl = entry
|
|
452
|
+
const baseUrl = imageSrcFromMediaItem(entry, null);
|
|
467
453
|
|
|
468
454
|
const queryParamName =
|
|
469
455
|
configuration.audio_player_background_image_query || "width";
|
|
470
456
|
|
|
457
|
+
const defaultBackgroundColor =
|
|
458
|
+
configuration.audio_player_background_image_default_color ||
|
|
459
|
+
theme?.app_background_color;
|
|
460
|
+
|
|
471
461
|
const queryParamValue = 15;
|
|
472
462
|
|
|
473
|
-
const url =
|
|
463
|
+
const url =
|
|
464
|
+
baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
|
|
474
465
|
|
|
475
466
|
return (
|
|
476
467
|
<MagicBG
|
|
477
468
|
image={url}
|
|
478
469
|
fallbackUrl={baseUrl}
|
|
479
470
|
colors={entry?.extensions?.backgroundColors}
|
|
480
|
-
defaultColor=
|
|
471
|
+
defaultColor={defaultBackgroundColor}
|
|
481
472
|
>
|
|
482
473
|
{MainContent}
|
|
483
474
|
</MagicBG>
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
3
|
import * as R from "ramda";
|
|
3
|
-
import { View } from "react-native";
|
|
4
|
+
import { View, Dimensions as ScreenDimensions } from "react-native";
|
|
4
5
|
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
|
-
ProgressBar,
|
|
8
8
|
Controls,
|
|
9
|
-
TransportInfo,
|
|
10
9
|
CountDown,
|
|
10
|
+
ProgressBar,
|
|
11
|
+
TransportInfo,
|
|
11
12
|
} from "@applicaster/quick-brick-mobile-transport-controls";
|
|
12
13
|
|
|
13
14
|
import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
@@ -15,26 +16,30 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
|
15
16
|
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
16
17
|
import {
|
|
17
18
|
forwardButton,
|
|
19
|
+
PADDING_HORIZONTAL,
|
|
18
20
|
playbackButton,
|
|
19
21
|
rewindButton,
|
|
20
|
-
skipToStartButton,
|
|
21
22
|
skipToLiveButton,
|
|
22
|
-
|
|
23
|
-
speedButton,
|
|
23
|
+
skipToStartButton,
|
|
24
24
|
sleepButton,
|
|
25
|
+
speedButton,
|
|
25
26
|
} from "./defaultLayoutsConts";
|
|
26
27
|
import { AudioDescription } from "../AudioDescription";
|
|
27
28
|
import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
|
|
28
29
|
import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
|
|
29
30
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
30
|
-
import { useEffect, useMemo, useState } from "react";
|
|
31
31
|
import {
|
|
32
|
-
useModalAnimationContext,
|
|
33
32
|
AnimationComponent,
|
|
34
33
|
ComponentAnimationType,
|
|
34
|
+
useModalAnimationContext,
|
|
35
35
|
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
36
|
+
import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
|
|
36
37
|
import DockedLayout from "./DockedLayout";
|
|
37
38
|
import { isValidDate } from "../../Utils";
|
|
39
|
+
import { calculateBorderRadius } from "../utils";
|
|
40
|
+
|
|
41
|
+
const getAudioPlayerWidthForTablet = (isPortraitMode: boolean) =>
|
|
42
|
+
isPortraitMode ? 463 : 350;
|
|
38
43
|
|
|
39
44
|
type Dimensions = {
|
|
40
45
|
width: number;
|
|
@@ -79,6 +84,9 @@ type LayoutProps = {
|
|
|
79
84
|
defaultDimensions: Dimensions;
|
|
80
85
|
};
|
|
81
86
|
|
|
87
|
+
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } =
|
|
88
|
+
ScreenDimensions.get("screen");
|
|
89
|
+
|
|
82
90
|
const Styles = {
|
|
83
91
|
container: { flex: 1 },
|
|
84
92
|
tabletContainer: (isPortraitMode: boolean) => ({
|
|
@@ -86,42 +94,63 @@ const Styles = {
|
|
|
86
94
|
alignItems: "center",
|
|
87
95
|
justifyContent: isPortraitMode ? "center" : "flex-start",
|
|
88
96
|
}),
|
|
89
|
-
playerImageWrapper: {
|
|
97
|
+
playerImageWrapper: (docked: boolean) => ({
|
|
90
98
|
width: "100%",
|
|
91
99
|
maxHeight: 750,
|
|
92
|
-
marginTop: 80,
|
|
100
|
+
marginTop: docked ? 80 : 20,
|
|
101
|
+
}),
|
|
102
|
+
playerInfoWrapper: {
|
|
103
|
+
flex: 1,
|
|
104
|
+
alignItems: "center",
|
|
105
|
+
justifyContent: "space-between",
|
|
93
106
|
},
|
|
94
|
-
tabletLayout: {
|
|
95
|
-
width:
|
|
107
|
+
tabletLayout: (isPortraitMode: boolean) => ({
|
|
108
|
+
width: getAudioPlayerWidthForTablet(isPortraitMode),
|
|
96
109
|
marginBottom: 80,
|
|
97
|
-
|
|
110
|
+
paddingHorizontal: undefined, // override paddingHorizontal
|
|
111
|
+
}),
|
|
98
112
|
controls: (isTablet: boolean, isLiveShowFullControls: boolean) => ({
|
|
99
113
|
height: isTablet ? 64 : 50,
|
|
100
|
-
marginHorizontal: 10,
|
|
114
|
+
marginHorizontal: isTablet ? undefined : 10,
|
|
101
115
|
justifyContent: isLiveShowFullControls ? "space-between" : "space-evenly",
|
|
102
116
|
}),
|
|
103
|
-
backgroundImageContainer: (
|
|
104
|
-
|
|
117
|
+
backgroundImageContainer: (
|
|
118
|
+
dimensions: Dimensions,
|
|
119
|
+
isTablet: boolean,
|
|
120
|
+
isPortraitMode: boolean
|
|
121
|
+
) => {
|
|
122
|
+
if (isTablet) {
|
|
123
|
+
const size = getAudioPlayerWidthForTablet(isPortraitMode);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
width: size,
|
|
127
|
+
height: size,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const mobileValue = dimensions?.width
|
|
105
132
|
? dimensions.width - PADDING_HORIZONTAL * 2
|
|
106
|
-
:
|
|
133
|
+
: SCREEN_WIDTH - PADDING_HORIZONTAL * 2;
|
|
107
134
|
|
|
108
135
|
return {
|
|
109
|
-
width:
|
|
110
|
-
height:
|
|
136
|
+
width: mobileValue,
|
|
137
|
+
height: mobileValue,
|
|
111
138
|
};
|
|
112
139
|
},
|
|
113
140
|
backgroundImageContainerImageStyle: (
|
|
114
141
|
isTablet: boolean,
|
|
115
142
|
docked: boolean,
|
|
116
143
|
startComponentsAnimation: boolean,
|
|
117
|
-
isActiveGesture: boolean
|
|
144
|
+
isActiveGesture: boolean,
|
|
145
|
+
playerAnimationState: PlayerAnimationStateT
|
|
118
146
|
) => ({
|
|
119
|
-
borderRadius:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
147
|
+
borderRadius: calculateBorderRadius(
|
|
148
|
+
isTablet,
|
|
149
|
+
docked,
|
|
150
|
+
startComponentsAnimation,
|
|
151
|
+
isActiveGesture,
|
|
152
|
+
playerAnimationState
|
|
153
|
+
),
|
|
125
154
|
}),
|
|
126
155
|
|
|
127
156
|
paddingHorizontal: {
|
|
@@ -173,8 +202,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
173
202
|
} = props;
|
|
174
203
|
|
|
175
204
|
const isTablet = useIsTablet();
|
|
176
|
-
const
|
|
177
|
-
const isPortraitMode = width < height;
|
|
205
|
+
const isPortraitMode = SCREEN_WIDTH < SCREEN_HEIGHT;
|
|
178
206
|
const isLiveContent = playerState.isLive;
|
|
179
207
|
const docked = layoutState.docked;
|
|
180
208
|
|
|
@@ -330,12 +358,15 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
330
358
|
|
|
331
359
|
const defaultImageDimensions = React.useMemo(
|
|
332
360
|
() =>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
361
|
+
Styles.backgroundImageContainer(
|
|
362
|
+
defaultDimensions,
|
|
363
|
+
isTablet,
|
|
364
|
+
isPortraitMode
|
|
365
|
+
),
|
|
366
|
+
[defaultDimensions, isTablet, isPortraitMode]
|
|
336
367
|
);
|
|
337
368
|
|
|
338
|
-
const { isActiveGesture, startComponentsAnimation } =
|
|
369
|
+
const { isActiveGesture, startComponentsAnimation, playerAnimationState } =
|
|
339
370
|
useModalAnimationContext();
|
|
340
371
|
|
|
341
372
|
return (
|
|
@@ -344,7 +375,10 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
344
375
|
isTablet ? Styles.tabletContainer(isPortraitMode) : Styles.container
|
|
345
376
|
}
|
|
346
377
|
>
|
|
347
|
-
<AnimationComponent
|
|
378
|
+
<AnimationComponent
|
|
379
|
+
animationType={ComponentAnimationType.componentFade}
|
|
380
|
+
style={isTablet ? Styles.playerInfoWrapper : undefined}
|
|
381
|
+
>
|
|
348
382
|
<TransportInfo
|
|
349
383
|
content={entry}
|
|
350
384
|
value={value}
|
|
@@ -356,13 +390,16 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
356
390
|
</AnimationComponent>
|
|
357
391
|
<AnimationComponent
|
|
358
392
|
animationType={ComponentAnimationType.moveUpComponent}
|
|
359
|
-
style={isTablet ? Styles.playerImageWrapper : undefined}
|
|
393
|
+
style={isTablet ? Styles.playerImageWrapper(docked) : undefined}
|
|
360
394
|
>
|
|
361
395
|
<AnimationComponent
|
|
362
396
|
animationType={ComponentAnimationType.moveComponentHorizontal}
|
|
363
397
|
additionalData={{
|
|
364
398
|
disableAnimatedComponent: !isTablet,
|
|
365
|
-
moveValue:
|
|
399
|
+
moveValue:
|
|
400
|
+
(dimensions.width -
|
|
401
|
+
getAudioPlayerWidthForTablet(isPortraitMode)) /
|
|
402
|
+
2,
|
|
366
403
|
extraAnimation: !isTablet
|
|
367
404
|
? {
|
|
368
405
|
[ComponentAnimationType.audioPlayerPaddingHorizontal]: {
|
|
@@ -375,12 +412,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
375
412
|
>
|
|
376
413
|
<PlayerImageBackground
|
|
377
414
|
entry={entry}
|
|
378
|
-
style={Styles.backgroundImageContainer(
|
|
415
|
+
style={Styles.backgroundImageContainer(
|
|
416
|
+
dimensions,
|
|
417
|
+
isTablet,
|
|
418
|
+
isPortraitMode
|
|
419
|
+
)}
|
|
379
420
|
imageStyle={Styles.backgroundImageContainerImageStyle(
|
|
380
421
|
isTablet,
|
|
381
422
|
docked,
|
|
382
423
|
startComponentsAnimation,
|
|
383
|
-
isActiveGesture
|
|
424
|
+
isActiveGesture,
|
|
425
|
+
playerAnimationState
|
|
384
426
|
)}
|
|
385
427
|
defaultImageDimensions={defaultImageDimensions}
|
|
386
428
|
/>
|
|
@@ -391,7 +433,7 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
391
433
|
<View
|
|
392
434
|
style={[
|
|
393
435
|
Styles.paddingHorizontal,
|
|
394
|
-
isTablet ? Styles.tabletLayout : undefined,
|
|
436
|
+
isTablet ? Styles.tabletLayout(isPortraitMode) : undefined,
|
|
395
437
|
]}
|
|
396
438
|
>
|
|
397
439
|
{!docked && (
|
|
@@ -405,9 +447,10 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
405
447
|
layoutState={layoutState}
|
|
406
448
|
/>
|
|
407
449
|
<Separator />
|
|
408
|
-
{needsToShowProgressBar
|
|
450
|
+
{needsToShowProgressBar ? (
|
|
409
451
|
<View>
|
|
410
452
|
<ProgressBar
|
|
453
|
+
name={"audio-default-layout"}
|
|
411
454
|
content={entry}
|
|
412
455
|
value={value}
|
|
413
456
|
visible={true}
|
|
@@ -422,17 +465,17 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
422
465
|
onSkipLive={onSkipLive}
|
|
423
466
|
/>
|
|
424
467
|
|
|
425
|
-
{showCounter && timeLeft >= 0
|
|
468
|
+
{showCounter && timeLeft >= 0 ? (
|
|
426
469
|
<CountDown
|
|
427
470
|
timeLeft={timeLeft}
|
|
428
471
|
value={value}
|
|
429
472
|
title={sleepTitle}
|
|
430
473
|
/>
|
|
431
|
-
)}
|
|
474
|
+
) : null}
|
|
432
475
|
|
|
433
476
|
<Separator />
|
|
434
477
|
</View>
|
|
435
|
-
)}
|
|
478
|
+
) : null}
|
|
436
479
|
<Controls
|
|
437
480
|
value={value}
|
|
438
481
|
visible={true}
|
|
@@ -9,18 +9,19 @@ 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,
|
|
22
|
+
defaultAspectRatioWidth,
|
|
23
23
|
PlayerAnimationStateEnum,
|
|
24
|
+
useModalAnimationContext,
|
|
24
25
|
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
25
26
|
|
|
26
27
|
type Dimensions = {
|
|
@@ -113,7 +114,7 @@ const playerDimensionsStyle = (
|
|
|
113
114
|
minimisedHeight: number,
|
|
114
115
|
addPadding: boolean
|
|
115
116
|
): ViewStyle => ({
|
|
116
|
-
paddingLeft: addPadding ? minimisedHeight || 100 : 0,
|
|
117
|
+
paddingLeft: addPadding ? defaultAspectRatioWidth(minimisedHeight) || 100 : 0,
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
const animatedContainerStyle = (docked: boolean): ViewStyle => ({
|
|
@@ -133,24 +134,11 @@ const backgroundImageStyle = (
|
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
const controlButtons = (
|
|
136
|
-
isPlayNextEnabled: boolean,
|
|
137
137
|
live: boolean,
|
|
138
138
|
paused: boolean,
|
|
139
139
|
buffering: boolean,
|
|
140
140
|
playerId: string
|
|
141
141
|
): Array<ControlsButton> => {
|
|
142
|
-
if (isPlayNextEnabled) {
|
|
143
|
-
return [
|
|
144
|
-
{
|
|
145
|
-
size: "medium",
|
|
146
|
-
name: "play_next",
|
|
147
|
-
icon: "play_next",
|
|
148
|
-
preferredFocus: true,
|
|
149
|
-
style: styles.buttons,
|
|
150
|
-
},
|
|
151
|
-
];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
142
|
if (buffering) {
|
|
155
143
|
return [
|
|
156
144
|
{
|
|
@@ -230,7 +218,7 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
230
218
|
} = useModalAnimationContext();
|
|
231
219
|
|
|
232
220
|
const maximazeVideoModal = React.useCallback(
|
|
233
|
-
() => setPlayerAnimationState(PlayerAnimationStateEnum.
|
|
221
|
+
() => setPlayerAnimationState(PlayerAnimationStateEnum.maximize),
|
|
234
222
|
[]
|
|
235
223
|
);
|
|
236
224
|
|
|
@@ -245,18 +233,19 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
245
233
|
]}
|
|
246
234
|
>
|
|
247
235
|
{!playerState.isLive &&
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
236
|
+
(layoutState.docked || startComponentsAnimation) ? (
|
|
237
|
+
<ProgressBar
|
|
238
|
+
name={"audio-docked-layout"}
|
|
239
|
+
content={entry}
|
|
240
|
+
value={value}
|
|
241
|
+
visible={visible}
|
|
242
|
+
layoutState={layoutState}
|
|
243
|
+
onPlayerSeek={onPlayerSeek}
|
|
244
|
+
playerState={playerState}
|
|
245
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
246
|
+
docked
|
|
247
|
+
/>
|
|
248
|
+
) : null}
|
|
260
249
|
|
|
261
250
|
<View style={styles.content}>
|
|
262
251
|
<View
|
|
@@ -302,7 +291,6 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
302
291
|
value={value}
|
|
303
292
|
visible={visible}
|
|
304
293
|
buttons={R.partial(controlButtons, [
|
|
305
|
-
playNextData,
|
|
306
294
|
playerState.isLive && liveButton,
|
|
307
295
|
])}
|
|
308
296
|
style={styles.playPauseButton}
|
|
@@ -17,7 +17,7 @@ const playbackButtonMargins = (
|
|
|
17
17
|
isTablet: boolean,
|
|
18
18
|
isLiveShowFullControls: boolean
|
|
19
19
|
): number =>
|
|
20
|
-
isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ?
|
|
20
|
+
isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ? undefined : 40;
|
|
21
21
|
|
|
22
22
|
export const PADDING_HORIZONTAL = 15;
|
|
23
23
|
|
|
@@ -37,8 +37,8 @@ export const playbackButton = ({
|
|
|
37
37
|
const icon = playerState.isPaused
|
|
38
38
|
? "play"
|
|
39
39
|
: playerState.isLive && liveButton
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
? "stop"
|
|
41
|
+
: "pause";
|
|
42
42
|
|
|
43
43
|
return {
|
|
44
44
|
size: playbackButtonSize(isTablet),
|
|
@@ -47,7 +47,7 @@ const fetchPaletteAndDominantColor = async (imageUrl: string) => {
|
|
|
47
47
|
const dominant = await getDominantColor(imageUrl);
|
|
48
48
|
const dominantColor = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
|
|
49
49
|
|
|
50
|
-
return { gradientColors: colors.slice(0, 2), dominantColor };
|
|
50
|
+
return { gradientColors: colors.slice(0, 2).reverse(), dominantColor };
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
export const MagicBG: React.FC<MagicBGProps> = ({
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
import { hasDatePassed, NAMESPACE } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { log_debug, log_error } = createLogger({
|
|
8
|
+
subsystem: "Player",
|
|
9
|
+
category: "SleepTimerManager",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type SleepTimer = {
|
|
13
|
+
label: string;
|
|
14
|
+
// interval in minutes for UI at the moment when timer was first started
|
|
15
|
+
value: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SLEEP_TIMER_OFF: SleepTimer = {
|
|
19
|
+
label: "playback_sleep_off_label",
|
|
20
|
+
value: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class SleepTimerState {
|
|
24
|
+
constructor(
|
|
25
|
+
public timer: SleepTimer,
|
|
26
|
+
public timerDate: number
|
|
27
|
+
) {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SLEEP_PREFERENCE_KEY = "sleep";
|
|
31
|
+
|
|
32
|
+
export class SleepTimerController {
|
|
33
|
+
private sleepTimerStateSubject = new BehaviorSubject<SleepTimerState>(
|
|
34
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
constructor(private player: Player) {}
|
|
38
|
+
|
|
39
|
+
get sleepTimerState$() {
|
|
40
|
+
return this.sleepTimerStateSubject.asObservable();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async scheduleSleep(sleepDate): Promise<boolean> {
|
|
44
|
+
log_debug(`Setting sleep interval to ${sleepDate}`);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.player.cancelSleepTimer?.();
|
|
48
|
+
this.player.startSleepTimer?.(sleepDate);
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
log_error("Failed to start sleep timer", { error: e.message });
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async setSleepTimer(item: SleepTimer) {
|
|
59
|
+
// User has selected Off state
|
|
60
|
+
if (item.value === 0) {
|
|
61
|
+
log_debug("Cancelling sleep timer");
|
|
62
|
+
this.player.cancelSleepTimer?.();
|
|
63
|
+
|
|
64
|
+
return this.onTimerExpired();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log_debug(`Starting sleep timer in ${item.value} minutes`);
|
|
68
|
+
|
|
69
|
+
const futureSleepDate = Date.now() + item.value * 60000;
|
|
70
|
+
|
|
71
|
+
if (await this.scheduleSleep(futureSleepDate)) {
|
|
72
|
+
await localStorage.setItem(
|
|
73
|
+
SLEEP_PREFERENCE_KEY,
|
|
74
|
+
{ ...item, futureSleepDate },
|
|
75
|
+
NAMESPACE
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this.sleepTimerStateSubject.next(
|
|
79
|
+
new SleepTimerState(item, futureSleepDate)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async onTimerExpired() {
|
|
85
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
86
|
+
|
|
87
|
+
this.sleepTimerStateSubject.next(new SleepTimerState(SLEEP_TIMER_OFF, 0));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async loadSleep() {
|
|
91
|
+
const sleep = await localStorage.getItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
92
|
+
|
|
93
|
+
if (!sleep) return;
|
|
94
|
+
|
|
95
|
+
const { futureSleepDate, ...rest } = sleep;
|
|
96
|
+
|
|
97
|
+
if (!futureSleepDate || hasDatePassed(futureSleepDate)) {
|
|
98
|
+
log_debug("Sleep date has already passed");
|
|
99
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
100
|
+
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (await this.scheduleSleep(futureSleepDate)) {
|
|
105
|
+
log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
|
|
106
|
+
|
|
107
|
+
this.sleepTimerStateSubject.next(
|
|
108
|
+
new SleepTimerState(rest as SleepTimer, futureSleepDate)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Will be called if user closed the player explicitly.
|
|
114
|
+
// TODO: check if it will be called on Android when we need.
|
|
115
|
+
// Probably we will have to handle it on native instead.
|
|
116
|
+
public static async onPlayerDestroy() {
|
|
117
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
import { NAMESPACE } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { log_debug } = createLogger({
|
|
8
|
+
subsystem: "Player",
|
|
9
|
+
category: "PlaybackSpeedManager",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type PlaybackSpeed = {
|
|
13
|
+
asset: string;
|
|
14
|
+
value: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SPEED_PREFERENCE_KEY = "speed";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_SPEED: PlaybackSpeed = {
|
|
20
|
+
asset: "speed_1",
|
|
21
|
+
value: 1.0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// todo: should not be applied to live streams
|
|
25
|
+
export class SpeedController {
|
|
26
|
+
private playbackSpeedSubject = new BehaviorSubject<PlaybackSpeed>(
|
|
27
|
+
DEFAULT_SPEED
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
constructor(private player: Player) {}
|
|
31
|
+
|
|
32
|
+
get playbackSpeed$() {
|
|
33
|
+
return this.playbackSpeedSubject.asObservable();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async loadSpeed() {
|
|
37
|
+
const speed = await localStorage.getItem(SPEED_PREFERENCE_KEY, NAMESPACE);
|
|
38
|
+
|
|
39
|
+
// checking for value in case we will pull empty or broken object
|
|
40
|
+
if (speed && speed.value) {
|
|
41
|
+
this.playbackSpeedSubject.next(speed);
|
|
42
|
+
this.player.setPlaybackRate?.(speed.value);
|
|
43
|
+
log_debug(`Speed loaded: ${speed}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async setPlaybackSpeed(speed: PlaybackSpeed) {
|
|
48
|
+
await localStorage.setItem(SPEED_PREFERENCE_KEY, speed, NAMESPACE);
|
|
49
|
+
this.playbackSpeedSubject.next(speed);
|
|
50
|
+
this.player.setPlaybackRate?.(speed.value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
2
|
+
import {
|
|
3
|
+
PlayerAnimationStateEnum,
|
|
4
|
+
PlayerAnimationStateT,
|
|
5
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
|
|
2
6
|
import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
3
7
|
|
|
4
8
|
export const NAMESPACE = "audio-player";
|
|
@@ -31,3 +35,23 @@ export const formatSpeed = (value: number) => {
|
|
|
31
35
|
// Return the formatted string with "speed_" prefix
|
|
32
36
|
return `speed_${formattedValue}`;
|
|
33
37
|
};
|
|
38
|
+
|
|
39
|
+
export const calculateBorderRadius = (
|
|
40
|
+
isTablet: boolean,
|
|
41
|
+
docked: boolean,
|
|
42
|
+
startComponentsAnimation: boolean,
|
|
43
|
+
isActiveGesture: boolean,
|
|
44
|
+
playerAnimationState: PlayerAnimationStateT
|
|
45
|
+
): number => {
|
|
46
|
+
const shouldRemoveBorderRadius =
|
|
47
|
+
(startComponentsAnimation &&
|
|
48
|
+
isActiveGesture &&
|
|
49
|
+
playerAnimationState !== PlayerAnimationStateEnum.maximize) ||
|
|
50
|
+
docked;
|
|
51
|
+
|
|
52
|
+
if (shouldRemoveBorderRadius) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return isTablet ? 24 : 10;
|
|
57
|
+
};
|
package/src/Player/index.tsx
CHANGED
|
@@ -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) => {
|
|
@@ -726,7 +721,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
726
721
|
|
|
727
722
|
const isAudioItem =
|
|
728
723
|
entry?.content?.type?.includes?.("audio") ||
|
|
729
|
-
entry?.type?.value
|
|
724
|
+
entry?.type?.value?.includes?.("audio");
|
|
730
725
|
|
|
731
726
|
const getStyle = (dimensions) => {
|
|
732
727
|
const generalStyles = isAudioItem ? styles.audio : styles.video;
|
|
@@ -796,8 +791,13 @@ export default class VideoPlayer extends React.Component<
|
|
|
796
791
|
controls: shouldUseNativeControls,
|
|
797
792
|
};
|
|
798
793
|
|
|
794
|
+
const isFullScreenAudioPlayer =
|
|
795
|
+
this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
|
|
796
|
+
|
|
797
|
+
const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
|
|
798
|
+
|
|
799
799
|
const pictureInPictureEnabled =
|
|
800
|
-
this.props.pluginConfiguration?.pictureInPictureEnabled;
|
|
800
|
+
this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
|
|
801
801
|
|
|
802
802
|
const nativeProps = {
|
|
803
803
|
entry, // Must be passed first in list
|
|
@@ -811,7 +811,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
811
811
|
nowPlayingEnabled,
|
|
812
812
|
paused,
|
|
813
813
|
seek,
|
|
814
|
-
rate,
|
|
815
814
|
textTrackId,
|
|
816
815
|
liveSeekingEnabled,
|
|
817
816
|
liveCatchUpEnabled,
|
|
@@ -822,7 +821,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
822
821
|
accessibilityProps: getAllAccessibilityProps(
|
|
823
822
|
this.getPluginConfiguration()
|
|
824
823
|
),
|
|
825
|
-
};
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
if (!isAppleTV()) {
|
|
827
|
+
// TODO: Move to RN method and remove from props.
|
|
828
|
+
// Causes tvOS bug: native player passes rate on fast seek,
|
|
829
|
+
// RN returns it and it breaks playback.
|
|
830
|
+
nativeProps.rate = rate;
|
|
831
|
+
}
|
|
826
832
|
|
|
827
833
|
if (
|
|
828
834
|
this.isReactNativeMethodsSupported() &&
|
|
@@ -923,8 +929,8 @@ export default class VideoPlayer extends React.Component<
|
|
|
923
929
|
const playerContainerStyles = isApplePlatform()
|
|
924
930
|
? styles.playerContainerBlack
|
|
925
931
|
: isTV() && isAudioItem
|
|
926
|
-
|
|
927
|
-
|
|
932
|
+
? styles.playerContainerTransparent
|
|
933
|
+
: styles.playerContainerBlack;
|
|
928
934
|
|
|
929
935
|
const isInline = () => {
|
|
930
936
|
if (isTV()) {
|
|
@@ -965,9 +971,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
965
971
|
) : null;
|
|
966
972
|
};
|
|
967
973
|
|
|
968
|
-
const isFullScreenAudioPlayer =
|
|
969
|
-
this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
|
|
970
|
-
|
|
971
974
|
const sharedWrapperViewProps = {
|
|
972
975
|
style,
|
|
973
976
|
entry,
|
|
@@ -994,14 +997,16 @@ export default class VideoPlayer extends React.Component<
|
|
|
994
997
|
|
|
995
998
|
const renderMobilePlayer = () => {
|
|
996
999
|
return isFullScreenAudioPlayer && isAudioItem ? (
|
|
997
|
-
|
|
998
|
-
|
|
1000
|
+
// Ensure the View remains in the native hierarchy for proper rendering and interaction
|
|
1001
|
+
<View collapsable={false} style={containerStyle}>
|
|
1002
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
999
1003
|
<this.props.PlayerComponent
|
|
1000
1004
|
ref={this._assignRoot}
|
|
1001
1005
|
{...nativeEvents}
|
|
1002
1006
|
{...nativeProps}
|
|
1007
|
+
listener={this.props.listener}
|
|
1003
1008
|
/>
|
|
1004
|
-
)}
|
|
1009
|
+
) : null}
|
|
1005
1010
|
<AudioPlayerWrapper {...sharedWrapperViewProps} />
|
|
1006
1011
|
</View>
|
|
1007
1012
|
) : (
|
|
@@ -1016,7 +1021,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1016
1021
|
return (
|
|
1017
1022
|
<View style={styles.container}>
|
|
1018
1023
|
<View style={[playerContainerStyles, playerDimensions]}>
|
|
1019
|
-
{isAudioItem
|
|
1024
|
+
{isAudioItem ? (
|
|
1020
1025
|
<AnimationComponent
|
|
1021
1026
|
animationType={ComponentAnimationType.player}
|
|
1022
1027
|
additionalData={{
|
|
@@ -1035,18 +1040,18 @@ export default class VideoPlayer extends React.Component<
|
|
|
1035
1040
|
plugin_configuration={this.getPluginConfiguration()}
|
|
1036
1041
|
/>
|
|
1037
1042
|
</AnimationComponent>
|
|
1038
|
-
)}
|
|
1043
|
+
) : null}
|
|
1039
1044
|
|
|
1040
|
-
{shouldRenderBackground
|
|
1045
|
+
{shouldRenderBackground ? (
|
|
1041
1046
|
<PlayerImageBackground
|
|
1042
1047
|
imageKey={backgroundImageKey}
|
|
1043
1048
|
configuration={this.getPluginConfiguration()}
|
|
1044
1049
|
entry={entry}
|
|
1045
1050
|
style={getBackgroundImageStyle(playerDimensions)}
|
|
1046
1051
|
/>
|
|
1047
|
-
)}
|
|
1052
|
+
) : null}
|
|
1048
1053
|
|
|
1049
|
-
{
|
|
1054
|
+
{!castAction?.state?.connected || supportNativeCast ? (
|
|
1050
1055
|
<AnimationComponent
|
|
1051
1056
|
animationType={ComponentAnimationType.player}
|
|
1052
1057
|
additionalData={playerDimensions}
|
|
@@ -1068,7 +1073,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1068
1073
|
{this.childrenWithPlayNext()}
|
|
1069
1074
|
</this.props.PlayerComponent>
|
|
1070
1075
|
</AnimationComponent>
|
|
1071
|
-
)}
|
|
1076
|
+
) : null}
|
|
1072
1077
|
</View>
|
|
1073
1078
|
|
|
1074
1079
|
{renderTransportControls(playerDimensions)}
|