@applicaster/quick-brick-player 12.0.0-alpha.5040434238 → 12.0.0-alpha.5050333009
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 -6
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +8 -10
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +102 -43
- package/src/Player/AudioLayer/Layout/DockedLayout.tsx +56 -22
- package/src/Player/AudioLayer/MagicBG/index.tsx +47 -15
- package/src/Player/Utils/Utils.ts +9 -22
- package/src/Player/index.tsx +57 -46
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.5050333009",
|
|
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-
|
|
40
|
-
"@applicaster/quick-brick-tv-transport-controls": "
|
|
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.
|
|
39
|
+
"@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.96",
|
|
40
|
+
"@applicaster/quick-brick-tv-transport-controls": "12.0.0-alpha.5050333009",
|
|
41
|
+
"@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.5050333009",
|
|
42
|
+
"@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.5050333009",
|
|
43
|
+
"@applicaster/zapp-react-native-utils": "12.0.0-alpha.5050333009",
|
|
44
44
|
"keymirror": "^0.1.1",
|
|
45
45
|
"query-string": "7.1.3",
|
|
46
46
|
"react-native-web-linear-gradient": "1.1.2",
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
AnimatedScrollModal,
|
|
33
33
|
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
34
34
|
import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
|
|
35
|
+
import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
35
36
|
|
|
36
37
|
const { log_debug } = createLogger({
|
|
37
38
|
subsystem: "UIComponent",
|
|
@@ -294,7 +295,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
294
295
|
playerState.isPaused ? player?.play() : player.pause();
|
|
295
296
|
}, [playerState, player]);
|
|
296
297
|
|
|
297
|
-
const seekDuration =
|
|
298
|
+
const seekDuration = toNumberWithDefault(10, value("seek_duration"));
|
|
298
299
|
|
|
299
300
|
const onRewindButtonPress = React.useCallback(() => {
|
|
300
301
|
player?.rewind(seekDuration);
|
|
@@ -377,10 +378,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
377
378
|
player?.seekTo(0);
|
|
378
379
|
}, [player]);
|
|
379
380
|
|
|
380
|
-
const edges = React.useMemo<Edge[]>(
|
|
381
|
-
() => (["right", "left", "top"]),
|
|
382
|
-
[docked]
|
|
383
|
-
);
|
|
381
|
+
const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
|
|
384
382
|
|
|
385
383
|
const ContainerView = SafeAreaView;
|
|
386
384
|
|
|
@@ -390,12 +388,10 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
390
388
|
<AnimationComponent
|
|
391
389
|
animationType={ComponentAnimationType.moveUpComponent}
|
|
392
390
|
additionalData={{ saveArea: true }}
|
|
391
|
+
style={Styles.playerContainer}
|
|
393
392
|
>
|
|
394
393
|
<AnimatedScrollModal>
|
|
395
|
-
<View
|
|
396
|
-
style={Styles.playerContainer}
|
|
397
|
-
onLayout={onLayout}
|
|
398
|
-
>
|
|
394
|
+
<View style={Styles.playerContainer} onLayout={onLayout}>
|
|
399
395
|
<Layout
|
|
400
396
|
entry={entry}
|
|
401
397
|
playerState={playerState}
|
|
@@ -426,7 +422,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
426
422
|
/>
|
|
427
423
|
<View style={Styles.paddingHorizontal}>
|
|
428
424
|
{children}
|
|
429
|
-
<AnimationComponent
|
|
425
|
+
<AnimationComponent
|
|
426
|
+
animationType={ComponentAnimationType.componentFade}
|
|
427
|
+
>
|
|
430
428
|
<PlayerDetails
|
|
431
429
|
configuration={configuration}
|
|
432
430
|
style={Styles.playerDetails}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import { View } from "react-native";
|
|
4
|
+
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
ProgressBar,
|
|
@@ -27,8 +28,13 @@ import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-compone
|
|
|
27
28
|
import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
|
|
28
29
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
29
30
|
import { useEffect, useMemo, useState } from "react";
|
|
30
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
useModalAnimationContext,
|
|
33
|
+
AnimationComponent,
|
|
34
|
+
ComponentAnimationType,
|
|
35
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
31
36
|
import DockedLayout from "./DockedLayout";
|
|
37
|
+
import { isValidDate } from "../../Utils";
|
|
32
38
|
|
|
33
39
|
type Dimensions = {
|
|
34
40
|
width: number;
|
|
@@ -75,7 +81,7 @@ type LayoutProps = {
|
|
|
75
81
|
|
|
76
82
|
const Styles = {
|
|
77
83
|
container: { flex: 1 },
|
|
78
|
-
tabletContainer: (isPortraitMode) => ({
|
|
84
|
+
tabletContainer: (isPortraitMode: boolean) => ({
|
|
79
85
|
flex: 1,
|
|
80
86
|
alignItems: "center",
|
|
81
87
|
justifyContent: isPortraitMode ? "center" : "flex-start",
|
|
@@ -95,19 +101,31 @@ const Styles = {
|
|
|
95
101
|
justifyContent: isLiveShowFullControls ? "space-between" : "space-evenly",
|
|
96
102
|
}),
|
|
97
103
|
backgroundImageContainer: (dimensions: Dimensions, isTablet: boolean) => {
|
|
98
|
-
const mobileValue = dimensions.width
|
|
104
|
+
const mobileValue = dimensions.width
|
|
105
|
+
? dimensions.width - PADDING_HORIZONTAL * 2
|
|
106
|
+
: 330;
|
|
99
107
|
|
|
100
108
|
return {
|
|
101
109
|
width: isTablet ? 463 : mobileValue,
|
|
102
110
|
height: isTablet ? 463 : mobileValue,
|
|
103
111
|
};
|
|
104
112
|
},
|
|
105
|
-
backgroundImageContainerImageStyle: (
|
|
106
|
-
|
|
113
|
+
backgroundImageContainerImageStyle: (
|
|
114
|
+
isTablet: boolean,
|
|
115
|
+
docked: boolean,
|
|
116
|
+
startComponentsAnimation: boolean,
|
|
117
|
+
isActiveGesture: boolean
|
|
118
|
+
) => ({
|
|
119
|
+
borderRadius:
|
|
120
|
+
(startComponentsAnimation && isActiveGesture) || docked
|
|
121
|
+
? 0
|
|
122
|
+
: isTablet
|
|
123
|
+
? 24
|
|
124
|
+
: 10,
|
|
107
125
|
}),
|
|
108
126
|
|
|
109
127
|
paddingHorizontal: {
|
|
110
|
-
paddingHorizontal: PADDING_HORIZONTAL
|
|
128
|
+
paddingHorizontal: PADDING_HORIZONTAL,
|
|
111
129
|
},
|
|
112
130
|
|
|
113
131
|
controlsMobile: {
|
|
@@ -174,8 +192,31 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
174
192
|
|
|
175
193
|
let showFullControls = !controller?.isAd();
|
|
176
194
|
|
|
195
|
+
const startTime = entry.extensions?.live?.start_time;
|
|
196
|
+
const endTime = entry.extensions?.live?.end_time;
|
|
197
|
+
|
|
198
|
+
const hasValidStartTime = isValidDate(startTime);
|
|
199
|
+
const hasValidEndTime = isValidDate(endTime);
|
|
200
|
+
|
|
201
|
+
const isDateBasedLiveContent =
|
|
202
|
+
isLiveContent && hasValidStartTime && hasValidEndTime;
|
|
203
|
+
|
|
204
|
+
const needsToShowProgressBar =
|
|
205
|
+
(isLiveContent &&
|
|
206
|
+
(Boolean(value("liveSeekingEnabled")) || isDateBasedLiveContent)) ||
|
|
207
|
+
!isLiveContent;
|
|
208
|
+
|
|
209
|
+
const startTimestamp = hasValidStartTime
|
|
210
|
+
? dayjs.utc(startTime!).valueOf()
|
|
211
|
+
: undefined;
|
|
212
|
+
|
|
213
|
+
const endTimestamp = hasValidEndTime
|
|
214
|
+
? dayjs.utc(endTime!).valueOf()
|
|
215
|
+
: undefined;
|
|
216
|
+
|
|
177
217
|
if (isLiveContent) {
|
|
178
218
|
const seekEnabled = Boolean(value("liveSeekingEnabled"));
|
|
219
|
+
|
|
179
220
|
showFullControls = !!(controller?.hasSeekableDuration?.() && seekEnabled);
|
|
180
221
|
}
|
|
181
222
|
|
|
@@ -205,12 +246,14 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
205
246
|
|
|
206
247
|
const rewind = rewindButton({
|
|
207
248
|
isTablet,
|
|
208
|
-
onPress: controller?.
|
|
249
|
+
onPress: controller?.hasSeekableDuration?.() ? onRewindButtonPress : null,
|
|
209
250
|
});
|
|
210
251
|
|
|
211
252
|
const forward = forwardButton({
|
|
212
253
|
isTablet,
|
|
213
|
-
onPress: controller?.
|
|
254
|
+
onPress: controller?.hasSeekableDuration?.()
|
|
255
|
+
? onForwardButtonPress
|
|
256
|
+
: null,
|
|
214
257
|
});
|
|
215
258
|
|
|
216
259
|
const speed = speedButton({
|
|
@@ -239,7 +282,6 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
239
282
|
return [skipToStart, rewind, play, forward, skipToLive];
|
|
240
283
|
}
|
|
241
284
|
|
|
242
|
-
// Temporary empty button until we add sleep button. It will ensure the buttons will not break in their position
|
|
243
285
|
return [speed, rewind, play, forward, sleep];
|
|
244
286
|
}, [
|
|
245
287
|
playerState,
|
|
@@ -260,15 +302,12 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
260
302
|
|
|
261
303
|
useEffect(() => {
|
|
262
304
|
if (showCounter && sleepTimerDate !== 0) {
|
|
263
|
-
// Setting the initial timeLeft based on sleepTimerDate
|
|
264
305
|
setTimeLeft(sleepTimerDate - Date.now());
|
|
265
306
|
|
|
266
|
-
// Setting up an interval to update the remaining time every second
|
|
267
307
|
const intervalId = setInterval(() => {
|
|
268
308
|
setTimeLeft(() => {
|
|
269
309
|
const newTimeLeft = sleepTimerDate - Date.now();
|
|
270
310
|
|
|
271
|
-
// If the new remaining time is less than or equal to 0, clear the interval and trigger the cancelSleepTimer function
|
|
272
311
|
if (newTimeLeft <= 0) {
|
|
273
312
|
clearInterval(intervalId);
|
|
274
313
|
|
|
@@ -276,31 +315,28 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
276
315
|
|
|
277
316
|
return 0;
|
|
278
317
|
} else {
|
|
279
|
-
// Otherwise, update the state with the new remaining time
|
|
280
318
|
return newTimeLeft;
|
|
281
319
|
}
|
|
282
320
|
});
|
|
283
321
|
}, 1000);
|
|
284
322
|
|
|
285
|
-
// Clearing the interval when the component unmounts or dependencies change
|
|
286
323
|
return () => clearInterval(intervalId);
|
|
287
324
|
} else {
|
|
288
|
-
// Ensure the timeLeft state is set correctly if showCounter is false
|
|
289
325
|
setTimeLeft(0);
|
|
290
326
|
}
|
|
291
327
|
}, [sleepTimerDate, showCounter, cancelSleepTimer]);
|
|
292
328
|
|
|
293
329
|
const Separator = () => <View style={Styles.separator} />;
|
|
294
330
|
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
331
|
+
const defaultImageDimensions = React.useMemo(
|
|
332
|
+
() =>
|
|
333
|
+
defaultDimensions &&
|
|
334
|
+
Styles.backgroundImageContainer(defaultDimensions, isTablet),
|
|
335
|
+
[defaultDimensions, isTablet]
|
|
336
|
+
);
|
|
299
337
|
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const { isActiveGesture, startComponentsAnimation } = useModalAnimationContext();
|
|
338
|
+
const { isActiveGesture, startComponentsAnimation } =
|
|
339
|
+
useModalAnimationContext();
|
|
304
340
|
|
|
305
341
|
return (
|
|
306
342
|
<View
|
|
@@ -318,34 +354,50 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
318
354
|
actionButtons={actionButtons}
|
|
319
355
|
/>
|
|
320
356
|
</AnimationComponent>
|
|
321
|
-
<AnimationComponent
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
357
|
+
<AnimationComponent
|
|
358
|
+
animationType={ComponentAnimationType.moveUpComponent}
|
|
359
|
+
style={isTablet ? Styles.playerImageWrapper : undefined}
|
|
360
|
+
>
|
|
361
|
+
<AnimationComponent
|
|
362
|
+
animationType={ComponentAnimationType.moveComponentHorizontal}
|
|
363
|
+
additionalData={{
|
|
364
|
+
disableAnimatedComponent: !isTablet,
|
|
365
|
+
moveValue: (dimensions.width - 463) / 2,
|
|
366
|
+
extraAnimation: !isTablet
|
|
367
|
+
? {
|
|
368
|
+
[ComponentAnimationType.audioPlayerPaddingHorizontal]: {
|
|
369
|
+
style: Styles.paddingHorizontal,
|
|
370
|
+
},
|
|
331
371
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
372
|
+
: null,
|
|
373
|
+
}}
|
|
374
|
+
style={isTablet ? Styles.alignItemsCenter : undefined}
|
|
375
|
+
>
|
|
336
376
|
<PlayerImageBackground
|
|
337
377
|
entry={entry}
|
|
338
378
|
style={Styles.backgroundImageContainer(dimensions, isTablet)}
|
|
339
|
-
imageStyle={Styles.backgroundImageContainerImageStyle(
|
|
379
|
+
imageStyle={Styles.backgroundImageContainerImageStyle(
|
|
380
|
+
isTablet,
|
|
381
|
+
docked,
|
|
382
|
+
startComponentsAnimation,
|
|
383
|
+
isActiveGesture
|
|
384
|
+
)}
|
|
340
385
|
defaultImageDimensions={defaultImageDimensions}
|
|
341
386
|
/>
|
|
342
387
|
</AnimationComponent>
|
|
343
388
|
|
|
344
389
|
<DockedLayout {...props} />
|
|
345
390
|
</AnimationComponent>
|
|
346
|
-
<View
|
|
347
|
-
{
|
|
348
|
-
|
|
391
|
+
<View
|
|
392
|
+
style={[
|
|
393
|
+
Styles.paddingHorizontal,
|
|
394
|
+
isTablet ? Styles.tabletLayout : undefined,
|
|
395
|
+
]}
|
|
396
|
+
>
|
|
397
|
+
{!docked && (
|
|
398
|
+
<AnimationComponent
|
|
399
|
+
animationType={ComponentAnimationType.componentFade}
|
|
400
|
+
>
|
|
349
401
|
<AudioDescription
|
|
350
402
|
title={title}
|
|
351
403
|
summary={summary}
|
|
@@ -364,6 +416,10 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
364
416
|
playerState={playerState}
|
|
365
417
|
disableFullscreen={true}
|
|
366
418
|
sliderStyle={2}
|
|
419
|
+
startTimestamp={startTimestamp}
|
|
420
|
+
endTimestamp={endTimestamp}
|
|
421
|
+
isDateBasedLiveContent={isDateBasedLiveContent}
|
|
422
|
+
onSkipLive={onSkipLive}
|
|
367
423
|
/>
|
|
368
424
|
|
|
369
425
|
{showCounter && timeLeft >= 0 && (
|
|
@@ -382,11 +438,14 @@ export default function DefaultLayout(props: LayoutProps) {
|
|
|
382
438
|
visible={true}
|
|
383
439
|
keepAlive={null}
|
|
384
440
|
buttons={controlButtons}
|
|
385
|
-
style={Styles.controls(
|
|
441
|
+
style={Styles.controls(
|
|
442
|
+
isTablet,
|
|
443
|
+
isLiveContent && showFullControls
|
|
444
|
+
)}
|
|
386
445
|
playerState={playerState}
|
|
387
446
|
/>
|
|
388
447
|
</AnimationComponent>
|
|
389
|
-
}
|
|
448
|
+
)}
|
|
390
449
|
</View>
|
|
391
450
|
</View>
|
|
392
451
|
);
|
|
@@ -109,18 +109,24 @@ const backgroundColorStyle = (value: GetValue): ViewStyle => ({
|
|
|
109
109
|
backgroundColor: value("docked_player_background_color"),
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
const playerDimensionsStyle = (
|
|
113
|
-
|
|
112
|
+
const playerDimensionsStyle = (
|
|
113
|
+
minimisedHeight: number,
|
|
114
|
+
addPadding: boolean
|
|
115
|
+
): ViewStyle => ({
|
|
116
|
+
paddingLeft: addPadding ? minimisedHeight || 100 : 0,
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
const animatedContainerStyle = (docked: boolean): ViewStyle => ({
|
|
117
|
-
top: docked ? 2 : null,
|
|
118
120
|
position: "absolute",
|
|
119
121
|
width: "100%",
|
|
120
122
|
zIndex: docked ? 1 : -1,
|
|
121
123
|
});
|
|
122
124
|
|
|
123
|
-
const backgroundImageStyle = (
|
|
125
|
+
const backgroundImageStyle = (
|
|
126
|
+
dimensions: Dimensions,
|
|
127
|
+
minimisedHeight: number,
|
|
128
|
+
isActiveGesture: boolean
|
|
129
|
+
): ViewStyle => ({
|
|
124
130
|
height: isActiveGesture ? minimisedHeight : dimensions.height,
|
|
125
131
|
aspectRatio: 1,
|
|
126
132
|
position: "absolute",
|
|
@@ -215,32 +221,60 @@ export default function DockedLayout(props: LayoutProps) {
|
|
|
215
221
|
|
|
216
222
|
const visible = true;
|
|
217
223
|
const liveButton = Boolean(value("live_badge_enabled"));
|
|
218
|
-
|
|
219
|
-
const
|
|
224
|
+
|
|
225
|
+
const {
|
|
226
|
+
isActiveGesture,
|
|
227
|
+
setPlayerAnimationState,
|
|
228
|
+
startComponentsAnimation,
|
|
229
|
+
minimisedHeight,
|
|
230
|
+
} = useModalAnimationContext();
|
|
231
|
+
|
|
232
|
+
const maximazeVideoModal = React.useCallback(
|
|
233
|
+
() => setPlayerAnimationState(PlayerAnimationStateEnum.maximaze),
|
|
234
|
+
[]
|
|
235
|
+
);
|
|
220
236
|
|
|
221
237
|
if (!layoutState.docked && !startComponentsAnimation) return null;
|
|
222
|
-
|
|
238
|
+
|
|
223
239
|
return (
|
|
224
|
-
<AnimationComponent
|
|
225
|
-
|
|
240
|
+
<AnimationComponent
|
|
241
|
+
animationType={ComponentAnimationType.componentAppears}
|
|
242
|
+
style={[
|
|
243
|
+
styles.container,
|
|
244
|
+
animatedContainerStyle(isActiveGesture || layoutState.docked),
|
|
245
|
+
]}
|
|
226
246
|
>
|
|
227
|
-
{!playerState.isLive &&
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
{!playerState.isLive &&
|
|
248
|
+
(layoutState.docked || startComponentsAnimation) && (
|
|
249
|
+
<ProgressBar
|
|
250
|
+
content={entry}
|
|
251
|
+
value={value}
|
|
252
|
+
visible={visible}
|
|
253
|
+
layoutState={layoutState}
|
|
254
|
+
onPlayerSeek={onPlayerSeek}
|
|
255
|
+
playerState={playerState}
|
|
256
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
257
|
+
docked
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
238
260
|
|
|
239
261
|
<View style={styles.content}>
|
|
240
|
-
<View
|
|
262
|
+
<View
|
|
263
|
+
style={backgroundImageStyle(
|
|
264
|
+
dimensions,
|
|
265
|
+
minimisedHeight,
|
|
266
|
+
isActiveGesture
|
|
267
|
+
)}
|
|
268
|
+
/>
|
|
241
269
|
<TouchableWithoutFeedback onPress={maximazeVideoModal}>
|
|
242
270
|
<View
|
|
243
|
-
style={[
|
|
271
|
+
style={[
|
|
272
|
+
styles.touchableArea,
|
|
273
|
+
playerDimensionsStyle(
|
|
274
|
+
minimisedHeight,
|
|
275
|
+
isActiveGesture || layoutState.docked
|
|
276
|
+
),
|
|
277
|
+
]}
|
|
244
278
|
>
|
|
245
279
|
<View
|
|
246
280
|
style={[
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// Disabling for now as lint can't recognise typescript definition here
|
|
2
1
|
/* eslint-disable react/prop-types */
|
|
3
2
|
import * as React from "react";
|
|
4
|
-
import { StyleSheet, View } from "react-native";
|
|
3
|
+
import { Animated, StyleSheet, View } from "react-native";
|
|
5
4
|
import LinearGradient from "../LinearGradient";
|
|
6
5
|
import { getDominantColor, getPalettes } from "../../../utils/utils";
|
|
7
6
|
import { useEffect } from "react";
|
|
@@ -11,7 +10,7 @@ type MagicBGProps = {
|
|
|
11
10
|
image: string;
|
|
12
11
|
fallbackUrl?: string;
|
|
13
12
|
defaultColor: string;
|
|
14
|
-
colors: string[];
|
|
13
|
+
colors: string[];
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
const defaultColors = {
|
|
@@ -66,31 +65,46 @@ export const MagicBG: React.FC<MagicBGProps> = ({
|
|
|
66
65
|
defaultColors.defaultGradientEnd,
|
|
67
66
|
]);
|
|
68
67
|
|
|
68
|
+
const [backgroundColorAnim] = React.useState(new Animated.Value(0)); // from 0 to 1
|
|
69
|
+
const [gradientOpacityAnim] = React.useState(new Animated.Value(0)); // from 0 to 1
|
|
70
|
+
|
|
69
71
|
useEffect(() => {
|
|
72
|
+
const animateColors = () => {
|
|
73
|
+
Animated.timing(backgroundColorAnim, {
|
|
74
|
+
toValue: 1,
|
|
75
|
+
duration: 500,
|
|
76
|
+
useNativeDriver: false,
|
|
77
|
+
}).start();
|
|
78
|
+
|
|
79
|
+
Animated.timing(gradientOpacityAnim, {
|
|
80
|
+
toValue: 1,
|
|
81
|
+
duration: 500,
|
|
82
|
+
useNativeDriver: false,
|
|
83
|
+
}).start();
|
|
84
|
+
};
|
|
85
|
+
|
|
70
86
|
const fetchColors = async () => {
|
|
71
|
-
// Check if colors are provided and not empty
|
|
72
87
|
if (colors && colors.length > 0) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
setGradientColors(colors.slice(0, 2));
|
|
89
|
+
setDominantColor(colors[0]);
|
|
90
|
+
animateColors();
|
|
76
91
|
} else if (image) {
|
|
77
|
-
// If no colors, try fetching from image
|
|
78
92
|
try {
|
|
79
93
|
const { gradientColors, dominantColor } =
|
|
80
94
|
await fetchPaletteAndDominantColor(image);
|
|
81
95
|
|
|
82
96
|
setGradientColors(gradientColors);
|
|
83
97
|
setDominantColor(dominantColor);
|
|
98
|
+
animateColors();
|
|
84
99
|
} catch (error) {
|
|
85
|
-
// If image fetch fails, try fallback URL
|
|
86
100
|
try {
|
|
87
101
|
const { gradientColors, dominantColor } =
|
|
88
102
|
await fetchPaletteAndDominantColor(fallbackUrl);
|
|
89
103
|
|
|
90
104
|
setGradientColors(gradientColors);
|
|
91
105
|
setDominantColor(dominantColor);
|
|
106
|
+
animateColors();
|
|
92
107
|
} catch {
|
|
93
|
-
// If everything fails, use default color
|
|
94
108
|
setGradientColors([
|
|
95
109
|
defaultColors.defaultGradientStart,
|
|
96
110
|
defaultColors.defaultGradientEnd,
|
|
@@ -100,7 +114,6 @@ export const MagicBG: React.FC<MagicBGProps> = ({
|
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
} else {
|
|
103
|
-
// If no image or colors, use default color
|
|
104
117
|
setGradientColors([
|
|
105
118
|
defaultColors.defaultGradientStart,
|
|
106
119
|
defaultColors.defaultGradientEnd,
|
|
@@ -111,13 +124,32 @@ export const MagicBG: React.FC<MagicBGProps> = ({
|
|
|
111
124
|
};
|
|
112
125
|
|
|
113
126
|
fetchColors();
|
|
114
|
-
}, [
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
const interpolatedBackgroundColor = backgroundColorAnim.interpolate({
|
|
130
|
+
inputRange: [0, 1],
|
|
131
|
+
outputRange: ["black", dominantColor],
|
|
132
|
+
});
|
|
115
133
|
|
|
116
134
|
return (
|
|
117
|
-
<View
|
|
135
|
+
<Animated.View
|
|
136
|
+
style={[
|
|
137
|
+
styles.container,
|
|
138
|
+
{ backgroundColor: interpolatedBackgroundColor },
|
|
139
|
+
]}
|
|
140
|
+
>
|
|
118
141
|
<View style={styles.fullScreenOverlay} />
|
|
119
|
-
|
|
142
|
+
|
|
143
|
+
<Animated.View
|
|
144
|
+
style={[styles.gradientOverlay, { opacity: gradientOpacityAnim }]}
|
|
145
|
+
>
|
|
146
|
+
<LinearGradient
|
|
147
|
+
colors={gradientColors}
|
|
148
|
+
style={StyleSheet.absoluteFill}
|
|
149
|
+
/>
|
|
150
|
+
</Animated.View>
|
|
151
|
+
|
|
120
152
|
{children}
|
|
121
|
-
</View>
|
|
153
|
+
</Animated.View>
|
|
122
154
|
);
|
|
123
155
|
};
|
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
import queryString from "query-string";
|
|
2
|
-
|
|
3
|
-
export const toTypeString = (obj) => {
|
|
4
|
-
switch (typeof obj) {
|
|
5
|
-
case "object":
|
|
6
|
-
return obj instanceof Date ? obj.toISOString() : JSON.stringify(obj); // object, null
|
|
7
|
-
case "undefined":
|
|
8
|
-
return "";
|
|
9
|
-
default:
|
|
10
|
-
// boolean, number, string
|
|
11
|
-
return obj.toString();
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const stringsOnlyObject = (obj) => {
|
|
16
|
-
const strObj = {};
|
|
17
|
-
|
|
18
|
-
Object.keys(obj).forEach((x) => {
|
|
19
|
-
strObj[x] = toTypeString(obj[x]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return strObj;
|
|
23
|
-
};
|
|
2
|
+
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
24
3
|
|
|
25
4
|
export const getLiveUrl = (url: string) => {
|
|
26
5
|
// Use the replace method with a regular expression to replace all instances of subClass with livePage
|
|
@@ -42,3 +21,11 @@ export const addQueryParam = (
|
|
|
42
21
|
|
|
43
22
|
return `${urlWithoutQuery}?${updatedQueryString}`;
|
|
44
23
|
};
|
|
24
|
+
|
|
25
|
+
export const isValidDate = (dateString: string | undefined): boolean => {
|
|
26
|
+
return (
|
|
27
|
+
typeof dateString === "string" &&
|
|
28
|
+
dateString.trim() !== "" &&
|
|
29
|
+
dayjs.utc(dateString).isValid()
|
|
30
|
+
);
|
|
31
|
+
};
|
package/src/Player/index.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
import * as R from "ramda";
|
|
6
6
|
|
|
7
|
-
import { findNodeHandle,
|
|
7
|
+
import { findNodeHandle, StyleSheet, View } from "react-native";
|
|
8
8
|
import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
|
|
9
9
|
import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
|
|
10
10
|
|
|
@@ -38,16 +38,11 @@ import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/c
|
|
|
38
38
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
39
39
|
import { getTargetScreenDataFromEntry } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData";
|
|
40
40
|
import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
: NativeModules.QuickBrickDefaultPlayerModule,
|
|
47
|
-
KalturaPlayerPlugin: isApplePlatform()
|
|
48
|
-
? NativeModules.KalturaPlayerModule
|
|
49
|
-
: NativeModules.QBKalturaPlayerModule,
|
|
50
|
-
};
|
|
41
|
+
import {
|
|
42
|
+
AnimationComponent,
|
|
43
|
+
ComponentAnimationType,
|
|
44
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
45
|
+
import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
|
|
51
46
|
|
|
52
47
|
const styles = StyleSheet.create({
|
|
53
48
|
container: { flex: 1 },
|
|
@@ -117,8 +112,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
componentWillUnmount() {
|
|
120
|
-
this.stopBackgroundPlayback();
|
|
121
|
-
|
|
122
115
|
this.playbackStarted = false;
|
|
123
116
|
this.handleAnalyticEvent(GENERAL_EVENT.session_end);
|
|
124
117
|
this.onPlayerClosed();
|
|
@@ -163,6 +156,11 @@ export default class VideoPlayer extends React.Component<
|
|
|
163
156
|
}
|
|
164
157
|
}
|
|
165
158
|
|
|
159
|
+
// For close button
|
|
160
|
+
public closeNativePlayer = () => {
|
|
161
|
+
this.stopBackgroundPlayback();
|
|
162
|
+
};
|
|
163
|
+
|
|
166
164
|
getPlayNextData = () => this.props.playNextData as PlayNextData;
|
|
167
165
|
|
|
168
166
|
/**
|
|
@@ -337,11 +335,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
337
335
|
}
|
|
338
336
|
};
|
|
339
337
|
|
|
340
|
-
|
|
341
|
-
* Sets either subtitle language, or audio language if we have a selected a new locale.
|
|
342
|
-
* If no locale is set it defaults to system language for audio, and leaves subtitles unaltered.
|
|
343
|
-
*/
|
|
344
|
-
setLanguage = (selected = null) => {
|
|
338
|
+
selectTrack = (selected) => {
|
|
345
339
|
const { trackType, id, language } = selected || {};
|
|
346
340
|
|
|
347
341
|
const isOff = language === "Off";
|
|
@@ -782,7 +776,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
782
776
|
|
|
783
777
|
render() {
|
|
784
778
|
const {
|
|
785
|
-
castAction,
|
|
786
779
|
docked,
|
|
787
780
|
entry,
|
|
788
781
|
fullscreen,
|
|
@@ -792,8 +785,13 @@ export default class VideoPlayer extends React.Component<
|
|
|
792
785
|
isTabletPortrait,
|
|
793
786
|
muted,
|
|
794
787
|
style,
|
|
788
|
+
castAction,
|
|
789
|
+
controller,
|
|
790
|
+
startComponentsAnimation,
|
|
795
791
|
} = this.props;
|
|
796
792
|
|
|
793
|
+
const supportNativeCast = controller.supportNativeCast();
|
|
794
|
+
|
|
797
795
|
const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
|
|
798
796
|
this.state;
|
|
799
797
|
|
|
@@ -855,10 +853,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
855
853
|
!!this.props.controller?.supportsNativeControls?.();
|
|
856
854
|
|
|
857
855
|
const controls = isAppleTV()
|
|
858
|
-
? supportsNativeControls && !isInlineTV
|
|
856
|
+
? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
|
|
859
857
|
: supportsNativeControls && !this.getPlayNextData();
|
|
860
858
|
|
|
861
|
-
const shouldUseNativeControls =
|
|
859
|
+
const shouldUseNativeControls =
|
|
860
|
+
controls &&
|
|
861
|
+
!startComponentsAnimation &&
|
|
862
|
+
!docked &&
|
|
863
|
+
!castAction?.state?.connected;
|
|
862
864
|
|
|
863
865
|
platformSpecificProps = {
|
|
864
866
|
...platformSpecificProps,
|
|
@@ -987,7 +989,13 @@ export default class VideoPlayer extends React.Component<
|
|
|
987
989
|
...androidLifecyclePiPEvents,
|
|
988
990
|
};
|
|
989
991
|
|
|
990
|
-
const needToShowPlayNextOverlay =
|
|
992
|
+
const needToShowPlayNextOverlay =
|
|
993
|
+
isTV() && this.getPlayNextData() && !docked;
|
|
994
|
+
|
|
995
|
+
const needToShowDefaultControls = !(
|
|
996
|
+
(controls && !castAction?.state?.connected) ||
|
|
997
|
+
this.getPlayNextData()
|
|
998
|
+
);
|
|
991
999
|
|
|
992
1000
|
const playerContainerStyles = isApplePlatform()
|
|
993
1001
|
? styles.playerContainerBlack
|
|
@@ -1029,6 +1037,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1029
1037
|
extraInfoText={castAction?.state?.friendlyStatus}
|
|
1030
1038
|
controlsAlwaysVisible={castAction?.state?.connected}
|
|
1031
1039
|
playNextData={this.getPlayNextData()}
|
|
1040
|
+
needToShowDefaultControls={needToShowDefaultControls}
|
|
1032
1041
|
/>
|
|
1033
1042
|
) : null;
|
|
1034
1043
|
};
|
|
@@ -1063,29 +1072,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
1063
1072
|
const renderMobilePlayer = () => {
|
|
1064
1073
|
return isFullScreenAudioPlayer && isAudioItem ? (
|
|
1065
1074
|
<View style={containerStyle}>
|
|
1066
|
-
{!castAction?.state?.connected && (
|
|
1075
|
+
{(!castAction?.state?.connected || supportNativeCast) && (
|
|
1067
1076
|
<this.props.PlayerComponent
|
|
1068
1077
|
ref={this._assignRoot}
|
|
1069
1078
|
{...nativeEvents}
|
|
1070
1079
|
{...nativeProps}
|
|
1071
1080
|
/>
|
|
1072
1081
|
)}
|
|
1073
|
-
<AudioPlayerWrapper {...sharedWrapperViewProps}
|
|
1074
|
-
<View>
|
|
1075
|
-
{
|
|
1076
|
-
<PlayNextOverlay
|
|
1077
|
-
item={entry}
|
|
1078
|
-
playerIsDocked={docked}
|
|
1079
|
-
pluginConfiguration={this.getPluginConfiguration()}
|
|
1080
|
-
playerId={this.props.playerId}
|
|
1081
|
-
playNextData={this.getPlayNextData()}
|
|
1082
|
-
setNextVideoPreloadThresholdPercentage={
|
|
1083
|
-
this.props.setNextVideoPreloadThresholdPercentage
|
|
1084
|
-
}
|
|
1085
|
-
/>
|
|
1086
|
-
}
|
|
1087
|
-
</View>
|
|
1088
|
-
</AudioPlayerWrapper>
|
|
1082
|
+
<AudioPlayerWrapper {...sharedWrapperViewProps} />
|
|
1089
1083
|
</View>
|
|
1090
1084
|
) : (
|
|
1091
1085
|
<PlayerWrapper {...sharedWrapperViewProps}>
|
|
@@ -1100,11 +1094,24 @@ export default class VideoPlayer extends React.Component<
|
|
|
1100
1094
|
<View style={styles.container}>
|
|
1101
1095
|
<View style={[playerContainerStyles, playerDimensions]}>
|
|
1102
1096
|
{isAudioItem && (
|
|
1103
|
-
<
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1097
|
+
<AnimationComponent
|
|
1098
|
+
animationType={ComponentAnimationType.player}
|
|
1099
|
+
additionalData={{
|
|
1100
|
+
...playerDimensions,
|
|
1101
|
+
inlineAudioPlayer: true,
|
|
1102
|
+
}}
|
|
1103
|
+
style={getStyle(playerDimensions)}
|
|
1104
|
+
>
|
|
1105
|
+
<AudioPlayer
|
|
1106
|
+
style={
|
|
1107
|
+
isTV()
|
|
1108
|
+
? getStyle(playerDimensions)
|
|
1109
|
+
: styles.playerSize
|
|
1110
|
+
}
|
|
1111
|
+
audio_item={entry}
|
|
1112
|
+
plugin_configuration={this.getPluginConfiguration()}
|
|
1113
|
+
/>
|
|
1114
|
+
</AnimationComponent>
|
|
1108
1115
|
)}
|
|
1109
1116
|
|
|
1110
1117
|
{shouldRenderBackground && (
|
|
@@ -1116,7 +1123,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1116
1123
|
/>
|
|
1117
1124
|
)}
|
|
1118
1125
|
|
|
1119
|
-
{!castAction?.state?.connected && (
|
|
1126
|
+
{(!castAction?.state?.connected || supportNativeCast) && (
|
|
1120
1127
|
<AnimationComponent
|
|
1121
1128
|
animationType={ComponentAnimationType.player}
|
|
1122
1129
|
additionalData={playerDimensions}
|
|
@@ -1127,7 +1134,11 @@ export default class VideoPlayer extends React.Component<
|
|
|
1127
1134
|
{...nativeEvents}
|
|
1128
1135
|
{...nativeProps}
|
|
1129
1136
|
listener={this.props.listener}
|
|
1130
|
-
style={
|
|
1137
|
+
style={
|
|
1138
|
+
isTV()
|
|
1139
|
+
? getStyle(playerDimensions)
|
|
1140
|
+
: styles.playerSize
|
|
1141
|
+
}
|
|
1131
1142
|
pluginConfiguration={this.getPluginConfiguration()}
|
|
1132
1143
|
controller={this.props.controller}
|
|
1133
1144
|
>
|