@applicaster/quick-brick-player 13.0.0-rc.99 → 14.0.0-rc.2
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 -6
- package/src/Player/AudioLayer/AudioDescription.tsx +109 -68
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +81 -189
- package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +23 -5
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +24 -49
- package/src/Player/AudioLayer/Layout/Controls/utils.ts +2 -2
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +38 -19
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +4 -13
- package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +38 -34
- package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +4 -16
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +1 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +99 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +37 -20
- package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +19 -0
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +101 -105
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +2 -11
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +19 -5
- package/src/Player/AudioLayer/Layout/const.ts +4 -0
- package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +1 -1
- package/src/Player/AudioLayer/Layout/utils.ts +7 -0
- package/src/Player/AudioLayer/SleepTimerManager.ts +22 -11
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +66 -0
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/index.ts +1 -0
- package/src/Player/AudioLayer/useSleepTimerModal.ts +109 -0
- package/src/Player/Const/sleepTimerOptions.ts +7 -5
- package/src/Player/Utils/index.tsx +6 -0
- package/src/Player/hooks/useIsCasting.tsx +57 -0
- package/src/Player/index.tsx +78 -33
- package/src/QuickBrickPlayer.tsx +3 -0
- package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.ts +0 -3
- package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.web.ts +0 -3
- package/src/Player/AudioLayer/LinearGradient/index.ts +0 -1
- package/src/Player/AudioLayer/MagicBG/index.tsx +0 -155
- package/src/utils/utils.ts +0 -25
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ModalHeader } from "@applicaster/zapp-react-native-ui-components/Components/ModalComponent/Header";
|
|
3
|
+
import { isNil } from "lodash";
|
|
4
|
+
|
|
5
|
+
const formatTime = (milliseconds: number) => {
|
|
6
|
+
const totalSeconds = Math.floor(milliseconds / 1000);
|
|
7
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
9
|
+
const seconds = totalSeconds % 60;
|
|
10
|
+
|
|
11
|
+
if (milliseconds < 3600000) {
|
|
12
|
+
return `${minutes.toString().padStart(2, "0")}:${seconds
|
|
13
|
+
.toString()
|
|
14
|
+
.padStart(2, "0")}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return `${hours.toString().padStart(2, "0")}:${minutes
|
|
18
|
+
.toString()
|
|
19
|
+
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const SleepTimerModalHeader = ({
|
|
23
|
+
summary,
|
|
24
|
+
...props
|
|
25
|
+
}: {
|
|
26
|
+
summary?: string;
|
|
27
|
+
}) => {
|
|
28
|
+
const sleepTimerDate = +summary;
|
|
29
|
+
|
|
30
|
+
const [timeLeft, setTimeLeft] = React.useState(
|
|
31
|
+
sleepTimerDate !== 0 ? sleepTimerDate - Date.now() : 0
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const showCounter = React.useMemo(() => sleepTimerDate > 0, [sleepTimerDate]);
|
|
35
|
+
|
|
36
|
+
React.useLayoutEffect(() => {
|
|
37
|
+
if (showCounter && sleepTimerDate !== 0) {
|
|
38
|
+
setTimeLeft(sleepTimerDate - Date.now());
|
|
39
|
+
|
|
40
|
+
const intervalId = setInterval(() => {
|
|
41
|
+
setTimeLeft(() => {
|
|
42
|
+
const newTimeLeft = sleepTimerDate - Date.now();
|
|
43
|
+
|
|
44
|
+
if (newTimeLeft <= 0) {
|
|
45
|
+
clearInterval(intervalId);
|
|
46
|
+
|
|
47
|
+
return 0;
|
|
48
|
+
} else {
|
|
49
|
+
return newTimeLeft;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}, 1000);
|
|
53
|
+
|
|
54
|
+
return () => clearInterval(intervalId);
|
|
55
|
+
} else {
|
|
56
|
+
setTimeLeft(0);
|
|
57
|
+
}
|
|
58
|
+
}, [sleepTimerDate, showCounter]);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ModalHeader
|
|
62
|
+
summary={!isNil(summary) ? formatTime(timeLeft) : summary}
|
|
63
|
+
{...(props as any)}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SleepTimerModalHeader";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
SLEEP_TIMER_OFF,
|
|
5
|
+
SleepTimerController,
|
|
6
|
+
SleepTimerState,
|
|
7
|
+
} from "./SleepTimerManager";
|
|
8
|
+
|
|
9
|
+
import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
10
|
+
|
|
11
|
+
import { SleepTimerModalHeader } from "./components/SleepTimerModalHeader";
|
|
12
|
+
import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
|
|
13
|
+
import {
|
|
14
|
+
getBottomSheetModalLabelProps,
|
|
15
|
+
getBottomSheetModalIconProps,
|
|
16
|
+
getBottomSheetModalItemProps,
|
|
17
|
+
getBottomSheetModalSelectedItemIcon,
|
|
18
|
+
selectSleepTitle,
|
|
19
|
+
selectSleepOffLabel,
|
|
20
|
+
} from "../Utils";
|
|
21
|
+
|
|
22
|
+
export const useSleepTimerModal = (player, configuration) => {
|
|
23
|
+
const playerState = usePlayerState("audio-player-sleep-timer-modal");
|
|
24
|
+
|
|
25
|
+
const title = configuration(selectSleepTitle);
|
|
26
|
+
const offLabel = configuration(selectSleepOffLabel);
|
|
27
|
+
|
|
28
|
+
const [sleepTimerManager, setSleepTimerManager] =
|
|
29
|
+
useState<SleepTimerController | null>(null);
|
|
30
|
+
|
|
31
|
+
const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
|
|
32
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (player) {
|
|
37
|
+
setSleepTimerManager(new SleepTimerController(player));
|
|
38
|
+
} else {
|
|
39
|
+
setSleepTimerManager(null);
|
|
40
|
+
}
|
|
41
|
+
}, [player]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (playerState?.isLive) {
|
|
45
|
+
// Resetting a sleep timer when audio content is live (Timer not supported on live video)
|
|
46
|
+
sleepTimerManager.setSleepTimer(SLEEP_TIMER_OFF).catch();
|
|
47
|
+
}
|
|
48
|
+
}, [playerState?.isLive]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!playerState.isReadyToPlay) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void sleepTimerManager?.loadSleep();
|
|
56
|
+
}, [playerState.isReadyToPlay, sleepTimerManager]);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const sleepSubscription =
|
|
60
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
sleepSubscription?.unsubscribe();
|
|
64
|
+
};
|
|
65
|
+
}, [sleepTimerManager]);
|
|
66
|
+
|
|
67
|
+
const onSleepSelect = useCallback(
|
|
68
|
+
async (item) => {
|
|
69
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
70
|
+
},
|
|
71
|
+
[sleepTimerManager]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const onSleepButtonPress = useCallback(() => {
|
|
75
|
+
const timerDate = sleepTimerState.timerDate;
|
|
76
|
+
|
|
77
|
+
const timerRemaining =
|
|
78
|
+
timerDate !== 0 && +timerDate > Date.now() ? timerDate : null;
|
|
79
|
+
|
|
80
|
+
openBottomSheetModal({
|
|
81
|
+
modalBottomSheetContentProps: {
|
|
82
|
+
headerComponent: SleepTimerModalHeader,
|
|
83
|
+
items: getSleepTimerOptions(offLabel, timerRemaining),
|
|
84
|
+
onPress: onSleepSelect,
|
|
85
|
+
title,
|
|
86
|
+
summary: timerRemaining,
|
|
87
|
+
itemProps: getBottomSheetModalItemProps,
|
|
88
|
+
iconProps: getBottomSheetModalIconProps,
|
|
89
|
+
labelProps: getBottomSheetModalLabelProps,
|
|
90
|
+
getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
|
|
91
|
+
},
|
|
92
|
+
options: {
|
|
93
|
+
supportedOrientations: [
|
|
94
|
+
"portrait",
|
|
95
|
+
"landscape",
|
|
96
|
+
"portrait-upside-down",
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}, [sleepTimerState.timerDate, sleepTimerState.timer, title, onSleepSelect]);
|
|
101
|
+
|
|
102
|
+
return useMemo(
|
|
103
|
+
() => ({
|
|
104
|
+
onSleepButtonPress,
|
|
105
|
+
sleepTimerState,
|
|
106
|
+
}),
|
|
107
|
+
[onSleepButtonPress, sleepTimerState]
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -28,12 +28,14 @@ const createTimerOptions = (value) => {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
export const getSleepTimerOptions = (
|
|
31
|
+
export const getSleepTimerOptions = (offLabel, sleepTimerValue) => {
|
|
32
32
|
return [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
sleepTimerValue
|
|
34
|
+
? {
|
|
35
|
+
label: offLabel,
|
|
36
|
+
value: 0,
|
|
37
|
+
}
|
|
38
|
+
: null,
|
|
37
39
|
...[5, 10, 15, 30, 45, 60, 120].map(createTimerOptions),
|
|
38
40
|
];
|
|
39
41
|
};
|
|
@@ -116,3 +116,9 @@ export const getBottomSheetModalSelectedItemIcon = (theme) =>
|
|
|
116
116
|
|
|
117
117
|
export const getBottomSheetModalDefaultItemIcon = (theme) =>
|
|
118
118
|
theme["audio_modal_bottom_sheet_item_default_icon"] || null;
|
|
119
|
+
|
|
120
|
+
export const selectSleepTitle = (configuration) =>
|
|
121
|
+
configuration.playback_sleep_title;
|
|
122
|
+
|
|
123
|
+
export const selectSleepOffLabel = (configuration) =>
|
|
124
|
+
configuration.playback_sleep_off_label;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
3
|
+
import { useAppState } from "@applicaster/zapp-react-native-utils/reactHooks/app";
|
|
4
|
+
import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
|
+
|
|
6
|
+
const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
|
|
7
|
+
|
|
8
|
+
const fakeEntry: any = {
|
|
9
|
+
title: "Chromecast use is casting fake entry",
|
|
10
|
+
id: "quick-brick-use-is-casting-fake-entry",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the cast action state connection status, does not update when app is in background on Apple platforms
|
|
15
|
+
* On other platforms, it continues to check the cast action state even when the app is in background
|
|
16
|
+
*/
|
|
17
|
+
export const useIsCasting = () => {
|
|
18
|
+
const actionContext = useActions(CHROMECAST_IDENTIFIER);
|
|
19
|
+
const appState = useAppState();
|
|
20
|
+
const isAppInForeground = appState === "active";
|
|
21
|
+
const shouldOnlyUpdateInForeground = isApplePlatform();
|
|
22
|
+
|
|
23
|
+
const [castActionState, setCastActionState] = React.useState(
|
|
24
|
+
!actionContext ? null : actionContext.initialEntryState(fakeEntry)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (!shouldOnlyUpdateInForeground || isAppInForeground) {
|
|
29
|
+
if (actionContext && castActionState === null) {
|
|
30
|
+
setCastActionState(actionContext.initialEntryState(fakeEntry));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}, [
|
|
34
|
+
actionContext,
|
|
35
|
+
setCastActionState,
|
|
36
|
+
isAppInForeground,
|
|
37
|
+
shouldOnlyUpdateInForeground,
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
if (typeof actionContext?.addListener === "function") {
|
|
42
|
+
return actionContext?.addListener(String(fakeEntry.id), (state) => {
|
|
43
|
+
// Only check foreground state on Apple platforms
|
|
44
|
+
if (!shouldOnlyUpdateInForeground || isAppInForeground) {
|
|
45
|
+
setCastActionState(state);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
setCastActionState,
|
|
51
|
+
actionContext,
|
|
52
|
+
isAppInForeground,
|
|
53
|
+
shouldOnlyUpdateInForeground,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return !!castActionState?.connected;
|
|
57
|
+
};
|
package/src/Player/index.tsx
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
import * as R from "ramda";
|
|
6
6
|
|
|
7
|
+
import { flatten } from "@applicaster/zapp-react-native-utils/utils";
|
|
8
|
+
|
|
7
9
|
import { findNodeHandle, StyleSheet, View } from "react-native";
|
|
8
10
|
import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
|
|
9
11
|
import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
|
|
@@ -40,6 +42,7 @@ import { PlayerAnalyticsManager } from "@applicaster/zapp-react-native-utils/ana
|
|
|
40
42
|
import { PlayerAnalyticsTrackerI } from "@applicaster/zapp-react-native-utils/analyticsUtils/playerAnalyticsTracker";
|
|
41
43
|
|
|
42
44
|
import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
|
|
45
|
+
import { isAudioItem as isAudioItemUtil } from "@applicaster/zapp-react-native-utils/playerUtils";
|
|
43
46
|
|
|
44
47
|
const styles = StyleSheet.create({
|
|
45
48
|
container: { flex: 1 },
|
|
@@ -50,6 +53,7 @@ const styles = StyleSheet.create({
|
|
|
50
53
|
position: "absolute",
|
|
51
54
|
zIndex: 1,
|
|
52
55
|
},
|
|
56
|
+
audioPlayerSize: { width: "100%", height: "100%" },
|
|
53
57
|
playerSize: { width: "100%", height: "100%" },
|
|
54
58
|
});
|
|
55
59
|
|
|
@@ -58,6 +62,10 @@ const logger = createLogger({
|
|
|
58
62
|
subsystem: Subsystem,
|
|
59
63
|
});
|
|
60
64
|
|
|
65
|
+
const isTvOS = isTvOSPlatform();
|
|
66
|
+
|
|
67
|
+
const AUDIO_PLAYER_NATIVE_ID = "audio-player-view";
|
|
68
|
+
|
|
61
69
|
export default class VideoPlayer extends React.Component<
|
|
62
70
|
QuickBrickPlayer.PlayerProps,
|
|
63
71
|
QuickBrickPlayer.NativePlayerState
|
|
@@ -74,6 +82,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
74
82
|
state: QuickBrickPlayer.NativePlayerState = {
|
|
75
83
|
audioTrackId: null,
|
|
76
84
|
textTrackId: null,
|
|
85
|
+
isCasting: this.props.isCasting,
|
|
77
86
|
|
|
78
87
|
// TODO: Can be deleted, on controller logic
|
|
79
88
|
isAd: false,
|
|
@@ -92,6 +101,8 @@ export default class VideoPlayer extends React.Component<
|
|
|
92
101
|
// Player state
|
|
93
102
|
currentTime: 0,
|
|
94
103
|
duration: 0,
|
|
104
|
+
|
|
105
|
+
nativePlayerIsReady: false,
|
|
95
106
|
};
|
|
96
107
|
|
|
97
108
|
private _tracker: PlayerAnalyticsTrackerI;
|
|
@@ -487,6 +498,9 @@ export default class VideoPlayer extends React.Component<
|
|
|
487
498
|
|
|
488
499
|
onVideoFullscreenPlayerDidPresent = () => {
|
|
489
500
|
this.props.listener?.onVideoFullscreenPlayerDidPresent?.();
|
|
501
|
+
|
|
502
|
+
// we have to have a signal that player is ready, in order to show audio_player(for example)
|
|
503
|
+
this.setState({ nativePlayerIsReady: true });
|
|
490
504
|
};
|
|
491
505
|
|
|
492
506
|
onVideoFullscreenPlayerWillDismiss = () => {
|
|
@@ -672,32 +686,57 @@ export default class VideoPlayer extends React.Component<
|
|
|
672
686
|
}
|
|
673
687
|
};
|
|
674
688
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
689
|
+
childrenWithExtensions = () => {
|
|
690
|
+
const { docked, entry, children } = this.props;
|
|
691
|
+
|
|
692
|
+
if (!isTvOS) {
|
|
693
|
+
return children;
|
|
678
694
|
}
|
|
679
695
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
696
|
+
const output = [];
|
|
697
|
+
|
|
698
|
+
// audio-player
|
|
699
|
+
if (isAudioItemUtil(entry)) {
|
|
700
|
+
const { nativePlayerIsReady } = this.state;
|
|
701
|
+
|
|
702
|
+
// we have to wait ready status of native player view, before showing audio_player
|
|
703
|
+
if (nativePlayerIsReady) {
|
|
704
|
+
output.push(
|
|
705
|
+
<View nativeID={AUDIO_PLAYER_NATIVE_ID} key={AUDIO_PLAYER_NATIVE_ID}>
|
|
706
|
+
{children}
|
|
707
|
+
</View>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
output.push(children);
|
|
712
|
+
}
|
|
713
|
+
// audio-player
|
|
714
|
+
|
|
715
|
+
// play-next
|
|
716
|
+
if (this.getPlayNextData()) {
|
|
717
|
+
output.push(
|
|
718
|
+
<PlayNextOverlay
|
|
719
|
+
key="play-next-overlay"
|
|
720
|
+
item={entry}
|
|
721
|
+
playerIsDocked={docked}
|
|
722
|
+
pluginConfiguration={this.getPluginConfiguration()}
|
|
723
|
+
playerId={this.props.playerId}
|
|
724
|
+
playNextData={this.getPlayNextData()}
|
|
725
|
+
setNextVideoPreloadThresholdPercentage={
|
|
726
|
+
this.props.setNextVideoPreloadThresholdPercentage
|
|
727
|
+
}
|
|
728
|
+
/>
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
// play-next
|
|
732
|
+
|
|
733
|
+
return flatten(output); // prevent possible case with nested array
|
|
699
734
|
};
|
|
700
735
|
|
|
736
|
+
get supportNativeCast() {
|
|
737
|
+
return this.props.controller.supportNativeCast();
|
|
738
|
+
}
|
|
739
|
+
|
|
701
740
|
render() {
|
|
702
741
|
const {
|
|
703
742
|
docked,
|
|
@@ -710,18 +749,13 @@ export default class VideoPlayer extends React.Component<
|
|
|
710
749
|
muted,
|
|
711
750
|
style,
|
|
712
751
|
castAction,
|
|
713
|
-
controller,
|
|
714
752
|
startComponentsAnimation,
|
|
715
753
|
} = this.props;
|
|
716
754
|
|
|
717
|
-
const supportNativeCast = controller.supportNativeCast();
|
|
718
|
-
|
|
719
755
|
const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
|
|
720
756
|
this.state;
|
|
721
757
|
|
|
722
|
-
const isAudioItem =
|
|
723
|
-
entry?.content?.type?.includes?.("audio") ||
|
|
724
|
-
entry?.type?.value?.includes?.("audio");
|
|
758
|
+
const isAudioItem = isAudioItemUtil(entry);
|
|
725
759
|
|
|
726
760
|
const getStyle = (dimensions) => {
|
|
727
761
|
const generalStyles = isAudioItem ? styles.audio : styles.video;
|
|
@@ -995,11 +1029,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
995
1029
|
const backgroundImageKey =
|
|
996
1030
|
this.getPluginConfiguration()?.player_preview_image_key;
|
|
997
1031
|
|
|
1032
|
+
const shouldRenderNativePlayer =
|
|
1033
|
+
!this.props.isCasting || this.supportNativeCast;
|
|
1034
|
+
|
|
998
1035
|
const renderMobilePlayer = () => {
|
|
999
1036
|
return isFullScreenAudioPlayer && isAudioItem ? (
|
|
1000
1037
|
// Ensure the View remains in the native hierarchy for proper rendering and interaction
|
|
1001
1038
|
<View collapsable={false} style={containerStyle}>
|
|
1002
|
-
{
|
|
1039
|
+
{shouldRenderNativePlayer ? (
|
|
1003
1040
|
<this.props.PlayerComponent
|
|
1004
1041
|
ref={this._assignRoot}
|
|
1005
1042
|
{...nativeEvents}
|
|
@@ -1051,7 +1088,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1051
1088
|
/>
|
|
1052
1089
|
) : null}
|
|
1053
1090
|
|
|
1054
|
-
{
|
|
1091
|
+
{shouldRenderNativePlayer ? (
|
|
1055
1092
|
<AnimationComponent
|
|
1056
1093
|
animationType={ComponentAnimationType.player}
|
|
1057
1094
|
additionalData={{
|
|
@@ -1073,7 +1110,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1073
1110
|
pluginConfiguration={this.getPluginConfiguration()}
|
|
1074
1111
|
controller={this.props.controller}
|
|
1075
1112
|
>
|
|
1076
|
-
{this.
|
|
1113
|
+
{this.props.children}
|
|
1077
1114
|
</this.props.PlayerComponent>
|
|
1078
1115
|
</AnimationComponent>
|
|
1079
1116
|
) : null}
|
|
@@ -1102,6 +1139,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
1102
1139
|
const renderTVPlayer = () => {
|
|
1103
1140
|
return (
|
|
1104
1141
|
<View style={this.takeParentStyle} testID={"player-view"}>
|
|
1142
|
+
{!isTvOS && isAudioItem ? (
|
|
1143
|
+
<AudioPlayer
|
|
1144
|
+
style={getStyle(styles.audioPlayerSize)}
|
|
1145
|
+
audio_item={entry}
|
|
1146
|
+
plugin_configuration={this.getPluginConfiguration()}
|
|
1147
|
+
/>
|
|
1148
|
+
) : null}
|
|
1149
|
+
|
|
1105
1150
|
<this.props.PlayerComponent
|
|
1106
1151
|
ref={this._assignRoot}
|
|
1107
1152
|
{...nativeEvents}
|
|
@@ -1111,11 +1156,11 @@ export default class VideoPlayer extends React.Component<
|
|
|
1111
1156
|
pluginConfiguration={this.getPluginConfiguration()}
|
|
1112
1157
|
controller={this.props.controller}
|
|
1113
1158
|
>
|
|
1114
|
-
{this.
|
|
1159
|
+
{this.childrenWithExtensions()}
|
|
1115
1160
|
</this.props.PlayerComponent>
|
|
1116
1161
|
|
|
1117
1162
|
{renderTransportControls(this.takeParentStyle)}
|
|
1118
|
-
{!
|
|
1163
|
+
{!isTvOS && (
|
|
1119
1164
|
<PlayNextOverlay
|
|
1120
1165
|
item={entry}
|
|
1121
1166
|
playerIsDocked={docked}
|
package/src/QuickBrickPlayer.tsx
CHANGED
|
@@ -5,6 +5,7 @@ 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 { useIsCasting } from "./Player/hooks/useIsCasting";
|
|
8
9
|
|
|
9
10
|
const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
|
|
10
11
|
|
|
@@ -24,6 +25,7 @@ const QuickBrickPlayerComponent = (
|
|
|
24
25
|
}, [props.PlayerComponent]);
|
|
25
26
|
|
|
26
27
|
const castAction = useActions(CHROMECAST_IDENTIFIER);
|
|
28
|
+
const isCasting = useIsCasting();
|
|
27
29
|
|
|
28
30
|
const isModal = Boolean(props?.isModal);
|
|
29
31
|
const isScreenDisabled = !controller?.isFullScreenSupported() || !isModal;
|
|
@@ -48,6 +50,7 @@ const QuickBrickPlayerComponent = (
|
|
|
48
50
|
{...props}
|
|
49
51
|
ref={ref}
|
|
50
52
|
castAction={!props?.disableCastAction && castAction}
|
|
53
|
+
isCasting={isCasting}
|
|
51
54
|
PlayerComponent={props.PlayerComponent}
|
|
52
55
|
openPiP={openPiP}
|
|
53
56
|
closePiP={closePiP}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "./LinearGradientProxy";
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/prop-types */
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { Animated, StyleSheet, View } from "react-native";
|
|
4
|
-
import LinearGradient from "../LinearGradient";
|
|
5
|
-
import { getDominantColor, getPalettes } from "../../../utils/utils";
|
|
6
|
-
import { useEffect } from "react";
|
|
7
|
-
|
|
8
|
-
type MagicBGProps = {
|
|
9
|
-
children: React.ReactNode;
|
|
10
|
-
image: string;
|
|
11
|
-
fallbackUrl?: string;
|
|
12
|
-
defaultColor: string;
|
|
13
|
-
colors: string[];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const defaultColors = {
|
|
17
|
-
overlay: "rgba(0, 0, 0, 0.35)",
|
|
18
|
-
defaultGradientStart: "rgba(0, 0, 0, 0.25)",
|
|
19
|
-
defaultGradientEnd: "rgba(0, 0, 0, 0)",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const styles = StyleSheet.create({
|
|
23
|
-
container: {
|
|
24
|
-
flex: 1,
|
|
25
|
-
},
|
|
26
|
-
fullScreenOverlay: {
|
|
27
|
-
...StyleSheet.absoluteFillObject,
|
|
28
|
-
backgroundColor: defaultColors.overlay,
|
|
29
|
-
},
|
|
30
|
-
gradientOverlay: {
|
|
31
|
-
position: "absolute",
|
|
32
|
-
bottom: 0,
|
|
33
|
-
left: 0,
|
|
34
|
-
right: 0,
|
|
35
|
-
height: "100%",
|
|
36
|
-
},
|
|
37
|
-
});
|
|
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).reverse(), dominantColor };
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const MagicBG: React.FC<MagicBGProps> = ({
|
|
54
|
-
children,
|
|
55
|
-
image,
|
|
56
|
-
fallbackUrl = "",
|
|
57
|
-
colors = [],
|
|
58
|
-
defaultColor,
|
|
59
|
-
}) => {
|
|
60
|
-
const [dominantColor, setDominantColor] =
|
|
61
|
-
React.useState<string>(defaultColor);
|
|
62
|
-
|
|
63
|
-
const [gradientColors, setGradientColors] = React.useState<string[]>([
|
|
64
|
-
defaultColors.defaultGradientStart,
|
|
65
|
-
defaultColors.defaultGradientEnd,
|
|
66
|
-
]);
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
const fetchColors = async () => {
|
|
87
|
-
if (colors && colors.length > 0) {
|
|
88
|
-
setGradientColors(colors.slice(0, 2));
|
|
89
|
-
setDominantColor(colors[0]);
|
|
90
|
-
animateColors();
|
|
91
|
-
} else if (image) {
|
|
92
|
-
try {
|
|
93
|
-
const { gradientColors, dominantColor } =
|
|
94
|
-
await fetchPaletteAndDominantColor(image);
|
|
95
|
-
|
|
96
|
-
setGradientColors(gradientColors);
|
|
97
|
-
setDominantColor(dominantColor);
|
|
98
|
-
animateColors();
|
|
99
|
-
} catch (error) {
|
|
100
|
-
try {
|
|
101
|
-
const { gradientColors, dominantColor } =
|
|
102
|
-
await fetchPaletteAndDominantColor(fallbackUrl);
|
|
103
|
-
|
|
104
|
-
setGradientColors(gradientColors);
|
|
105
|
-
setDominantColor(dominantColor);
|
|
106
|
-
animateColors();
|
|
107
|
-
} catch {
|
|
108
|
-
setGradientColors([
|
|
109
|
-
defaultColors.defaultGradientStart,
|
|
110
|
-
defaultColors.defaultGradientEnd,
|
|
111
|
-
]);
|
|
112
|
-
|
|
113
|
-
setDominantColor(defaultColor);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
setGradientColors([
|
|
118
|
-
defaultColors.defaultGradientStart,
|
|
119
|
-
defaultColors.defaultGradientEnd,
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
setDominantColor(defaultColor);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
fetchColors();
|
|
127
|
-
}, []);
|
|
128
|
-
|
|
129
|
-
const interpolatedBackgroundColor = backgroundColorAnim.interpolate({
|
|
130
|
-
inputRange: [0, 1],
|
|
131
|
-
outputRange: ["black", dominantColor],
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<Animated.View
|
|
136
|
-
style={[
|
|
137
|
-
styles.container,
|
|
138
|
-
{ backgroundColor: interpolatedBackgroundColor },
|
|
139
|
-
]}
|
|
140
|
-
>
|
|
141
|
-
<View style={styles.fullScreenOverlay} />
|
|
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
|
-
|
|
152
|
-
{children}
|
|
153
|
-
</Animated.View>
|
|
154
|
-
);
|
|
155
|
-
};
|