@applicaster/quick-brick-player 13.0.0-rc.11 → 13.0.0-rc.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +27 -14
- package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
- package/src/Player/AudioLayer/AudioDescription.tsx +5 -7
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +174 -280
- package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +27 -0
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +316 -0
- package/src/Player/AudioLayer/Layout/{defaultLayoutsConts.ts → Controls/utils.ts} +7 -25
- package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
- package/src/Player/AudioLayer/Layout/{DockedLayout.tsx → DockedControls/index.tsx} +44 -74
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +230 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +83 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +25 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +101 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +26 -0
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +215 -0
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +223 -0
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +115 -0
- package/src/Player/AudioLayer/Layout/VerticalGutter/index.tsx +8 -0
- package/src/Player/AudioLayer/Layout/const.ts +22 -0
- package/src/Player/AudioLayer/Layout/debug.ts +6 -0
- package/src/Player/AudioLayer/Layout/hooks/index.ts +5 -0
- package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
- package/src/Player/AudioLayer/Layout/hooks/useTabletLayoutImageSize.ts +105 -0
- package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +54 -0
- package/src/Player/AudioLayer/Layout/index.tsx +21 -48
- package/src/Player/AudioLayer/Layout/types.ts +7 -0
- package/src/Player/AudioLayer/SleepTimerManager.ts +119 -0
- package/src/Player/AudioLayer/SpeedManager.ts +52 -0
- package/src/Player/AudioLayer/utils.ts +27 -0
- package/src/Player/Const/sleepTimerOptions.ts +39 -0
- package/src/Player/Const/speedOptions.ts +4 -0
- package/src/Player/Utils/Utils.ts +0 -5
- package/src/Player/Utils/index.tsx +117 -0
- package/src/Player/hooks/useIsCasting.tsx +57 -0
- package/src/Player/index.tsx +39 -22
- package/src/QuickBrickPlayer.tsx +3 -0
- package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
- 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/Player/Helpers/VideoResizeMode.ts +0 -7
- package/src/utils/utils.ts +0 -25
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
import { hasDatePassed, NAMESPACE } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { log_debug, log_error } = createLogger({
|
|
8
|
+
subsystem: "Player",
|
|
9
|
+
category: "SleepTimerManager",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type SleepTimer = {
|
|
13
|
+
label: string;
|
|
14
|
+
// interval in minutes for UI at the moment when timer was first started
|
|
15
|
+
value: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SLEEP_TIMER_OFF: SleepTimer = {
|
|
19
|
+
label: "playback_sleep_off_label",
|
|
20
|
+
value: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class SleepTimerState {
|
|
24
|
+
constructor(
|
|
25
|
+
public timer: SleepTimer,
|
|
26
|
+
public timerDate: number
|
|
27
|
+
) {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SLEEP_PREFERENCE_KEY = "sleep";
|
|
31
|
+
|
|
32
|
+
export class SleepTimerController {
|
|
33
|
+
private sleepTimerStateSubject = new BehaviorSubject<SleepTimerState>(
|
|
34
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
constructor(private player: Player) {}
|
|
38
|
+
|
|
39
|
+
get sleepTimerState$() {
|
|
40
|
+
return this.sleepTimerStateSubject.asObservable();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async scheduleSleep(sleepDate): Promise<boolean> {
|
|
44
|
+
log_debug(`Setting sleep interval to ${sleepDate}`);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.player.cancelSleepTimer?.();
|
|
48
|
+
this.player.startSleepTimer?.(sleepDate);
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
log_error("Failed to start sleep timer", { error: e.message });
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async setSleepTimer(item: SleepTimer) {
|
|
59
|
+
// User has selected Off state
|
|
60
|
+
if (item.value === 0) {
|
|
61
|
+
log_debug("Cancelling sleep timer");
|
|
62
|
+
this.player.cancelSleepTimer?.();
|
|
63
|
+
|
|
64
|
+
return this.onTimerExpired();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log_debug(`Starting sleep timer in ${item.value} minutes`);
|
|
68
|
+
|
|
69
|
+
const futureSleepDate = Date.now() + item.value * 60000;
|
|
70
|
+
|
|
71
|
+
if (await this.scheduleSleep(futureSleepDate)) {
|
|
72
|
+
await localStorage.setItem(
|
|
73
|
+
SLEEP_PREFERENCE_KEY,
|
|
74
|
+
{ ...item, futureSleepDate },
|
|
75
|
+
NAMESPACE
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this.sleepTimerStateSubject.next(
|
|
79
|
+
new SleepTimerState(item, futureSleepDate)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async onTimerExpired() {
|
|
85
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
86
|
+
|
|
87
|
+
this.sleepTimerStateSubject.next(new SleepTimerState(SLEEP_TIMER_OFF, 0));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async loadSleep() {
|
|
91
|
+
const sleep = await localStorage.getItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
92
|
+
|
|
93
|
+
if (!sleep) return;
|
|
94
|
+
|
|
95
|
+
const { futureSleepDate, ...rest } = sleep;
|
|
96
|
+
|
|
97
|
+
if (!futureSleepDate || hasDatePassed(futureSleepDate)) {
|
|
98
|
+
log_debug("Sleep date has already passed");
|
|
99
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
100
|
+
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (await this.scheduleSleep(futureSleepDate)) {
|
|
105
|
+
log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
|
|
106
|
+
|
|
107
|
+
this.sleepTimerStateSubject.next(
|
|
108
|
+
new SleepTimerState(rest as SleepTimer, futureSleepDate)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Will be called if user closed the player explicitly.
|
|
114
|
+
// TODO: check if it will be called on Android when we need.
|
|
115
|
+
// Probably we will have to handle it on native instead.
|
|
116
|
+
public static async onPlayerDestroy() {
|
|
117
|
+
await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
import { NAMESPACE } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { log_debug } = createLogger({
|
|
8
|
+
subsystem: "Player",
|
|
9
|
+
category: "PlaybackSpeedManager",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type PlaybackSpeed = {
|
|
13
|
+
asset: string;
|
|
14
|
+
value: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SPEED_PREFERENCE_KEY = "speed";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_SPEED: PlaybackSpeed = {
|
|
20
|
+
asset: "speed_1",
|
|
21
|
+
value: 1.0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// todo: should not be applied to live streams
|
|
25
|
+
export class SpeedController {
|
|
26
|
+
private playbackSpeedSubject = new BehaviorSubject<PlaybackSpeed>(
|
|
27
|
+
DEFAULT_SPEED
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
constructor(private player: Player) {}
|
|
31
|
+
|
|
32
|
+
get playbackSpeed$() {
|
|
33
|
+
return this.playbackSpeedSubject.asObservable();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async loadSpeed() {
|
|
37
|
+
const speed = await localStorage.getItem(SPEED_PREFERENCE_KEY, NAMESPACE);
|
|
38
|
+
|
|
39
|
+
// checking for value in case we will pull empty or broken object
|
|
40
|
+
if (speed && speed.value) {
|
|
41
|
+
this.playbackSpeedSubject.next(speed);
|
|
42
|
+
this.player.setPlaybackRate?.(speed.value);
|
|
43
|
+
log_debug(`Speed loaded: ${speed}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async setPlaybackSpeed(speed: PlaybackSpeed) {
|
|
48
|
+
await localStorage.setItem(SPEED_PREFERENCE_KEY, speed, NAMESPACE);
|
|
49
|
+
this.playbackSpeedSubject.next(speed);
|
|
50
|
+
this.player.setPlaybackRate?.(speed.value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
2
|
+
import {
|
|
3
|
+
PlayerAnimationStateEnum,
|
|
4
|
+
PlayerAnimationStateT,
|
|
5
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
|
|
2
6
|
import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
7
|
+
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
3
8
|
|
|
4
9
|
export const NAMESPACE = "audio-player";
|
|
5
10
|
|
|
@@ -31,3 +36,25 @@ export const formatSpeed = (value: number) => {
|
|
|
31
36
|
// Return the formatted string with "speed_" prefix
|
|
32
37
|
return `speed_${formattedValue}`;
|
|
33
38
|
};
|
|
39
|
+
|
|
40
|
+
export const calculateBorderRadius = (
|
|
41
|
+
docked: boolean,
|
|
42
|
+
startComponentsAnimation: boolean,
|
|
43
|
+
isActiveGesture: boolean,
|
|
44
|
+
playerAnimationState: PlayerAnimationStateT,
|
|
45
|
+
configuration: Record<string, any>
|
|
46
|
+
): number => {
|
|
47
|
+
const shouldRemoveBorderRadius =
|
|
48
|
+
(startComponentsAnimation &&
|
|
49
|
+
isActiveGesture &&
|
|
50
|
+
playerAnimationState !== PlayerAnimationStateEnum.maximize) ||
|
|
51
|
+
docked;
|
|
52
|
+
|
|
53
|
+
if (shouldRemoveBorderRadius) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return toNumberWithDefaultZero(
|
|
58
|
+
configuration.audio_player_artwork_border_radius
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { toDayJSLocaleMap } from "@applicaster/zapp-react-native-utils/appUtils/localizationsHelper";
|
|
2
|
+
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
3
|
+
import { getUILanguage } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
4
|
+
|
|
5
|
+
const createTimerOptions = (value) => {
|
|
6
|
+
let label = "";
|
|
7
|
+
|
|
8
|
+
const str = dayjs
|
|
9
|
+
.duration(value, "minute")
|
|
10
|
+
.locale(toDayJSLocaleMap(getUILanguage()))
|
|
11
|
+
.humanize();
|
|
12
|
+
|
|
13
|
+
if (value === 60) {
|
|
14
|
+
/** Skipping prefix "an"/"un"/"una" for 1 hour */
|
|
15
|
+
|
|
16
|
+
if (str.includes(" ")) {
|
|
17
|
+
label = `1 ${str.split(" ")[1]}`; // Combine "1" with second part of the string
|
|
18
|
+
} else {
|
|
19
|
+
label = `1 ${str}`; // Use dayjs provided translation
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
label = str;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
label,
|
|
27
|
+
value,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getSleepTimerOptions = (configuration) => {
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
label: configuration["playback_sleep_off_label"],
|
|
35
|
+
value: 0,
|
|
36
|
+
},
|
|
37
|
+
...[5, 10, 15, 30, 45, 60, 120].map(createTimerOptions),
|
|
38
|
+
];
|
|
39
|
+
};
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import queryString from "query-string";
|
|
2
2
|
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
3
3
|
|
|
4
|
-
export const getLiveUrl = (url: string) => {
|
|
5
|
-
// Use the replace method with a regular expression to replace all instances of subClass with livePage
|
|
6
|
-
return url.replace(new RegExp("subClass", "g"), "livePage");
|
|
7
|
-
};
|
|
8
|
-
|
|
9
4
|
export const addQueryParam = (
|
|
10
5
|
baseUrl: string,
|
|
11
6
|
paramName: string,
|
|
@@ -1 +1,118 @@
|
|
|
1
|
+
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
2
|
+
|
|
1
3
|
export * from "./Utils";
|
|
4
|
+
|
|
5
|
+
export const getBottomSheetModalIconProps = (theme) => ({
|
|
6
|
+
height: theme["audio_modal_bottom_sheet_item_icon_height"],
|
|
7
|
+
width: theme["audio_modal_bottom_sheet_item_icon_width"],
|
|
8
|
+
marginBottom: theme["audio_modal_bottom_sheet_item_icon_margin_bottom"],
|
|
9
|
+
marginLeft: theme["audio_modal_bottom_sheet_item_icon_margin_left"],
|
|
10
|
+
marginRight: theme["audio_modal_bottom_sheet_item_icon_margin_right"],
|
|
11
|
+
marginTop: theme["audio_modal_bottom_sheet_item_icon_margin_top"],
|
|
12
|
+
flavour: theme["modal_bottom_sheet_item_icon_flavour"],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const getBottomSheetModalLabelProps = (theme, width: number) => {
|
|
16
|
+
const {
|
|
17
|
+
audio_modal_bottom_sheet_item_label_android_letter_spacing,
|
|
18
|
+
audio_modal_bottom_sheet_item_label_ios_letter_spacing,
|
|
19
|
+
audio_modal_bottom_sheet_item_label_family_ios_font_selector,
|
|
20
|
+
audio_modal_bottom_sheet_item_label_family_android_font_selector,
|
|
21
|
+
audio_modal_bottom_sheet_item_label_focus_font_color,
|
|
22
|
+
audio_modal_bottom_sheet_item_label_font_color,
|
|
23
|
+
audio_modal_bottom_sheet_item_label_selected_font_color,
|
|
24
|
+
audio_modal_bottom_sheet_item_label_hover_selected_font_color,
|
|
25
|
+
audio_modal_bottom_sheet_item_label_font_size,
|
|
26
|
+
audio_modal_bottom_sheet_item_label_line_height,
|
|
27
|
+
audio_modal_bottom_sheet_item_label_margin_bottom,
|
|
28
|
+
audio_modal_bottom_sheet_item_label_margin_top,
|
|
29
|
+
audio_modal_bottom_sheet_item_label_margin_left,
|
|
30
|
+
audio_modal_bottom_sheet_item_label_margin_right,
|
|
31
|
+
audio_modal_bottom_sheet_item_label_transform,
|
|
32
|
+
audio_modal_bottom_sheet_item_icon_width,
|
|
33
|
+
audio_modal_bottom_sheet_item_icon_margin_left,
|
|
34
|
+
audio_modal_bottom_sheet_item_icon_margin_right,
|
|
35
|
+
audio_modal_bottom_sheet_item_margin_left,
|
|
36
|
+
audio_modal_bottom_sheet_item_margin_right,
|
|
37
|
+
audio_modal_bottom_sheet_item_padding_left,
|
|
38
|
+
audio_modal_bottom_sheet_item_padding_right,
|
|
39
|
+
} = theme;
|
|
40
|
+
|
|
41
|
+
const maxWidth =
|
|
42
|
+
width -
|
|
43
|
+
(audio_modal_bottom_sheet_item_icon_width +
|
|
44
|
+
audio_modal_bottom_sheet_item_icon_margin_left +
|
|
45
|
+
audio_modal_bottom_sheet_item_icon_margin_right +
|
|
46
|
+
audio_modal_bottom_sheet_item_margin_left +
|
|
47
|
+
audio_modal_bottom_sheet_item_margin_right +
|
|
48
|
+
audio_modal_bottom_sheet_item_padding_left +
|
|
49
|
+
audio_modal_bottom_sheet_item_padding_right);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
fontFamily: platformSelect({
|
|
53
|
+
ios: audio_modal_bottom_sheet_item_label_family_ios_font_selector,
|
|
54
|
+
android: audio_modal_bottom_sheet_item_label_family_android_font_selector,
|
|
55
|
+
}),
|
|
56
|
+
letterSpacing: platformSelect({
|
|
57
|
+
ios: audio_modal_bottom_sheet_item_label_ios_letter_spacing,
|
|
58
|
+
android: audio_modal_bottom_sheet_item_label_android_letter_spacing,
|
|
59
|
+
}),
|
|
60
|
+
fontSize: audio_modal_bottom_sheet_item_label_font_size,
|
|
61
|
+
color: audio_modal_bottom_sheet_item_label_font_color,
|
|
62
|
+
focusColor: audio_modal_bottom_sheet_item_label_focus_font_color,
|
|
63
|
+
hoverSelectedColor:
|
|
64
|
+
audio_modal_bottom_sheet_item_label_hover_selected_font_color,
|
|
65
|
+
selectedColor: audio_modal_bottom_sheet_item_label_selected_font_color,
|
|
66
|
+
lineHeight: audio_modal_bottom_sheet_item_label_line_height,
|
|
67
|
+
marginBottom: audio_modal_bottom_sheet_item_label_margin_bottom,
|
|
68
|
+
marginTop: audio_modal_bottom_sheet_item_label_margin_top,
|
|
69
|
+
marginLeft: audio_modal_bottom_sheet_item_label_margin_left,
|
|
70
|
+
marginRight: audio_modal_bottom_sheet_item_label_margin_right,
|
|
71
|
+
transform: audio_modal_bottom_sheet_item_label_transform,
|
|
72
|
+
maxWidth,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const getBottomSheetModalItemProps = (theme, width: number) => {
|
|
77
|
+
const {
|
|
78
|
+
audio_modal_bottom_sheet_item_background_color,
|
|
79
|
+
audio_modal_bottom_sheet_item_focus_background_color,
|
|
80
|
+
audio_modal_bottom_sheet_item_selected_background_color,
|
|
81
|
+
audio_modal_bottom_sheet_item_focused_selected_background_color,
|
|
82
|
+
audio_modal_bottom_sheet_item_corner_radius,
|
|
83
|
+
audio_modal_bottom_sheet_item_margin_bottom,
|
|
84
|
+
audio_modal_bottom_sheet_item_margin_left,
|
|
85
|
+
audio_modal_bottom_sheet_item_margin_right,
|
|
86
|
+
audio_modal_bottom_sheet_item_margin_top,
|
|
87
|
+
audio_modal_bottom_sheet_item_padding_bottom,
|
|
88
|
+
audio_modal_bottom_sheet_item_padding_left,
|
|
89
|
+
audio_modal_bottom_sheet_item_padding_right,
|
|
90
|
+
audio_modal_bottom_sheet_item_padding_top,
|
|
91
|
+
} = theme;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
backgroundColor: audio_modal_bottom_sheet_item_background_color,
|
|
95
|
+
focusedBackgroundColor:
|
|
96
|
+
audio_modal_bottom_sheet_item_focus_background_color,
|
|
97
|
+
selectedBackgroundColor:
|
|
98
|
+
audio_modal_bottom_sheet_item_selected_background_color,
|
|
99
|
+
focusedSelectedBackgroundColor:
|
|
100
|
+
audio_modal_bottom_sheet_item_focused_selected_background_color,
|
|
101
|
+
borderRadius: audio_modal_bottom_sheet_item_corner_radius,
|
|
102
|
+
marginBottom: audio_modal_bottom_sheet_item_margin_bottom,
|
|
103
|
+
marginTop: audio_modal_bottom_sheet_item_margin_top,
|
|
104
|
+
marginLeft: audio_modal_bottom_sheet_item_margin_left,
|
|
105
|
+
marginRight: audio_modal_bottom_sheet_item_margin_right,
|
|
106
|
+
paddingTop: audio_modal_bottom_sheet_item_padding_top,
|
|
107
|
+
paddingBottom: audio_modal_bottom_sheet_item_padding_bottom,
|
|
108
|
+
paddingRight: audio_modal_bottom_sheet_item_padding_right,
|
|
109
|
+
paddingLeft: audio_modal_bottom_sheet_item_padding_left,
|
|
110
|
+
maxWidth: width,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const getBottomSheetModalSelectedItemIcon = (theme) =>
|
|
115
|
+
theme["audio_modal_bottom_sheet_item_selected_icon"] || null;
|
|
116
|
+
|
|
117
|
+
export const getBottomSheetModalDefaultItemIcon = (theme) =>
|
|
118
|
+
theme["audio_modal_bottom_sheet_item_default_icon"] || null;
|
|
@@ -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
|
@@ -74,6 +74,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
74
74
|
state: QuickBrickPlayer.NativePlayerState = {
|
|
75
75
|
audioTrackId: null,
|
|
76
76
|
textTrackId: null,
|
|
77
|
+
isCasting: this.props.isCasting,
|
|
77
78
|
|
|
78
79
|
// TODO: Can be deleted, on controller logic
|
|
79
80
|
isAd: false,
|
|
@@ -698,6 +699,10 @@ export default class VideoPlayer extends React.Component<
|
|
|
698
699
|
];
|
|
699
700
|
};
|
|
700
701
|
|
|
702
|
+
get supportNativeCast() {
|
|
703
|
+
return this.props.controller.supportNativeCast();
|
|
704
|
+
}
|
|
705
|
+
|
|
701
706
|
render() {
|
|
702
707
|
const {
|
|
703
708
|
docked,
|
|
@@ -710,18 +715,15 @@ export default class VideoPlayer extends React.Component<
|
|
|
710
715
|
muted,
|
|
711
716
|
style,
|
|
712
717
|
castAction,
|
|
713
|
-
controller,
|
|
714
718
|
startComponentsAnimation,
|
|
715
719
|
} = this.props;
|
|
716
720
|
|
|
717
|
-
const supportNativeCast = controller.supportNativeCast();
|
|
718
|
-
|
|
719
721
|
const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
|
|
720
722
|
this.state;
|
|
721
723
|
|
|
722
724
|
const isAudioItem =
|
|
723
725
|
entry?.content?.type?.includes?.("audio") ||
|
|
724
|
-
entry?.type?.value
|
|
726
|
+
entry?.type?.value?.includes?.("audio");
|
|
725
727
|
|
|
726
728
|
const getStyle = (dimensions) => {
|
|
727
729
|
const generalStyles = isAudioItem ? styles.audio : styles.video;
|
|
@@ -791,8 +793,13 @@ export default class VideoPlayer extends React.Component<
|
|
|
791
793
|
controls: shouldUseNativeControls,
|
|
792
794
|
};
|
|
793
795
|
|
|
796
|
+
const isFullScreenAudioPlayer =
|
|
797
|
+
this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
|
|
798
|
+
|
|
799
|
+
const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
|
|
800
|
+
|
|
794
801
|
const pictureInPictureEnabled =
|
|
795
|
-
this.props.pluginConfiguration?.pictureInPictureEnabled;
|
|
802
|
+
this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
|
|
796
803
|
|
|
797
804
|
const nativeProps = {
|
|
798
805
|
entry, // Must be passed first in list
|
|
@@ -806,7 +813,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
806
813
|
nowPlayingEnabled,
|
|
807
814
|
paused,
|
|
808
815
|
seek,
|
|
809
|
-
rate,
|
|
810
816
|
textTrackId,
|
|
811
817
|
liveSeekingEnabled,
|
|
812
818
|
liveCatchUpEnabled,
|
|
@@ -817,7 +823,14 @@ export default class VideoPlayer extends React.Component<
|
|
|
817
823
|
accessibilityProps: getAllAccessibilityProps(
|
|
818
824
|
this.getPluginConfiguration()
|
|
819
825
|
),
|
|
820
|
-
};
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
if (!isAppleTV()) {
|
|
829
|
+
// TODO: Move to RN method and remove from props.
|
|
830
|
+
// Causes tvOS bug: native player passes rate on fast seek,
|
|
831
|
+
// RN returns it and it breaks playback.
|
|
832
|
+
nativeProps.rate = rate;
|
|
833
|
+
}
|
|
821
834
|
|
|
822
835
|
if (
|
|
823
836
|
this.isReactNativeMethodsSupported() &&
|
|
@@ -918,8 +931,8 @@ export default class VideoPlayer extends React.Component<
|
|
|
918
931
|
const playerContainerStyles = isApplePlatform()
|
|
919
932
|
? styles.playerContainerBlack
|
|
920
933
|
: isTV() && isAudioItem
|
|
921
|
-
|
|
922
|
-
|
|
934
|
+
? styles.playerContainerTransparent
|
|
935
|
+
: styles.playerContainerBlack;
|
|
923
936
|
|
|
924
937
|
const isInline = () => {
|
|
925
938
|
if (isTV()) {
|
|
@@ -960,9 +973,6 @@ export default class VideoPlayer extends React.Component<
|
|
|
960
973
|
) : null;
|
|
961
974
|
};
|
|
962
975
|
|
|
963
|
-
const isFullScreenAudioPlayer =
|
|
964
|
-
this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
|
|
965
|
-
|
|
966
976
|
const sharedWrapperViewProps = {
|
|
967
977
|
style,
|
|
968
978
|
entry,
|
|
@@ -987,17 +997,21 @@ export default class VideoPlayer extends React.Component<
|
|
|
987
997
|
const backgroundImageKey =
|
|
988
998
|
this.getPluginConfiguration()?.player_preview_image_key;
|
|
989
999
|
|
|
1000
|
+
const shouldRenderNativePlayer =
|
|
1001
|
+
!this.props.isCasting || this.supportNativeCast;
|
|
1002
|
+
|
|
990
1003
|
const renderMobilePlayer = () => {
|
|
991
1004
|
return isFullScreenAudioPlayer && isAudioItem ? (
|
|
992
|
-
|
|
993
|
-
|
|
1005
|
+
// Ensure the View remains in the native hierarchy for proper rendering and interaction
|
|
1006
|
+
<View collapsable={false} style={containerStyle}>
|
|
1007
|
+
{shouldRenderNativePlayer ? (
|
|
994
1008
|
<this.props.PlayerComponent
|
|
995
1009
|
ref={this._assignRoot}
|
|
996
1010
|
{...nativeEvents}
|
|
997
1011
|
{...nativeProps}
|
|
998
1012
|
listener={this.props.listener}
|
|
999
1013
|
/>
|
|
1000
|
-
)}
|
|
1014
|
+
) : null}
|
|
1001
1015
|
<AudioPlayerWrapper {...sharedWrapperViewProps} />
|
|
1002
1016
|
</View>
|
|
1003
1017
|
) : (
|
|
@@ -1012,7 +1026,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1012
1026
|
return (
|
|
1013
1027
|
<View style={styles.container}>
|
|
1014
1028
|
<View style={[playerContainerStyles, playerDimensions]}>
|
|
1015
|
-
{isAudioItem
|
|
1029
|
+
{isAudioItem ? (
|
|
1016
1030
|
<AnimationComponent
|
|
1017
1031
|
animationType={ComponentAnimationType.player}
|
|
1018
1032
|
additionalData={{
|
|
@@ -1031,21 +1045,24 @@ export default class VideoPlayer extends React.Component<
|
|
|
1031
1045
|
plugin_configuration={this.getPluginConfiguration()}
|
|
1032
1046
|
/>
|
|
1033
1047
|
</AnimationComponent>
|
|
1034
|
-
)}
|
|
1048
|
+
) : null}
|
|
1035
1049
|
|
|
1036
|
-
{shouldRenderBackground
|
|
1050
|
+
{shouldRenderBackground ? (
|
|
1037
1051
|
<PlayerImageBackground
|
|
1038
1052
|
imageKey={backgroundImageKey}
|
|
1039
1053
|
configuration={this.getPluginConfiguration()}
|
|
1040
1054
|
entry={entry}
|
|
1041
1055
|
style={getBackgroundImageStyle(playerDimensions)}
|
|
1042
1056
|
/>
|
|
1043
|
-
)}
|
|
1057
|
+
) : null}
|
|
1044
1058
|
|
|
1045
|
-
{
|
|
1059
|
+
{shouldRenderNativePlayer ? (
|
|
1046
1060
|
<AnimationComponent
|
|
1047
1061
|
animationType={ComponentAnimationType.player}
|
|
1048
|
-
additionalData={
|
|
1062
|
+
additionalData={{
|
|
1063
|
+
...playerDimensions,
|
|
1064
|
+
disableAnimatedComponent: isAudioItem,
|
|
1065
|
+
}}
|
|
1049
1066
|
style={getStyle(playerDimensions)}
|
|
1050
1067
|
>
|
|
1051
1068
|
<this.props.PlayerComponent
|
|
@@ -1064,7 +1081,7 @@ export default class VideoPlayer extends React.Component<
|
|
|
1064
1081
|
{this.childrenWithPlayNext()}
|
|
1065
1082
|
</this.props.PlayerComponent>
|
|
1066
1083
|
</AnimationComponent>
|
|
1067
|
-
)}
|
|
1084
|
+
) : null}
|
|
1068
1085
|
</View>
|
|
1069
1086
|
|
|
1070
1087
|
{renderTransportControls(playerDimensions)}
|
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}
|