@applicaster/quick-brick-player 12.0.0-alpha.3021004521 → 12.0.0-alpha.3425722002
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 +6 -5
- package/src/CellPlayer/Assets/AssetsComponent.tsx +2 -1
- package/src/Player/AudioLayer/AudioDescription.tsx +3 -3
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +211 -4
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +103 -12
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +12 -6
- package/src/Player/AudioLayer/Layout/defaultLayoutsConts.ts +30 -1
- package/src/Player/AudioLayer/Layout/index.tsx +8 -0
- package/src/Player/AudioLayer/MagicBG/index.tsx +32 -13
- package/src/Player/AudioLayer/utils.ts +33 -0
- package/src/Player/Utils/Utils.ts +23 -0
- package/src/Player/Utils/index.tsx +0 -2
- package/src/Player/index.tsx +62 -22
- package/src/QuickBrickPlayer.tsx +6 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-player",
|
|
3
|
-
"version": "12.0.0-alpha.
|
|
3
|
+
"version": "12.0.0-alpha.3425722002",
|
|
4
4
|
"description": "Quick Brick Player",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -36,12 +36,13 @@
|
|
|
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-alpha.
|
|
42
|
-
"@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.
|
|
43
|
-
"@applicaster/zapp-react-native-utils": "12.0.0-alpha.
|
|
41
|
+
"@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.3425722002",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.3425722002",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "12.0.0-alpha.3425722002",
|
|
44
44
|
"keymirror": "^0.1.1",
|
|
45
|
+
"query-string": "7.1.3",
|
|
45
46
|
"react-native-web-linear-gradient": "1.1.2",
|
|
46
47
|
"shaka-player": "4.3.5",
|
|
47
48
|
"typeface-montserrat": "^0.0.54",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
3
|
import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
|
|
4
4
|
import { MUTED_USAGE } from "../const";
|
|
5
5
|
import { LiveImageManager } from "@applicaster/zapp-react-native-ui-components/Components/VideoLive/LiveImageManager";
|
|
6
|
+
import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
|
|
6
7
|
|
|
7
8
|
type Asset = string | undefined;
|
|
8
9
|
|
|
@@ -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,7 +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";
|
|
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";
|
|
19
29
|
|
|
20
30
|
const { log_debug } = createLogger({
|
|
21
31
|
subsystem: "UIComponent",
|
|
@@ -32,6 +42,7 @@ type Dimensions = {
|
|
|
32
42
|
type Configuration = {
|
|
33
43
|
[key: string]: any;
|
|
34
44
|
magic_background?: boolean;
|
|
45
|
+
audio_player_background_image_query?: string;
|
|
35
46
|
tablet_landscape_sidebar_width?: string;
|
|
36
47
|
tablet_landscape_player_container_background_color?: string;
|
|
37
48
|
};
|
|
@@ -96,6 +107,17 @@ const Styles = {
|
|
|
96
107
|
},
|
|
97
108
|
};
|
|
98
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
|
+
|
|
99
121
|
export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
100
122
|
const player = usePlayer();
|
|
101
123
|
const playerState = usePlayerState("audio-player-wrapper");
|
|
@@ -111,15 +133,48 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
111
133
|
isTabletPortrait,
|
|
112
134
|
} = props;
|
|
113
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
|
+
|
|
114
144
|
const isTablet = useIsTablet();
|
|
115
145
|
|
|
116
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);
|
|
117
170
|
|
|
118
171
|
const [dimensions, setDimensions] = React.useState<Dimensions>({
|
|
119
172
|
width: 0,
|
|
120
173
|
height: 0,
|
|
121
174
|
});
|
|
122
175
|
|
|
176
|
+
const navigator = useNavigation();
|
|
177
|
+
|
|
123
178
|
const { maximiseVideoModal } = useNavigation();
|
|
124
179
|
|
|
125
180
|
const onLayout = (event: any) => {
|
|
@@ -131,6 +186,84 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
131
186
|
});
|
|
132
187
|
};
|
|
133
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
|
+
|
|
134
267
|
const getValue = (key: string, stylesObj: {}) =>
|
|
135
268
|
R.propOr(null, key, stylesObj);
|
|
136
269
|
|
|
@@ -170,6 +303,60 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
170
303
|
player?.forward(seekDuration);
|
|
171
304
|
}, [seekDuration, player]);
|
|
172
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
|
+
|
|
173
360
|
const onCloseVideoModal = React.useCallback(() => {
|
|
174
361
|
playerManager.close();
|
|
175
362
|
}, [playerManager]);
|
|
@@ -213,12 +400,22 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
213
400
|
onForwardButtonPress={onForwardButtonPress}
|
|
214
401
|
onPlayerSeek={onPlayerSeek}
|
|
215
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}
|
|
216
411
|
onSkipLive={onSkipLive}
|
|
217
412
|
onCloseVideoModal={onCloseVideoModal}
|
|
218
413
|
maximiseVideoModal={maximiseVideoModal}
|
|
219
414
|
configuration={configuration}
|
|
220
415
|
dimensions={dimensions}
|
|
221
416
|
playNextData={playNextData}
|
|
417
|
+
title={title}
|
|
418
|
+
summary={summary}
|
|
222
419
|
/>
|
|
223
420
|
{children}
|
|
224
421
|
{!docked && (
|
|
@@ -252,14 +449,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
252
449
|
playNextData,
|
|
253
450
|
onSkipStart,
|
|
254
451
|
onSkipLive,
|
|
452
|
+
sleepTimer.value,
|
|
453
|
+
sleepTimerDate,
|
|
255
454
|
]);
|
|
256
455
|
|
|
257
456
|
if (configuration.magic_background) {
|
|
457
|
+
const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
|
|
458
|
+
|
|
459
|
+
// TODO: To be added when background is ready
|
|
460
|
+
|
|
461
|
+
// const queryParamName =
|
|
462
|
+
// configuration.audio_player_background_image_query || "width";
|
|
463
|
+
//
|
|
464
|
+
// const queryParamValue = 15;
|
|
465
|
+
//
|
|
466
|
+
// const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
|
|
467
|
+
|
|
258
468
|
return (
|
|
259
|
-
<MagicBG
|
|
260
|
-
image={entry?.extensions?.audio_player_background_image}
|
|
261
|
-
defaultColor="#000000"
|
|
262
|
-
>
|
|
469
|
+
<MagicBG image={baseUrl} fallbackUrl={baseUrl} defaultColor="#000000">
|
|
263
470
|
{MainContent}
|
|
264
471
|
</MagicBG>
|
|
265
472
|
);
|
|
@@ -6,21 +6,25 @@ 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";
|
|
12
13
|
|
|
13
|
-
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks
|
|
14
|
+
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
14
15
|
import {
|
|
15
16
|
forwardButton,
|
|
16
17
|
playbackButton,
|
|
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,13 +80,13 @@ 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 {
|
|
76
|
-
width: isTablet ? 463 : dimensions.width,
|
|
77
|
-
height: isTablet ? 463 : dimensions.width,
|
|
88
|
+
width: isTablet ? 463 : dimensions.width || 330,
|
|
89
|
+
height: isTablet ? 463 : dimensions.width || 330,
|
|
78
90
|
};
|
|
79
91
|
},
|
|
80
92
|
backgroundImageContainerImageStyle: (isTablet) => ({
|
|
@@ -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,11 +148,17 @@ 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
|
-
let showFullControls =
|
|
135
|
-
controller?.hasContentDuration?.() && !controller?.isAd();
|
|
161
|
+
let showFullControls = !controller?.isAd();
|
|
136
162
|
|
|
137
163
|
if (isLiveContent) {
|
|
138
164
|
const seekEnabled = Boolean(value("liveSeekingEnabled"));
|
|
@@ -163,8 +189,27 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
163
189
|
return [play];
|
|
164
190
|
}
|
|
165
191
|
|
|
166
|
-
const rewind = rewindButton({
|
|
167
|
-
|
|
192
|
+
const rewind = rewindButton({
|
|
193
|
+
isTablet,
|
|
194
|
+
onPress: controller?.hasContentDuration?.() ? onRewindButtonPress : null,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const forward = forwardButton({
|
|
198
|
+
isTablet,
|
|
199
|
+
onPress: controller?.hasContentDuration?.() ? onForwardButtonPress : null,
|
|
200
|
+
});
|
|
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
|
+
});
|
|
168
213
|
|
|
169
214
|
if (isLiveContent) {
|
|
170
215
|
const skipToStart = skipToStartButton({
|
|
@@ -180,7 +225,8 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
180
225
|
return [skipToStart, rewind, play, forward, skipToLive];
|
|
181
226
|
}
|
|
182
227
|
|
|
183
|
-
|
|
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];
|
|
184
230
|
}, [
|
|
185
231
|
playerState,
|
|
186
232
|
liveButton,
|
|
@@ -188,12 +234,48 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
188
234
|
onPlaybackButtonPress,
|
|
189
235
|
onRewindButtonPress,
|
|
190
236
|
onForwardButtonPress,
|
|
237
|
+
onSpeedButtonPress,
|
|
191
238
|
showFullControls,
|
|
192
239
|
onSkipStart,
|
|
193
240
|
onSkipLive,
|
|
194
241
|
isLiveContent,
|
|
242
|
+
playbackSpeedName,
|
|
243
|
+
playbackSleepName,
|
|
244
|
+
controller,
|
|
195
245
|
]);
|
|
196
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
|
+
|
|
197
279
|
const Separator = () => <View style={Styles.separator} />;
|
|
198
280
|
|
|
199
281
|
const liveSeekingEnabled = value("liveSeekingEnabled");
|
|
@@ -222,13 +304,13 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
222
304
|
>
|
|
223
305
|
<View style={isTablet ? Styles.tabletLayout : null}>
|
|
224
306
|
<PlayerImageBackground
|
|
225
|
-
configuration={configuration}
|
|
226
307
|
entry={entry}
|
|
227
308
|
style={Styles.backgroundImageContainer(dimensions, isTablet)}
|
|
228
309
|
imageStyle={Styles.backgroundImageContainerImageStyle(isTablet)}
|
|
229
310
|
/>
|
|
230
311
|
<AudioDescription
|
|
231
|
-
|
|
312
|
+
title={title}
|
|
313
|
+
summary={summary}
|
|
232
314
|
value={value}
|
|
233
315
|
layoutState={layoutState}
|
|
234
316
|
/>
|
|
@@ -245,6 +327,15 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
245
327
|
disableFullscreen={true}
|
|
246
328
|
sliderStyle={2}
|
|
247
329
|
/>
|
|
330
|
+
|
|
331
|
+
{showCounter && timeLeft >= 0 && (
|
|
332
|
+
<CountDown
|
|
333
|
+
timeLeft={timeLeft}
|
|
334
|
+
value={value}
|
|
335
|
+
title={sleepTitle}
|
|
336
|
+
/>
|
|
337
|
+
)}
|
|
338
|
+
|
|
248
339
|
<Separator />
|
|
249
340
|
</View>
|
|
250
341
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import * as React from "react";
|
|
2
2
|
import {
|
|
3
3
|
StyleSheet,
|
|
4
4
|
TouchableWithoutFeedback,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ViewStyle,
|
|
7
7
|
} from "react-native";
|
|
8
8
|
import * as R from "ramda";
|
|
9
|
-
import {
|
|
9
|
+
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
ContentInfo,
|
|
@@ -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,11 +198,11 @@ 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
|
-
const
|
|
202
|
-
deviceInfo: { isTablet },
|
|
203
|
-
} = useDimensions("screen", { deviceInfo: true });
|
|
205
|
+
const isTablet = useIsTablet();
|
|
204
206
|
|
|
205
207
|
const visible = true;
|
|
206
208
|
const liveButton = Boolean(value("live_badge_enabled"));
|
|
@@ -234,7 +236,11 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
234
236
|
!configuration.magic_background && backgroundColorStyle(value),
|
|
235
237
|
]}
|
|
236
238
|
>
|
|
237
|
-
<ContentInfo
|
|
239
|
+
<ContentInfo
|
|
240
|
+
content={{ ...entry, title, summary }}
|
|
241
|
+
value={value}
|
|
242
|
+
docked
|
|
243
|
+
/>
|
|
238
244
|
</View>
|
|
239
245
|
</View>
|
|
240
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(
|
|
@@ -4,10 +4,12 @@ import * as React from "react";
|
|
|
4
4
|
import { StyleSheet, View } from "react-native";
|
|
5
5
|
import LinearGradient from "../LinearGradient";
|
|
6
6
|
import { getDominantColor, getPalettes } from "../../../utils/utils";
|
|
7
|
+
import { useEffect } from "react";
|
|
7
8
|
|
|
8
9
|
type MagicBGProps = {
|
|
9
10
|
children: React.ReactNode;
|
|
10
11
|
image: string;
|
|
12
|
+
fallbackUrl?: string;
|
|
11
13
|
defaultColor: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
@@ -34,9 +36,24 @@ const styles = StyleSheet.create({
|
|
|
34
36
|
},
|
|
35
37
|
});
|
|
36
38
|
|
|
39
|
+
const fetchPaletteAndDominantColor = async (imageUrl: string) => {
|
|
40
|
+
const palette = await getPalettes(imageUrl);
|
|
41
|
+
|
|
42
|
+
const colors = palette.map(
|
|
43
|
+
(color: { r: number; g: number; b: number }) =>
|
|
44
|
+
`rgb(${color.r}, ${color.g}, ${color.b})`
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const dominant = await getDominantColor(imageUrl);
|
|
48
|
+
const dominantColor = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
|
|
49
|
+
|
|
50
|
+
return { gradientColors: colors.slice(0, 2), dominantColor };
|
|
51
|
+
};
|
|
52
|
+
|
|
37
53
|
export const MagicBG: React.FC<MagicBGProps> = ({
|
|
38
54
|
children,
|
|
39
55
|
image,
|
|
56
|
+
fallbackUrl = "",
|
|
40
57
|
defaultColor,
|
|
41
58
|
}) => {
|
|
42
59
|
const [dominantColor, setDominantColor] =
|
|
@@ -47,22 +64,24 @@ export const MagicBG: React.FC<MagicBGProps> = ({
|
|
|
47
64
|
colors.defaultGradientEnd,
|
|
48
65
|
]);
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
useEffect(() => {
|
|
51
68
|
const fetchColors = async () => {
|
|
52
|
-
if (image)
|
|
53
|
-
try {
|
|
54
|
-
const palette = await getPalettes(image);
|
|
69
|
+
if (!image) return;
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
);
|
|
71
|
+
try {
|
|
72
|
+
const { gradientColors, dominantColor } =
|
|
73
|
+
await fetchPaletteAndDominantColor(image);
|
|
60
74
|
|
|
61
|
-
|
|
75
|
+
setGradientColors(gradientColors);
|
|
76
|
+
setDominantColor(dominantColor);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
try {
|
|
79
|
+
const { gradientColors, dominantColor } =
|
|
80
|
+
await fetchPaletteAndDominantColor(fallbackUrl);
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
setDominantColor(
|
|
65
|
-
} catch
|
|
82
|
+
setGradientColors(gradientColors);
|
|
83
|
+
setDominantColor(dominantColor);
|
|
84
|
+
} catch {
|
|
66
85
|
setGradientColors([
|
|
67
86
|
colors.defaultGradientStart,
|
|
68
87
|
colors.defaultGradientEnd,
|
|
@@ -74,7 +93,7 @@ export const MagicBG: React.FC<MagicBGProps> = ({
|
|
|
74
93
|
};
|
|
75
94
|
|
|
76
95
|
fetchColors();
|
|
77
|
-
}, [image, defaultColor]);
|
|
96
|
+
}, [image, fallbackUrl, colors, defaultColor]);
|
|
78
97
|
|
|
79
98
|
return (
|
|
80
99
|
<View style={[styles.container, { backgroundColor: dominantColor }]}>
|
|
@@ -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
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import queryString from "query-string";
|
|
2
|
+
|
|
1
3
|
export const toTypeString = (obj) => {
|
|
2
4
|
switch (typeof obj) {
|
|
3
5
|
case "object":
|
|
@@ -19,3 +21,24 @@ export const stringsOnlyObject = (obj) => {
|
|
|
19
21
|
|
|
20
22
|
return strObj;
|
|
21
23
|
};
|
|
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
|
+
|
|
30
|
+
export const addQueryParam = (
|
|
31
|
+
baseUrl: string,
|
|
32
|
+
paramName: string,
|
|
33
|
+
paramValue: string | number
|
|
34
|
+
) => {
|
|
35
|
+
const [urlWithoutQuery, query] = baseUrl.split("?");
|
|
36
|
+
|
|
37
|
+
const parsedQuery = queryString.parse(query);
|
|
38
|
+
|
|
39
|
+
parsedQuery[paramName] = paramValue.toString();
|
|
40
|
+
|
|
41
|
+
const updatedQueryString = queryString.stringify(parsedQuery);
|
|
42
|
+
|
|
43
|
+
return `${urlWithoutQuery}?${updatedQueryString}`;
|
|
44
|
+
};
|
package/src/Player/index.tsx
CHANGED
|
@@ -36,6 +36,8 @@ import { AudioPlayerWrapper } from "./AudioLayer/AudioPlayerWrapper";
|
|
|
36
36
|
import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
37
37
|
import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/configurationUtils";
|
|
38
38
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
39
|
+
import { getTargetScreenDataFromEntry } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData";
|
|
40
|
+
import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
|
|
39
41
|
|
|
40
42
|
const RNPlayerManager = {
|
|
41
43
|
QuickBrickPlayerPlugin: isApplePlatform()
|
|
@@ -87,7 +89,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
87
89
|
// TODO: Can be deleted, on controller logic
|
|
88
90
|
paused: false,
|
|
89
91
|
piped: false,
|
|
90
|
-
rate: 1,
|
|
92
|
+
rate: 1.0,
|
|
91
93
|
// TODO: Hack for analytics, must br removed
|
|
92
94
|
resume: null,
|
|
93
95
|
|
|
@@ -368,6 +370,10 @@ export default class VideoPlayer extends React.Component<
|
|
|
368
370
|
}
|
|
369
371
|
};
|
|
370
372
|
|
|
373
|
+
setPlaybackRate = (rate = 1.0) => {
|
|
374
|
+
this.setState({ rate });
|
|
375
|
+
};
|
|
376
|
+
|
|
371
377
|
getCurrentTime = () => {
|
|
372
378
|
return this.state.currentTime;
|
|
373
379
|
};
|
|
@@ -551,6 +557,26 @@ export default class VideoPlayer extends React.Component<
|
|
|
551
557
|
this.props.listener?.onPlayerDetached?.(nativeEvent);
|
|
552
558
|
};
|
|
553
559
|
|
|
560
|
+
onVideoFullscreenPlayerDidDismiss = () => {
|
|
561
|
+
this.props.listener?.onVideoFullscreenPlayerDidDismiss?.();
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
onVideoFullscreenPlayerDidPresent = () => {
|
|
565
|
+
this.props.listener?.onVideoFullscreenPlayerDidPresent?.();
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
onVideoFullscreenPlayerWillDismiss = () => {
|
|
569
|
+
this.props.listener?.onVideoFullscreenPlayerWillDismiss?.();
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
onVideoFullscreenPlayerWillPresent = () => {
|
|
573
|
+
this.props.listener?.onVideoFullscreenPlayerWillPresent?.();
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
onPlayerClose = () => {
|
|
577
|
+
this.props.listener?.onPlayerClose?.();
|
|
578
|
+
};
|
|
579
|
+
|
|
554
580
|
onPiPEnter = () => {
|
|
555
581
|
this.setState({ piped: true });
|
|
556
582
|
this.props.openPiP();
|
|
@@ -755,7 +781,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
755
781
|
render() {
|
|
756
782
|
const {
|
|
757
783
|
castAction,
|
|
758
|
-
controls,
|
|
759
784
|
docked,
|
|
760
785
|
entry,
|
|
761
786
|
fullscreen,
|
|
@@ -767,7 +792,8 @@ export default class VideoPlayer extends React.Component<
|
|
|
767
792
|
style,
|
|
768
793
|
} = this.props;
|
|
769
794
|
|
|
770
|
-
const { audioTrackId, keyEvent, paused, seek, textTrackId } =
|
|
795
|
+
const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
|
|
796
|
+
this.state;
|
|
771
797
|
|
|
772
798
|
const isAudioItem =
|
|
773
799
|
entry?.content?.type?.includes?.("audio") ||
|
|
@@ -812,17 +838,32 @@ export default class VideoPlayer extends React.Component<
|
|
|
812
838
|
const liveCatchUpEnabled =
|
|
813
839
|
this.props.pluginConfiguration?.liveCatchUpEnabled;
|
|
814
840
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
841
|
+
// TODO: Get screen data more reliable way useCurrentScreenData
|
|
842
|
+
const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
|
|
843
|
+
|
|
844
|
+
const isInlineTV = screenData?.ui_components?.length > 0 && isTV();
|
|
818
845
|
|
|
819
|
-
|
|
846
|
+
let platformSpecificProps: any = isApplePlatform()
|
|
820
847
|
? {
|
|
821
|
-
...hideNativeControls,
|
|
822
848
|
resizeMode: nativeResizeMode,
|
|
823
849
|
}
|
|
824
850
|
: {};
|
|
825
851
|
|
|
852
|
+
const supportsNativeControls =
|
|
853
|
+
!!this.props.controller?.supportsNativeControls?.();
|
|
854
|
+
|
|
855
|
+
const controls = isAppleTV()
|
|
856
|
+
? supportsNativeControls && isInlineTV && !this.getPlayNextData()
|
|
857
|
+
: supportsNativeControls && !this.getPlayNextData();
|
|
858
|
+
|
|
859
|
+
const shouldUseNativeControls =
|
|
860
|
+
controls && supportsNativeControls && !docked;
|
|
861
|
+
|
|
862
|
+
platformSpecificProps = {
|
|
863
|
+
...platformSpecificProps,
|
|
864
|
+
controls: shouldUseNativeControls,
|
|
865
|
+
};
|
|
866
|
+
|
|
826
867
|
const pictureInPictureEnabled =
|
|
827
868
|
this.props.pluginConfiguration?.pictureInPictureEnabled;
|
|
828
869
|
|
|
@@ -830,7 +871,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
830
871
|
entry, // Must be passed first in list
|
|
831
872
|
...platformSpecificProps,
|
|
832
873
|
audioTrackId,
|
|
833
|
-
controls,
|
|
834
874
|
docked,
|
|
835
875
|
enableAutoAudioTrackSelection,
|
|
836
876
|
fullscreen, // TODO: Check fullscreen params not crash
|
|
@@ -839,6 +879,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
839
879
|
nowPlayingEnabled,
|
|
840
880
|
paused,
|
|
841
881
|
seek,
|
|
882
|
+
rate,
|
|
842
883
|
textTrackId,
|
|
843
884
|
liveSeekingEnabled,
|
|
844
885
|
liveCatchUpEnabled,
|
|
@@ -884,22 +925,17 @@ export default class VideoPlayer extends React.Component<
|
|
|
884
925
|
onVideoEnd: this.onVideoEnd, // complete
|
|
885
926
|
onVideoError: this.onVideoError, // error
|
|
886
927
|
onVideoProgress: this.onVideoProgress,
|
|
887
|
-
onVideoFullscreenPlayerDidDismiss: this.
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
onVideoFullscreenPlayerWillDismiss: this.unHandledEvent(
|
|
894
|
-
"onVideoFullscreenPlayerWillDismiss"
|
|
895
|
-
),
|
|
896
|
-
onVideoFullscreenPlayerWillPresent: this.unHandledEvent(
|
|
897
|
-
"onVideoFullscreenPlayerWillPresent"
|
|
898
|
-
),
|
|
928
|
+
onVideoFullscreenPlayerDidDismiss: this.onVideoFullscreenPlayerDidDismiss,
|
|
929
|
+
onVideoFullscreenPlayerDidPresent: this.onVideoFullscreenPlayerDidPresent,
|
|
930
|
+
onVideoFullscreenPlayerWillDismiss:
|
|
931
|
+
this.onVideoFullscreenPlayerWillDismiss,
|
|
932
|
+
onVideoFullscreenPlayerWillPresent:
|
|
933
|
+
this.onVideoFullscreenPlayerWillPresent,
|
|
899
934
|
onVideoLoad: this.onVideoLoad,
|
|
900
935
|
onVideoLoadStart: this.unHandledEvent("onVideoLoadStart"),
|
|
901
936
|
onVideoBuffer: this.onVideoBuffer,
|
|
902
937
|
onPlaybackStalled: this.onPlaybackStalled,
|
|
938
|
+
onPlayerClose: this.onPlayerClose,
|
|
903
939
|
onPlayerAttached: this.onPlayerAttached,
|
|
904
940
|
onPlayerDetached: this.onPlayerDetached,
|
|
905
941
|
};
|
|
@@ -973,7 +1009,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
973
1009
|
!needToShowPlayNextOverlay &&
|
|
974
1010
|
this.props.mode !== "PIP";
|
|
975
1011
|
|
|
976
|
-
if (
|
|
1012
|
+
if (shouldUseNativeControls) {
|
|
977
1013
|
return null;
|
|
978
1014
|
}
|
|
979
1015
|
|
|
@@ -1005,10 +1041,12 @@ export default class VideoPlayer extends React.Component<
|
|
|
1005
1041
|
inline,
|
|
1006
1042
|
docked,
|
|
1007
1043
|
isModal,
|
|
1044
|
+
pip: this.state.piped,
|
|
1008
1045
|
layoutState: {
|
|
1009
1046
|
inline,
|
|
1010
1047
|
docked,
|
|
1011
1048
|
isModal,
|
|
1049
|
+
pip: this.state.piped,
|
|
1012
1050
|
},
|
|
1013
1051
|
configuration: this.getPluginConfiguration(),
|
|
1014
1052
|
fullscreen: fullscreen,
|
|
@@ -1085,6 +1123,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1085
1123
|
listener={this.props.listener}
|
|
1086
1124
|
style={getStyle(playerDimensions)}
|
|
1087
1125
|
pluginConfiguration={this.getPluginConfiguration()}
|
|
1126
|
+
controller={this.props.controller}
|
|
1088
1127
|
>
|
|
1089
1128
|
{this.childrenWithPlayNext()}
|
|
1090
1129
|
</this.props.PlayerComponent>
|
|
@@ -1121,6 +1160,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1121
1160
|
listener={this.props.listener}
|
|
1122
1161
|
style={this.takeParentStyle}
|
|
1123
1162
|
pluginConfiguration={this.getPluginConfiguration()}
|
|
1163
|
+
controller={this.props.controller}
|
|
1124
1164
|
>
|
|
1125
1165
|
{this.childrenWithPlayNext()}
|
|
1126
1166
|
</this.props.PlayerComponent>
|
package/src/QuickBrickPlayer.tsx
CHANGED
|
@@ -1,55 +1,13 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { useOrientationEffect } from "@applicaster/zapp-react-native-utils/reactHooks/videoModal";
|
|
4
4
|
import Player from "./Player";
|
|
5
5
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
6
6
|
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
7
7
|
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
8
|
-
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
9
|
-
import { isEqual } from "@applicaster/zapp-react-native-utils/equalUtils";
|
|
10
|
-
import { toNoopIfNil } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
11
8
|
|
|
12
9
|
const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
|
|
13
10
|
|
|
14
|
-
const MemoizedPlayer = React.memo(
|
|
15
|
-
Player,
|
|
16
|
-
isEqual({
|
|
17
|
-
byValue: [
|
|
18
|
-
"PlayerComponent",
|
|
19
|
-
"autoplay",
|
|
20
|
-
"controls",
|
|
21
|
-
"disableCastAction",
|
|
22
|
-
"docked",
|
|
23
|
-
"focused",
|
|
24
|
-
"fullscreen",
|
|
25
|
-
"inline",
|
|
26
|
-
"isModal",
|
|
27
|
-
"isTabletPortrait",
|
|
28
|
-
"muted",
|
|
29
|
-
"playerId",
|
|
30
|
-
"playerPluginIdentifier",
|
|
31
|
-
"mode",
|
|
32
|
-
"style",
|
|
33
|
-
],
|
|
34
|
-
byReference: [
|
|
35
|
-
"controller",
|
|
36
|
-
"listener",
|
|
37
|
-
"playNextData",
|
|
38
|
-
"entry",
|
|
39
|
-
"playableItem",
|
|
40
|
-
"source",
|
|
41
|
-
"playerEvent",
|
|
42
|
-
"playerPluginManifest",
|
|
43
|
-
"pluginConfiguration",
|
|
44
|
-
"setNextVideoPreloadThresholdPercentage",
|
|
45
|
-
"toggleFullscreen",
|
|
46
|
-
"openPiP",
|
|
47
|
-
"closePiP",
|
|
48
|
-
"castAction",
|
|
49
|
-
],
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
|
|
53
11
|
const QuickBrickPlayerComponent = (
|
|
54
12
|
props: QuickBrickPlayer.PlayerProps,
|
|
55
13
|
ref
|
|
@@ -67,7 +25,7 @@ const QuickBrickPlayerComponent = (
|
|
|
67
25
|
|
|
68
26
|
const castAction = useActions(CHROMECAST_IDENTIFIER);
|
|
69
27
|
|
|
70
|
-
const isModal =
|
|
28
|
+
const isModal = Boolean(props?.isModal);
|
|
71
29
|
const isScreenDisabled = !controller?.isFullScreenSupported() || !isModal;
|
|
72
30
|
|
|
73
31
|
const {
|
|
@@ -77,24 +35,16 @@ const QuickBrickPlayerComponent = (
|
|
|
77
35
|
} = useNavigation();
|
|
78
36
|
|
|
79
37
|
useOrientationEffect({
|
|
80
|
-
isTabletPortrait:
|
|
38
|
+
isTabletPortrait: props?.isTabletPortrait,
|
|
81
39
|
docked: mode === "MINIMIZED",
|
|
82
|
-
toggleFullscreen:
|
|
40
|
+
toggleFullscreen: props?.toggleFullscreen,
|
|
83
41
|
disable: isScreenDisabled,
|
|
84
42
|
skipInitialOrientationEffects: mode === "FULLSCREEN",
|
|
85
|
-
isRunningPlayNext:
|
|
43
|
+
isRunningPlayNext: !!props?.playNextData,
|
|
86
44
|
});
|
|
87
45
|
|
|
88
|
-
// console.log("debug_2", "QuickBrickPlayerComponent - render", {
|
|
89
|
-
// mode,
|
|
90
|
-
// props,
|
|
91
|
-
// disableCastAction: props?.disableCastAction,
|
|
92
|
-
// castAction,
|
|
93
|
-
// PlayerComponent: props.PlayerComponent,
|
|
94
|
-
// });
|
|
95
|
-
|
|
96
46
|
return (
|
|
97
|
-
<
|
|
47
|
+
<Player
|
|
98
48
|
{...props}
|
|
99
49
|
ref={ref}
|
|
100
50
|
castAction={!props?.disableCastAction && castAction}
|