@applicaster/quick-brick-player 12.0.0-rc.26 → 12.0.0-rc.28
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 +5 -5
- package/src/Player/AudioLayer/AudioDescription.tsx +3 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +206 -7
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +89 -5
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +9 -1
- package/src/Player/AudioLayer/Layout/defaultLayoutsConts.ts +30 -1
- package/src/Player/AudioLayer/Layout/index.tsx +8 -0
- package/src/Player/AudioLayer/utils.ts +33 -0
- package/src/Player/Utils/Utils.ts +5 -0
- package/src/Player/index.tsx +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-player",
|
|
3
|
-
"version": "12.0.0-rc.
|
|
3
|
+
"version": "12.0.0-rc.28",
|
|
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": "12.0.0-rc.
|
|
39
|
+
"@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.28",
|
|
40
40
|
"@applicaster/quick-brick-tv-transport-controls": "11.0.0-rc.133",
|
|
41
|
-
"@applicaster/zapp-react-native-tvos-app": "12.0.0-rc.
|
|
42
|
-
"@applicaster/zapp-react-native-ui-components": "12.0.0-rc.
|
|
43
|
-
"@applicaster/zapp-react-native-utils": "12.0.0-rc.
|
|
41
|
+
"@applicaster/zapp-react-native-tvos-app": "12.0.0-rc.28",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "12.0.0-rc.28",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "12.0.0-rc.28",
|
|
44
44
|
"keymirror": "^0.1.1",
|
|
45
45
|
"query-string": "7.1.3",
|
|
46
46
|
"react-native-web-linear-gradient": "1.1.2",
|
|
@@ -3,7 +3,8 @@ import { Text, TextStyle, View } from "react-native";
|
|
|
3
3
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
4
4
|
|
|
5
5
|
type AudioDescriptionProps = {
|
|
6
|
-
|
|
6
|
+
title: string | number;
|
|
7
|
+
summary: string | number;
|
|
7
8
|
layoutState: {
|
|
8
9
|
inline: boolean;
|
|
9
10
|
docked: boolean;
|
|
@@ -78,8 +79,7 @@ const summaryStyles = (value: GetValue, prefix: string): TextStyle => ({
|
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
export const AudioDescription = (props: AudioDescriptionProps) => {
|
|
81
|
-
const {
|
|
82
|
-
const { title, summary } = content;
|
|
82
|
+
const { title, summary, layoutState, value } = props;
|
|
83
83
|
const prefix = layoutState.docked ? "docked_player_" : "";
|
|
84
84
|
|
|
85
85
|
return (
|
|
@@ -15,8 +15,17 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
15
15
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
16
16
|
import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/PlayerDetails";
|
|
17
17
|
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
18
|
+
import { useEffect, useState } from "react";
|
|
18
19
|
import { MagicBG } from "./MagicBG";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
formatSpeed,
|
|
22
|
+
hasDatePassed,
|
|
23
|
+
loadItemsFromStorage,
|
|
24
|
+
NAMESPACE,
|
|
25
|
+
} from "./utils";
|
|
26
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
27
|
+
import { getLiveUrl } from "../Utils";
|
|
28
|
+
import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
|
|
20
29
|
|
|
21
30
|
const { log_debug } = createLogger({
|
|
22
31
|
subsystem: "UIComponent",
|
|
@@ -98,6 +107,17 @@ const Styles = {
|
|
|
98
107
|
},
|
|
99
108
|
};
|
|
100
109
|
|
|
110
|
+
const sleeps = [
|
|
111
|
+
// { label: "End of episode", value: "episode" },
|
|
112
|
+
{ label: "5 minutes", value: 5 },
|
|
113
|
+
{ label: "10 minutes", value: 10 },
|
|
114
|
+
{ label: "15 minutes", value: 15 },
|
|
115
|
+
{ label: "30 minutes", value: 30 },
|
|
116
|
+
{ label: "45 minutes", value: 45 },
|
|
117
|
+
{ label: "1 hour", value: 60 },
|
|
118
|
+
{ label: "2 hours", value: 120 },
|
|
119
|
+
];
|
|
120
|
+
|
|
101
121
|
export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
102
122
|
const player = usePlayer();
|
|
103
123
|
const playerState = usePlayerState("audio-player-wrapper");
|
|
@@ -113,15 +133,48 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
113
133
|
isTabletPortrait,
|
|
114
134
|
} = props;
|
|
115
135
|
|
|
136
|
+
const speeds = [
|
|
137
|
+
{ asset: configuration["speed_0_8"], value: 0.8 },
|
|
138
|
+
{ asset: configuration["speed_1"], value: 1.0 },
|
|
139
|
+
{ asset: configuration["speed_1_2"], value: 1.2 },
|
|
140
|
+
{ asset: configuration["speed_1_5"], value: 1.5 },
|
|
141
|
+
{ asset: configuration["speed_2"], value: 2.0 },
|
|
142
|
+
];
|
|
143
|
+
|
|
116
144
|
const isTablet = useIsTablet();
|
|
117
145
|
|
|
118
146
|
const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
|
|
147
|
+
|
|
148
|
+
// TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
|
|
149
|
+
|
|
150
|
+
const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
|
|
151
|
+
|
|
152
|
+
const { title, summary } = useTitleSummaryOverlay(
|
|
153
|
+
feedUrl,
|
|
154
|
+
entry,
|
|
155
|
+
playerState?.isLive,
|
|
156
|
+
configuration?.audio_live_player_update_interval
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const [playbackSpeed, setPlaybackSpeed] = useState({
|
|
160
|
+
asset: configuration["speed_1"],
|
|
161
|
+
value: 1.0,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const [sleepTimer, setSleepTimer] = useState({
|
|
165
|
+
label: "playback_sleep_off_label",
|
|
166
|
+
value: 0,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const [sleepTimerDate, setSleepTimerDate] = useState(0);
|
|
119
170
|
|
|
120
171
|
const [dimensions, setDimensions] = React.useState<Dimensions>({
|
|
121
172
|
width: 0,
|
|
122
173
|
height: 0,
|
|
123
174
|
});
|
|
124
175
|
|
|
176
|
+
const navigator = useNavigation();
|
|
177
|
+
|
|
125
178
|
const { maximiseVideoModal } = useNavigation();
|
|
126
179
|
|
|
127
180
|
const onLayout = (event: any) => {
|
|
@@ -133,6 +186,84 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
133
186
|
});
|
|
134
187
|
};
|
|
135
188
|
|
|
189
|
+
const cancelSleepTimer = async () => {
|
|
190
|
+
player?.cancelSleepTimer();
|
|
191
|
+
|
|
192
|
+
setSleepTimer({
|
|
193
|
+
label: "playback_sleep_off_label",
|
|
194
|
+
value: 0,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
setSleepTimerDate(0);
|
|
198
|
+
player?.pause();
|
|
199
|
+
|
|
200
|
+
await localStorage.removeItem("sleep", NAMESPACE);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const startSleepTimer = (timerValue: number) => {
|
|
204
|
+
try {
|
|
205
|
+
const currentDate = new Date();
|
|
206
|
+
const futureDate = new Date(currentDate.getTime() + timerValue * 60000);
|
|
207
|
+
const futureSleepDate = futureDate.getTime();
|
|
208
|
+
|
|
209
|
+
player?.cancelSleepTimer();
|
|
210
|
+
|
|
211
|
+
player?.startSleepTimer(futureSleepDate);
|
|
212
|
+
|
|
213
|
+
return futureSleepDate;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const handleSleep = async (
|
|
220
|
+
timerValue: number,
|
|
221
|
+
currentFutureSleepDate = null
|
|
222
|
+
) => {
|
|
223
|
+
if (currentFutureSleepDate) {
|
|
224
|
+
if (hasDatePassed(currentFutureSleepDate)) {
|
|
225
|
+
await cancelSleepTimer();
|
|
226
|
+
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return currentFutureSleepDate;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (timerValue === 0) {
|
|
234
|
+
await cancelSleepTimer();
|
|
235
|
+
|
|
236
|
+
return timerValue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return startSleepTimer(timerValue);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
const loadSpeed = async () => {
|
|
244
|
+
const speed = await loadItemsFromStorage("speed");
|
|
245
|
+
|
|
246
|
+
if (playerState.isReadyToPlay && speed) {
|
|
247
|
+
setPlaybackSpeed(speed);
|
|
248
|
+
player?.setPlaybackRate(speed.value);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const loadSleep = async () => {
|
|
253
|
+
const sleep = await loadItemsFromStorage("sleep");
|
|
254
|
+
|
|
255
|
+
if (playerState.isReadyToPlay && sleep) {
|
|
256
|
+
const { futureSleepDate, ...rest } = sleep;
|
|
257
|
+
setSleepTimer(rest);
|
|
258
|
+
const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
|
|
259
|
+
setSleepTimerDate(sleepTimerDate);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
loadSpeed().then();
|
|
264
|
+
loadSleep().then();
|
|
265
|
+
}, [playerState.isReadyToPlay]);
|
|
266
|
+
|
|
136
267
|
const getValue = (key: string, stylesObj: {}) =>
|
|
137
268
|
R.propOr(null, key, stylesObj);
|
|
138
269
|
|
|
@@ -172,6 +303,60 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
172
303
|
player?.forward(seekDuration);
|
|
173
304
|
}, [seekDuration, player]);
|
|
174
305
|
|
|
306
|
+
const onSpeedSelect = React.useCallback(
|
|
307
|
+
async (item) => {
|
|
308
|
+
setPlaybackSpeed(item);
|
|
309
|
+
player.setPlaybackRate(item.value);
|
|
310
|
+
await localStorage.setItem("speed", item, NAMESPACE);
|
|
311
|
+
},
|
|
312
|
+
[player, setPlaybackSpeed]
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const onSleepSelect = React.useCallback(
|
|
316
|
+
async (item) => {
|
|
317
|
+
setSleepTimer(item);
|
|
318
|
+
const futureSleepDate = await handleSleep(item.value);
|
|
319
|
+
setSleepTimerDate(futureSleepDate);
|
|
320
|
+
|
|
321
|
+
await localStorage.setItem(
|
|
322
|
+
"sleep",
|
|
323
|
+
{ ...item, futureSleepDate },
|
|
324
|
+
NAMESPACE
|
|
325
|
+
);
|
|
326
|
+
},
|
|
327
|
+
[player, setSleepTimer]
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const onSpeedButtonPress = React.useCallback(() => {
|
|
331
|
+
navigator.openModalBottomSheet({
|
|
332
|
+
modalBottomSheetContentProps: {
|
|
333
|
+
items: speeds,
|
|
334
|
+
current_selection: playbackSpeed,
|
|
335
|
+
onPress: onSpeedSelect,
|
|
336
|
+
title: configuration["playback_speed_title"],
|
|
337
|
+
},
|
|
338
|
+
options: {
|
|
339
|
+
supportedOrientations: ["portrait"],
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
}, [playbackSpeed, setPlaybackSpeed]);
|
|
343
|
+
|
|
344
|
+
const onSleepButtonPress = React.useCallback(() => {
|
|
345
|
+
navigator.openModalBottomSheet({
|
|
346
|
+
modalBottomSheetContentProps: {
|
|
347
|
+
items: [
|
|
348
|
+
{ label: configuration["playback_sleep_off_label"], value: 0 },
|
|
349
|
+
].concat(sleeps),
|
|
350
|
+
current_selection: sleepTimer,
|
|
351
|
+
onPress: onSleepSelect,
|
|
352
|
+
title: configuration["playback_sleep_title"],
|
|
353
|
+
},
|
|
354
|
+
options: {
|
|
355
|
+
supportedOrientations: ["portrait"],
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}, [sleepTimer, setSleepTimer, configuration]);
|
|
359
|
+
|
|
175
360
|
const onCloseVideoModal = React.useCallback(() => {
|
|
176
361
|
playerManager.close();
|
|
177
362
|
}, [playerManager]);
|
|
@@ -215,12 +400,22 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
215
400
|
onForwardButtonPress={onForwardButtonPress}
|
|
216
401
|
onPlayerSeek={onPlayerSeek}
|
|
217
402
|
onSkipStart={onSkipStart}
|
|
403
|
+
onSpeedButtonPress={onSpeedButtonPress}
|
|
404
|
+
onSleepButtonPress={onSleepButtonPress}
|
|
405
|
+
playbackSpeedName={formatSpeed(playbackSpeed.value)}
|
|
406
|
+
playbackSleepName={
|
|
407
|
+
sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
|
|
408
|
+
}
|
|
409
|
+
sleepTimerDate={sleepTimerDate}
|
|
410
|
+
cancelSleepTimer={cancelSleepTimer}
|
|
218
411
|
onSkipLive={onSkipLive}
|
|
219
412
|
onCloseVideoModal={onCloseVideoModal}
|
|
220
413
|
maximiseVideoModal={maximiseVideoModal}
|
|
221
414
|
configuration={configuration}
|
|
222
415
|
dimensions={dimensions}
|
|
223
416
|
playNextData={playNextData}
|
|
417
|
+
title={title}
|
|
418
|
+
summary={summary}
|
|
224
419
|
/>
|
|
225
420
|
{children}
|
|
226
421
|
{!docked && (
|
|
@@ -254,20 +449,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
254
449
|
playNextData,
|
|
255
450
|
onSkipStart,
|
|
256
451
|
onSkipLive,
|
|
452
|
+
sleepTimer.value,
|
|
453
|
+
sleepTimerDate,
|
|
257
454
|
]);
|
|
258
455
|
|
|
259
456
|
if (configuration.magic_background) {
|
|
260
457
|
const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
|
|
261
458
|
|
|
262
|
-
|
|
263
|
-
configuration.audio_player_background_image_query || "width";
|
|
264
|
-
|
|
265
|
-
const queryParamValue = 15;
|
|
459
|
+
// TODO: To be added when background is ready
|
|
266
460
|
|
|
267
|
-
const
|
|
461
|
+
// const queryParamName =
|
|
462
|
+
// configuration.audio_player_background_image_query || "width";
|
|
463
|
+
//
|
|
464
|
+
// const queryParamValue = 15;
|
|
465
|
+
//
|
|
466
|
+
// const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
|
|
268
467
|
|
|
269
468
|
return (
|
|
270
|
-
<MagicBG image={
|
|
469
|
+
<MagicBG image={baseUrl} fallbackUrl={baseUrl} defaultColor="#000000">
|
|
271
470
|
{MainContent}
|
|
272
471
|
</MagicBG>
|
|
273
472
|
);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ProgressBar,
|
|
7
7
|
Controls,
|
|
8
8
|
TransportInfo,
|
|
9
|
+
CountDown,
|
|
9
10
|
} from "@applicaster/quick-brick-mobile-transport-controls";
|
|
10
11
|
|
|
11
12
|
import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
@@ -17,10 +18,13 @@ import {
|
|
|
17
18
|
rewindButton,
|
|
18
19
|
skipToStartButton,
|
|
19
20
|
skipToLiveButton,
|
|
21
|
+
speedButton,
|
|
22
|
+
sleepButton,
|
|
20
23
|
} from "./defaultLayoutsConts";
|
|
21
24
|
import { AudioDescription } from "../AudioDescription";
|
|
22
25
|
import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
|
|
23
26
|
import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
|
|
27
|
+
import { useEffect, useMemo, useState } from "react";
|
|
24
28
|
|
|
25
29
|
type Dimensions = {
|
|
26
30
|
width: number;
|
|
@@ -42,15 +46,23 @@ type LayoutProps = {
|
|
|
42
46
|
isPaused: boolean;
|
|
43
47
|
isBuffering: boolean;
|
|
44
48
|
};
|
|
49
|
+
sleepTimerDate: number;
|
|
45
50
|
value: GetValue;
|
|
51
|
+
playbackSpeedName: string;
|
|
52
|
+
playbackSleepName: string;
|
|
46
53
|
configuration: { [key: string]: any };
|
|
47
54
|
onPlaybackButtonPress: () => void;
|
|
48
55
|
onRewindButtonPress: () => void;
|
|
49
56
|
onForwardButtonPress: () => void;
|
|
57
|
+
onSpeedButtonPress: () => void;
|
|
58
|
+
onSleepButtonPress: () => void;
|
|
59
|
+
cancelSleepTimer: () => void;
|
|
50
60
|
onPlayerSeek: (targetTime: number) => void;
|
|
51
61
|
onSkipLive: () => void;
|
|
52
62
|
onSkipStart: () => void;
|
|
53
63
|
dimensions: Dimensions;
|
|
64
|
+
title: string | number;
|
|
65
|
+
summary: string | number;
|
|
54
66
|
};
|
|
55
67
|
|
|
56
68
|
const Styles = {
|
|
@@ -68,8 +80,8 @@ const Styles = {
|
|
|
68
80
|
},
|
|
69
81
|
controls: (isTablet: boolean, isLiveShowFullControls: boolean) => ({
|
|
70
82
|
height: isTablet ? 64 : 50,
|
|
71
|
-
marginHorizontal:
|
|
72
|
-
justifyContent: isLiveShowFullControls ? "space-between" : "
|
|
83
|
+
marginHorizontal: 10,
|
|
84
|
+
justifyContent: isLiveShowFullControls ? "space-between" : "space-evenly",
|
|
73
85
|
}),
|
|
74
86
|
backgroundImageContainer: (dimensions: Dimensions, isTablet: boolean) => {
|
|
75
87
|
return {
|
|
@@ -117,8 +129,16 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
117
129
|
onPlayerSeek,
|
|
118
130
|
onSkipStart,
|
|
119
131
|
onSkipLive,
|
|
132
|
+
onSpeedButtonPress,
|
|
133
|
+
onSleepButtonPress,
|
|
134
|
+
playbackSpeedName,
|
|
135
|
+
playbackSleepName,
|
|
120
136
|
configuration,
|
|
121
137
|
dimensions,
|
|
138
|
+
cancelSleepTimer,
|
|
139
|
+
sleepTimerDate,
|
|
140
|
+
title,
|
|
141
|
+
summary,
|
|
122
142
|
} = props;
|
|
123
143
|
|
|
124
144
|
const isTablet = useIsTablet();
|
|
@@ -128,8 +148,15 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
128
148
|
|
|
129
149
|
const languageData = useLanguageData("audio-player");
|
|
130
150
|
|
|
151
|
+
const showCounter = useMemo(() => sleepTimerDate > 0, [sleepTimerDate]);
|
|
152
|
+
|
|
131
153
|
const controller = playerManager.getActivePlayer();
|
|
132
154
|
const liveButton = Boolean(value("live_badge_enabled"));
|
|
155
|
+
const sleepTitle = value("playback_sleep_countdown_label");
|
|
156
|
+
|
|
157
|
+
const [timeLeft, setTimeLeft] = useState(
|
|
158
|
+
sleepTimerDate !== 0 ? sleepTimerDate - Date.now() : 0
|
|
159
|
+
);
|
|
133
160
|
|
|
134
161
|
let showFullControls = !controller?.isAd();
|
|
135
162
|
|
|
@@ -172,6 +199,18 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
172
199
|
onPress: controller?.hasContentDuration?.() ? onForwardButtonPress : null,
|
|
173
200
|
});
|
|
174
201
|
|
|
202
|
+
const speed = speedButton({
|
|
203
|
+
isTablet,
|
|
204
|
+
onPress: onSpeedButtonPress,
|
|
205
|
+
name: playbackSpeedName,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const sleep = sleepButton({
|
|
209
|
+
isTablet,
|
|
210
|
+
onPress: onSleepButtonPress,
|
|
211
|
+
name: playbackSleepName,
|
|
212
|
+
});
|
|
213
|
+
|
|
175
214
|
if (isLiveContent) {
|
|
176
215
|
const skipToStart = skipToStartButton({
|
|
177
216
|
isTablet,
|
|
@@ -186,7 +225,8 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
186
225
|
return [skipToStart, rewind, play, forward, skipToLive];
|
|
187
226
|
}
|
|
188
227
|
|
|
189
|
-
|
|
228
|
+
// Temporary empty button until we add sleep button. It will ensure the buttons will not break in their position
|
|
229
|
+
return [speed, rewind, play, forward, sleep];
|
|
190
230
|
}, [
|
|
191
231
|
playerState,
|
|
192
232
|
liveButton,
|
|
@@ -194,13 +234,48 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
194
234
|
onPlaybackButtonPress,
|
|
195
235
|
onRewindButtonPress,
|
|
196
236
|
onForwardButtonPress,
|
|
237
|
+
onSpeedButtonPress,
|
|
197
238
|
showFullControls,
|
|
198
239
|
onSkipStart,
|
|
199
240
|
onSkipLive,
|
|
200
241
|
isLiveContent,
|
|
242
|
+
playbackSpeedName,
|
|
243
|
+
playbackSleepName,
|
|
201
244
|
controller,
|
|
202
245
|
]);
|
|
203
246
|
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
if (showCounter && sleepTimerDate !== 0) {
|
|
249
|
+
// Setting the initial timeLeft based on sleepTimerDate
|
|
250
|
+
setTimeLeft(sleepTimerDate - Date.now());
|
|
251
|
+
|
|
252
|
+
// Setting up an interval to update the remaining time every second
|
|
253
|
+
const intervalId = setInterval(() => {
|
|
254
|
+
setTimeLeft(() => {
|
|
255
|
+
const newTimeLeft = sleepTimerDate - Date.now();
|
|
256
|
+
|
|
257
|
+
// If the new remaining time is less than or equal to 0, clear the interval and trigger the cancelSleepTimer function
|
|
258
|
+
if (newTimeLeft <= 0) {
|
|
259
|
+
clearInterval(intervalId);
|
|
260
|
+
|
|
261
|
+
cancelSleepTimer();
|
|
262
|
+
|
|
263
|
+
return 0;
|
|
264
|
+
} else {
|
|
265
|
+
// Otherwise, update the state with the new remaining time
|
|
266
|
+
return newTimeLeft;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}, 1000);
|
|
270
|
+
|
|
271
|
+
// Clearing the interval when the component unmounts or dependencies change
|
|
272
|
+
return () => clearInterval(intervalId);
|
|
273
|
+
} else {
|
|
274
|
+
// Ensure the timeLeft state is set correctly if showCounter is false
|
|
275
|
+
setTimeLeft(0);
|
|
276
|
+
}
|
|
277
|
+
}, [sleepTimerDate, showCounter, cancelSleepTimer]);
|
|
278
|
+
|
|
204
279
|
const Separator = () => <View style={Styles.separator} />;
|
|
205
280
|
|
|
206
281
|
const liveSeekingEnabled = value("liveSeekingEnabled");
|
|
@@ -229,13 +304,13 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
229
304
|
>
|
|
230
305
|
<View style={isTablet ? Styles.tabletLayout : null}>
|
|
231
306
|
<PlayerImageBackground
|
|
232
|
-
configuration={configuration}
|
|
233
307
|
entry={entry}
|
|
234
308
|
style={Styles.backgroundImageContainer(dimensions, isTablet)}
|
|
235
309
|
imageStyle={Styles.backgroundImageContainerImageStyle(isTablet)}
|
|
236
310
|
/>
|
|
237
311
|
<AudioDescription
|
|
238
|
-
|
|
312
|
+
title={title}
|
|
313
|
+
summary={summary}
|
|
239
314
|
value={value}
|
|
240
315
|
layoutState={layoutState}
|
|
241
316
|
/>
|
|
@@ -252,6 +327,15 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
252
327
|
disableFullscreen={true}
|
|
253
328
|
sliderStyle={2}
|
|
254
329
|
/>
|
|
330
|
+
|
|
331
|
+
{showCounter && timeLeft >= 0 && (
|
|
332
|
+
<CountDown
|
|
333
|
+
timeLeft={timeLeft}
|
|
334
|
+
value={value}
|
|
335
|
+
title={sleepTitle}
|
|
336
|
+
/>
|
|
337
|
+
)}
|
|
338
|
+
|
|
255
339
|
<Separator />
|
|
256
340
|
</View>
|
|
257
341
|
)}
|
|
@@ -48,6 +48,8 @@ type LayoutProps = {
|
|
|
48
48
|
maximiseVideoModal: () => void;
|
|
49
49
|
dimensions: Dimensions;
|
|
50
50
|
playNextData: PlayNextData;
|
|
51
|
+
title: string | number;
|
|
52
|
+
summary: string | number;
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
const styles = StyleSheet.create({
|
|
@@ -196,6 +198,8 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
196
198
|
playerState,
|
|
197
199
|
configuration,
|
|
198
200
|
playNextData,
|
|
201
|
+
title,
|
|
202
|
+
summary,
|
|
199
203
|
} = props;
|
|
200
204
|
|
|
201
205
|
const isTablet = useIsTablet();
|
|
@@ -232,7 +236,11 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
232
236
|
!configuration.magic_background && backgroundColorStyle(value),
|
|
233
237
|
]}
|
|
234
238
|
>
|
|
235
|
-
<ContentInfo
|
|
239
|
+
<ContentInfo
|
|
240
|
+
content={{ ...entry, title, summary }}
|
|
241
|
+
value={value}
|
|
242
|
+
docked
|
|
243
|
+
/>
|
|
236
244
|
</View>
|
|
237
245
|
</View>
|
|
238
246
|
</TouchableWithoutFeedback>
|
|
@@ -53,8 +53,9 @@ type ButtonsProps = {
|
|
|
53
53
|
onPress: () => void;
|
|
54
54
|
isTablet: boolean;
|
|
55
55
|
style?: any;
|
|
56
|
+
name?: string;
|
|
56
57
|
};
|
|
57
|
-
type ButtonsByNameProps = ButtonsProps & { name
|
|
58
|
+
type ButtonsByNameProps = ButtonsProps & { name?: string };
|
|
58
59
|
|
|
59
60
|
const rewindButtonByName = ({
|
|
60
61
|
name,
|
|
@@ -79,6 +80,28 @@ const skipButtonByName = ({
|
|
|
79
80
|
style,
|
|
80
81
|
});
|
|
81
82
|
|
|
83
|
+
const speedButtonByName = ({
|
|
84
|
+
name = "speed_1",
|
|
85
|
+
onPress,
|
|
86
|
+
style,
|
|
87
|
+
}: ButtonsByNameProps): ControlsButton => ({
|
|
88
|
+
size: "small",
|
|
89
|
+
icon: name,
|
|
90
|
+
onPress,
|
|
91
|
+
style,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const sleepButtonByName = ({
|
|
95
|
+
name = "sleep_timer",
|
|
96
|
+
onPress,
|
|
97
|
+
style,
|
|
98
|
+
}: ButtonsByNameProps): ControlsButton => ({
|
|
99
|
+
size: "small",
|
|
100
|
+
icon: name,
|
|
101
|
+
onPress,
|
|
102
|
+
style,
|
|
103
|
+
});
|
|
104
|
+
|
|
82
105
|
export const rewindButton = (props: ButtonsProps): ControlsButton =>
|
|
83
106
|
rewindButtonByName({ ...props, name: "rewind" });
|
|
84
107
|
|
|
@@ -90,3 +113,9 @@ export const skipToStartButton = (props: ButtonsProps): ControlsButton =>
|
|
|
90
113
|
|
|
91
114
|
export const skipToLiveButton = (props: ButtonsProps): ControlsButton =>
|
|
92
115
|
skipButtonByName({ ...props, name: "skip_live" });
|
|
116
|
+
|
|
117
|
+
export const speedButton = (props: ButtonsProps): ControlsButton =>
|
|
118
|
+
speedButtonByName({ ...props });
|
|
119
|
+
|
|
120
|
+
export const sleepButton = (props: ButtonsProps): ControlsButton =>
|
|
121
|
+
sleepButtonByName({ ...props });
|
|
@@ -24,17 +24,25 @@ type LayoutProps = {
|
|
|
24
24
|
isBuffering: boolean;
|
|
25
25
|
};
|
|
26
26
|
value: GetValue;
|
|
27
|
+
sleepTimerDate: number;
|
|
28
|
+
playbackSpeedName: string;
|
|
29
|
+
playbackSleepName: string;
|
|
27
30
|
configuration: { [key: string]: any };
|
|
28
31
|
onPlaybackButtonPress: () => void;
|
|
29
32
|
onRewindButtonPress: () => void;
|
|
30
33
|
onForwardButtonPress: () => void;
|
|
34
|
+
onSpeedButtonPress: () => void;
|
|
35
|
+
onSleepButtonPress: () => void;
|
|
31
36
|
onCloseVideoModal: () => void;
|
|
37
|
+
cancelSleepTimer: () => void;
|
|
32
38
|
onPlayerSeek: (targetTime: number) => void;
|
|
33
39
|
onSkipStart: () => void;
|
|
34
40
|
onSkipLive: () => void;
|
|
35
41
|
maximiseVideoModal: () => void;
|
|
36
42
|
dimensions: Dimensions;
|
|
37
43
|
playNextData: PlayNextData;
|
|
44
|
+
title: string | number;
|
|
45
|
+
summary: string | number;
|
|
38
46
|
};
|
|
39
47
|
|
|
40
48
|
export default function Layout(
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
2
|
+
import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
3
|
+
|
|
4
|
+
export const NAMESPACE = "audio-player";
|
|
5
|
+
|
|
6
|
+
export async function loadItemsFromStorage(key: string) {
|
|
7
|
+
const storedItems = await localStorage.getItem(key, NAMESPACE);
|
|
8
|
+
|
|
9
|
+
return parseJsonIfNeeded(storedItems) || null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const hasDatePassed = (timestamp: number) => {
|
|
13
|
+
// Get the current date and time
|
|
14
|
+
const currentDate = new Date();
|
|
15
|
+
|
|
16
|
+
// Compare the current date's timestamp with the given timestamp
|
|
17
|
+
return currentDate.getTime() > timestamp;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const formatSpeed = (value: number) => {
|
|
21
|
+
let formattedValue = value.toString();
|
|
22
|
+
|
|
23
|
+
// If the value ends with ".0", remove it
|
|
24
|
+
if (formattedValue.endsWith(".0")) {
|
|
25
|
+
formattedValue = formattedValue.slice(0, -2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Replace "." with "_"
|
|
29
|
+
formattedValue = formattedValue.replace(".", "_");
|
|
30
|
+
|
|
31
|
+
// Return the formatted string with "speed_" prefix
|
|
32
|
+
return `speed_${formattedValue}`;
|
|
33
|
+
};
|
|
@@ -22,6 +22,11 @@ export const stringsOnlyObject = (obj) => {
|
|
|
22
22
|
return strObj;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
export const getLiveUrl = (url: string) => {
|
|
26
|
+
// Use the replace method with a regular expression to replace all instances of subClass with livePage
|
|
27
|
+
return url.replace(new RegExp("subClass", "g"), "livePage");
|
|
28
|
+
};
|
|
29
|
+
|
|
25
30
|
export const addQueryParam = (
|
|
26
31
|
baseUrl: string,
|
|
27
32
|
paramName: string,
|
package/src/Player/index.tsx
CHANGED
|
@@ -87,7 +87,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
87
87
|
// TODO: Can be deleted, on controller logic
|
|
88
88
|
paused: false,
|
|
89
89
|
piped: false,
|
|
90
|
-
rate: 1,
|
|
90
|
+
rate: 1.0,
|
|
91
91
|
// TODO: Hack for analytics, must br removed
|
|
92
92
|
resume: null,
|
|
93
93
|
|
|
@@ -368,6 +368,10 @@ export default class VideoPlayer extends React.Component<
|
|
|
368
368
|
}
|
|
369
369
|
};
|
|
370
370
|
|
|
371
|
+
setPlaybackRate = (rate = 1.0) => {
|
|
372
|
+
this.setState({ rate });
|
|
373
|
+
};
|
|
374
|
+
|
|
371
375
|
getCurrentTime = () => {
|
|
372
376
|
return this.state.currentTime;
|
|
373
377
|
};
|
|
@@ -767,7 +771,8 @@ export default class VideoPlayer extends React.Component<
|
|
|
767
771
|
style,
|
|
768
772
|
} = this.props;
|
|
769
773
|
|
|
770
|
-
const { audioTrackId, keyEvent, paused, seek, textTrackId } =
|
|
774
|
+
const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
|
|
775
|
+
this.state;
|
|
771
776
|
|
|
772
777
|
const isAudioItem =
|
|
773
778
|
entry?.content?.type?.includes?.("audio") ||
|
|
@@ -839,6 +844,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
839
844
|
nowPlayingEnabled,
|
|
840
845
|
paused,
|
|
841
846
|
seek,
|
|
847
|
+
rate,
|
|
842
848
|
textTrackId,
|
|
843
849
|
liveSeekingEnabled,
|
|
844
850
|
liveCatchUpEnabled,
|