@applicaster/quick-brick-player 16.0.0-rc.6 → 16.0.0-rc.60
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/AudioDescription.tsx +46 -11
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +38 -48
- package/src/Player/AudioLayer/Layout/Controls/__tests__/utils.test.ts +66 -0
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +34 -14
- package/src/Player/AudioLayer/Layout/Controls/utils.ts +26 -0
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +1 -13
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +0 -4
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +0 -4
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +0 -4
- package/src/Player/AudioLayer/SleepTimerManager.ts +5 -1
- package/src/Player/AudioLayer/SpeedManager.ts +4 -0
- package/src/Player/AudioLayer/__tests__/AudioDescription.test.tsx +147 -0
- package/src/Player/AudioLayer/__tests__/AudioPlayerWrapper.test.tsx +130 -0
- package/src/Player/AudioLayer/__tests__/SleepTimerManager.test.ts +78 -0
- package/src/Player/AudioLayer/__tests__/SpeedManager.test.ts +57 -0
- package/src/Player/AudioLayer/__tests__/playerActions.test.ts +351 -0
- package/src/Player/AudioLayer/__tests__/usePlaylistNavigation.test.ts +387 -0
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +9 -8
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/__tests__/SleepTimerModalHeader.test.tsx +53 -0
- package/src/Player/AudioLayer/playerActions.ts +280 -0
- package/src/Player/AudioLayer/usePlaylistNavigation.ts +134 -0
- package/src/Player/AudioLayer/useSleepTimerModal.ts +20 -57
- package/src/Player/__tests__/getNativeProps.test.ts +115 -0
- package/src/Player/__tests__/toNativeMetadata.test.ts +77 -0
- package/src/Player/index.tsx +72 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-player",
|
|
3
|
-
"version": "16.0.0-rc.
|
|
3
|
+
"version": "16.0.0-rc.60",
|
|
4
4
|
"description": "Quick Brick Player",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@applicaster/quick-brick-mobile-transport-controls": "16.0.0-rc.
|
|
39
|
-
"@applicaster/quick-brick-tv-transport-controls": "16.0.0-rc.
|
|
40
|
-
"@applicaster/zapp-react-native-tvos-app": "16.0.0-rc.
|
|
41
|
-
"@applicaster/zapp-react-native-ui-components": "16.0.0-rc.
|
|
42
|
-
"@applicaster/zapp-react-native-utils": "16.0.0-rc.
|
|
38
|
+
"@applicaster/quick-brick-mobile-transport-controls": "16.0.0-rc.60",
|
|
39
|
+
"@applicaster/quick-brick-tv-transport-controls": "16.0.0-rc.60",
|
|
40
|
+
"@applicaster/zapp-react-native-tvos-app": "16.0.0-rc.60",
|
|
41
|
+
"@applicaster/zapp-react-native-ui-components": "16.0.0-rc.60",
|
|
42
|
+
"@applicaster/zapp-react-native-utils": "16.0.0-rc.60",
|
|
43
43
|
"query-string": "7.1.3",
|
|
44
44
|
"shaka-player": "4.3.5",
|
|
45
45
|
"typeface-montserrat": "^0.0.54",
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { StyleSheet, Text, TextStyle, View } from "react-native";
|
|
3
3
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
4
|
+
import { usePlayerContent } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerContent";
|
|
4
5
|
|
|
5
6
|
type AudioDescriptionProps = {
|
|
6
|
-
title: string | number;
|
|
7
|
-
summary: string | number;
|
|
8
7
|
layoutState: {
|
|
9
8
|
inline: boolean;
|
|
10
9
|
docked: boolean;
|
|
@@ -96,17 +95,48 @@ const getSummaryStyles = (value: GetValue, prefix: string): TextStyle =>
|
|
|
96
95
|
}),
|
|
97
96
|
}) as const;
|
|
98
97
|
|
|
99
|
-
const getHeight = (
|
|
100
|
-
|
|
98
|
+
const getHeight = ({
|
|
99
|
+
showTitle,
|
|
100
|
+
showSummary,
|
|
101
|
+
titleLineHeight,
|
|
102
|
+
subtitleLineHeight,
|
|
103
|
+
}: {
|
|
104
|
+
showTitle: boolean;
|
|
105
|
+
showSummary: boolean;
|
|
106
|
+
titleLineHeight: number;
|
|
107
|
+
subtitleLineHeight: number;
|
|
108
|
+
}) => {
|
|
109
|
+
const titleHeight = showTitle
|
|
110
|
+
? titleLineHeight * TITLE_LINES + TITLE_PADDING_BOTTOM
|
|
111
|
+
: 0;
|
|
112
|
+
|
|
113
|
+
const summaryHeight = showSummary ? subtitleLineHeight : 0;
|
|
114
|
+
|
|
115
|
+
return titleHeight + summaryHeight;
|
|
116
|
+
};
|
|
101
117
|
|
|
102
118
|
export const AudioDescription = (props: AudioDescriptionProps) => {
|
|
103
|
-
const {
|
|
119
|
+
const { layoutState, value, maxWidth } = props;
|
|
120
|
+
const { title, summary } = usePlayerContent();
|
|
104
121
|
const prefix = layoutState.docked ? "docked_player_" : "";
|
|
105
122
|
|
|
106
123
|
const summaryStyles = getSummaryStyles(value, prefix);
|
|
107
124
|
const titleStyles = getTitleStyles(value, prefix);
|
|
108
125
|
|
|
109
|
-
|
|
126
|
+
// Both depend on the Studio toggle alone, never on whether the text has
|
|
127
|
+
// arrived yet. Live audio resolves its title/summary asynchronously (the
|
|
128
|
+
// channel feed is polled after playback starts), so gating on the value
|
|
129
|
+
// would collapse the row on first render and expand it a second later -
|
|
130
|
+
// the control has to hold its place and render an empty line instead.
|
|
131
|
+
const showTitle = Boolean(value(`${prefix}title`));
|
|
132
|
+
const showSummary = Boolean(value(`${prefix}subtitle`));
|
|
133
|
+
|
|
134
|
+
const height = getHeight({
|
|
135
|
+
showTitle,
|
|
136
|
+
showSummary,
|
|
137
|
+
titleLineHeight: titleStyles.lineHeight,
|
|
138
|
+
subtitleLineHeight: summaryStyles.lineHeight,
|
|
139
|
+
});
|
|
110
140
|
|
|
111
141
|
return (
|
|
112
142
|
<View
|
|
@@ -118,15 +148,20 @@ export const AudioDescription = (props: AudioDescriptionProps) => {
|
|
|
118
148
|
},
|
|
119
149
|
]}
|
|
120
150
|
>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
{showTitle ? (
|
|
152
|
+
<Text
|
|
153
|
+
style={[styles.titleText, titleStyles]}
|
|
154
|
+
numberOfLines={TITLE_LINES}
|
|
155
|
+
>
|
|
156
|
+
{title ?? ""}
|
|
157
|
+
</Text>
|
|
158
|
+
) : null}
|
|
159
|
+
{showSummary ? (
|
|
125
160
|
<Text
|
|
126
161
|
style={[styles.summaryText, summaryStyles]}
|
|
127
162
|
numberOfLines={SUBTITLE_LINES}
|
|
128
163
|
>
|
|
129
|
-
{summary}
|
|
164
|
+
{summary ?? ""}
|
|
130
165
|
</Text>
|
|
131
166
|
) : null}
|
|
132
167
|
</View>
|
|
@@ -12,24 +12,17 @@ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
|
12
12
|
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
13
13
|
import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
|
|
14
14
|
import { formatSpeed } from "./utils";
|
|
15
|
-
import {
|
|
16
|
-
getBottomSheetModalDefaultItemIcon,
|
|
17
|
-
getBottomSheetModalIconProps,
|
|
18
|
-
getBottomSheetModalItemProps,
|
|
19
|
-
getBottomSheetModalLabelProps,
|
|
20
|
-
getBottomSheetModalSelectedItemIcon,
|
|
21
|
-
} from "../Utils";
|
|
22
|
-
import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
|
|
23
|
-
|
|
24
15
|
import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
25
|
-
|
|
26
16
|
import { DEFAULT_SPEED, PlaybackSpeed, SpeedController } from "./SpeedManager";
|
|
27
17
|
import {
|
|
28
18
|
dismissModal as dismissBottomSheetModal,
|
|
29
|
-
openBottomSheetModal,
|
|
30
19
|
useModalStoreState,
|
|
31
20
|
} from "@applicaster/zapp-react-native-utils/modalState";
|
|
32
|
-
import {
|
|
21
|
+
import { uiActionsRegistry } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
|
|
22
|
+
import {
|
|
23
|
+
PLAYER_ACTION_IDENTIFIERS,
|
|
24
|
+
registerPlayerActions,
|
|
25
|
+
} from "./playerActions";
|
|
33
26
|
|
|
34
27
|
const { log_debug } = createLogger({
|
|
35
28
|
subsystem: "UIComponent",
|
|
@@ -89,11 +82,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
89
82
|
playNextData = null,
|
|
90
83
|
} = props;
|
|
91
84
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
// For live audio the programme changes while the entry stays the same, so
|
|
86
|
+
// the player polls the channel's programme feed and republishes its content.
|
|
87
|
+
// Everything about that - whether the content is live at all, where the feed
|
|
88
|
+
// lives, how often to poll - is decided by `OverlaysObserver`, which loads
|
|
89
|
+
// the feed without React. This effect owns only the lifecycle: keep the
|
|
90
|
+
// metadata fresh while this player is mounted. Consumers read the result
|
|
91
|
+
// through `usePlayerContent` like any other player content.
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (!player) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const observer = player.getOverlayObservable();
|
|
98
|
+
|
|
99
|
+
observer.startLiveContentUpdates();
|
|
100
|
+
|
|
101
|
+
return () => observer.stopLiveContentUpdates();
|
|
102
|
+
// Keyed on the entry's id rather than the object: starting the updates
|
|
103
|
+
// fires an immediate request, so a re-created `entry` prop with identical
|
|
104
|
+
// contents would re-fetch the feed on every render.
|
|
105
|
+
}, [player, playerState?.isLive, entry?.id]);
|
|
97
106
|
|
|
98
107
|
const [speedManager, setSpeedManager] = useState<SpeedController | null>(
|
|
99
108
|
null
|
|
@@ -103,6 +112,14 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
103
112
|
setSpeedManager(player ? new SpeedController(player) : null);
|
|
104
113
|
}, [player]);
|
|
105
114
|
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!speedManager || playerState.isLive) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return registerPlayerActions({ speedController: speedManager });
|
|
121
|
+
}, [speedManager, playerState.isLive]);
|
|
122
|
+
|
|
106
123
|
useEffect(() => {
|
|
107
124
|
if (!player) return;
|
|
108
125
|
|
|
@@ -201,36 +218,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
201
218
|
player?.forward(seekDuration);
|
|
202
219
|
}, [seekDuration, player]);
|
|
203
220
|
|
|
204
|
-
const onSpeedSelect = React.useCallback(
|
|
205
|
-
async (item) => {
|
|
206
|
-
await speedManager?.setPlaybackSpeed(item);
|
|
207
|
-
},
|
|
208
|
-
[speedManager]
|
|
209
|
-
);
|
|
210
|
-
|
|
211
221
|
const onSpeedButtonPress = React.useCallback(() => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
onPress: onSpeedSelect,
|
|
217
|
-
title: configuration.playback_speed_title,
|
|
218
|
-
itemProps: getBottomSheetModalItemProps,
|
|
219
|
-
iconProps: getBottomSheetModalIconProps,
|
|
220
|
-
labelProps: getBottomSheetModalLabelProps,
|
|
221
|
-
getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
|
|
222
|
-
getDefaultItemIcon: getBottomSheetModalDefaultItemIcon,
|
|
223
|
-
iconPlacement: "right",
|
|
224
|
-
},
|
|
225
|
-
options: {
|
|
226
|
-
supportedOrientations: [
|
|
227
|
-
"portrait",
|
|
228
|
-
"landscape",
|
|
229
|
-
"portrait-upside-down",
|
|
230
|
-
],
|
|
231
|
-
},
|
|
232
|
-
});
|
|
233
|
-
}, [playbackSpeed, onSpeedSelect, configuration.playback_speed_title]);
|
|
222
|
+
uiActionsRegistry
|
|
223
|
+
.getAction(PLAYER_ACTION_IDENTIFIERS.PLAYBACK_SPEED)
|
|
224
|
+
?.invokeAction(entry);
|
|
225
|
+
}, [entry]);
|
|
234
226
|
|
|
235
227
|
const onPlayerSeek = React.useCallback(
|
|
236
228
|
(targetTime: number) => {
|
|
@@ -282,8 +274,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
|
|
|
282
274
|
configuration={configuration}
|
|
283
275
|
dimensions={dimensions}
|
|
284
276
|
playNextData={playNextData}
|
|
285
|
-
title={title}
|
|
286
|
-
summary={summary}
|
|
287
277
|
/>
|
|
288
278
|
</View>
|
|
289
279
|
</SafeAreaView>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
nextTrackButton,
|
|
3
|
+
previousTrackButton,
|
|
4
|
+
usesPlaylistControls,
|
|
5
|
+
} from "../utils";
|
|
6
|
+
|
|
7
|
+
describe("usesPlaylistControls", () => {
|
|
8
|
+
const configuredAs = (layout?: unknown): GetValue =>
|
|
9
|
+
((key: string) =>
|
|
10
|
+
key === "player_controls_layout" ? layout : null) as unknown as GetValue;
|
|
11
|
+
|
|
12
|
+
it("opts in only for the playlist controls value", () => {
|
|
13
|
+
expect(usesPlaylistControls(configuredAs("playlist_controls"))).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("keeps the original controls for the default value", () => {
|
|
17
|
+
expect(
|
|
18
|
+
usesPlaylistControls(configuredAs("playback_speed_and_sleep_timer"))
|
|
19
|
+
).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it.each([
|
|
23
|
+
["an undeclared key", undefined],
|
|
24
|
+
["a null value", null],
|
|
25
|
+
["an empty string", ""],
|
|
26
|
+
["an unrecognised value", "something_else"],
|
|
27
|
+
])("keeps the original controls for %s", (_label, layout) => {
|
|
28
|
+
expect(usesPlaylistControls(configuredAs(layout))).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("track control buttons", () => {
|
|
33
|
+
it("builds a previous button using the previous_track icon", () => {
|
|
34
|
+
const onPress = jest.fn();
|
|
35
|
+
|
|
36
|
+
expect(previousTrackButton({ isTablet: false, onPress })).toMatchObject({
|
|
37
|
+
size: "small",
|
|
38
|
+
icon: "previous_track",
|
|
39
|
+
onPress,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("builds a next button using the next_track icon", () => {
|
|
44
|
+
const onPress = jest.fn();
|
|
45
|
+
|
|
46
|
+
expect(nextTrackButton({ isTablet: false, onPress })).toMatchObject({
|
|
47
|
+
size: "small",
|
|
48
|
+
icon: "next_track",
|
|
49
|
+
onPress,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("propagates the disabled flag", () => {
|
|
54
|
+
expect(
|
|
55
|
+
nextTrackButton({ isTablet: false, onPress: jest.fn(), disabled: true })
|
|
56
|
+
).toMatchObject({ disabled: true });
|
|
57
|
+
|
|
58
|
+
expect(
|
|
59
|
+
previousTrackButton({
|
|
60
|
+
isTablet: false,
|
|
61
|
+
onPress: jest.fn(),
|
|
62
|
+
disabled: true,
|
|
63
|
+
})
|
|
64
|
+
).toMatchObject({ disabled: true });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -2,10 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { StyleSheet, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
|
|
5
|
-
import {
|
|
6
|
-
useIsTablet,
|
|
7
|
-
useZStore,
|
|
8
|
-
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
5
|
+
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
9
6
|
import {
|
|
10
7
|
Controls as MobileTransportControls,
|
|
11
8
|
ProgressBar,
|
|
@@ -18,14 +15,18 @@ import { VerticalGutter } from "./VerticalGutter";
|
|
|
18
15
|
|
|
19
16
|
import {
|
|
20
17
|
forwardButton,
|
|
18
|
+
nextTrackButton,
|
|
21
19
|
playbackButton,
|
|
20
|
+
previousTrackButton,
|
|
22
21
|
rewindButton,
|
|
23
22
|
skipToLiveButton,
|
|
24
23
|
skipToStartButton,
|
|
25
24
|
sleepButton,
|
|
26
25
|
speedButton,
|
|
26
|
+
usesPlaylistControls,
|
|
27
27
|
} from "./utils";
|
|
28
28
|
import { useSleepTimerModal } from "../../useSleepTimerModal";
|
|
29
|
+
import { usePlaylistNavigation } from "../../usePlaylistNavigation";
|
|
29
30
|
|
|
30
31
|
const styles = StyleSheet.create({
|
|
31
32
|
flex: {
|
|
@@ -61,8 +62,6 @@ type Props = {
|
|
|
61
62
|
layoutState: QuickBrickPlayer.LayoutState;
|
|
62
63
|
playerState: QuickBrickPlayer.PlayerState;
|
|
63
64
|
value: GetValue;
|
|
64
|
-
title: string | number;
|
|
65
|
-
summary: string | number;
|
|
66
65
|
|
|
67
66
|
playbackSpeedName: string;
|
|
68
67
|
|
|
@@ -85,8 +84,6 @@ export const Controls = (props: Props) => {
|
|
|
85
84
|
layoutState,
|
|
86
85
|
playerState,
|
|
87
86
|
value,
|
|
88
|
-
title,
|
|
89
|
-
summary,
|
|
90
87
|
|
|
91
88
|
playbackSpeedName,
|
|
92
89
|
|
|
@@ -100,13 +97,17 @@ export const Controls = (props: Props) => {
|
|
|
100
97
|
maxWidth = 320,
|
|
101
98
|
} = props;
|
|
102
99
|
|
|
103
|
-
const configuration = useZStore("playerConfiguration");
|
|
104
|
-
|
|
105
100
|
const { sleepTimerState, onSleepButtonPress } = useSleepTimerModal(
|
|
106
|
-
playerManager.getActivePlayer()
|
|
107
|
-
configuration
|
|
101
|
+
playerManager.getActivePlayer()
|
|
108
102
|
);
|
|
109
103
|
|
|
104
|
+
const { hasNext, hasPrevious, goNext, goPrevious } = usePlaylistNavigation(
|
|
105
|
+
entry,
|
|
106
|
+
playerState
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const isPlaylistControlsLayout = usesPlaylistControls(value);
|
|
110
|
+
|
|
110
111
|
const timerRemaining =
|
|
111
112
|
sleepTimerState.timerDate !== 0 && +sleepTimerState.timerDate > Date.now()
|
|
112
113
|
? sleepTimerState.timerDate
|
|
@@ -203,6 +204,22 @@ export const Controls = (props: Props) => {
|
|
|
203
204
|
return [skipToStart, rewind, play, forward, skipToLive];
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
if (isPlaylistControlsLayout) {
|
|
208
|
+
const previous = previousTrackButton({
|
|
209
|
+
isTablet,
|
|
210
|
+
onPress: goPrevious,
|
|
211
|
+
disabled: !hasPrevious,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const next = nextTrackButton({
|
|
215
|
+
isTablet,
|
|
216
|
+
onPress: goNext,
|
|
217
|
+
disabled: !hasNext,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return [previous, rewind, play, forward, next];
|
|
221
|
+
}
|
|
222
|
+
|
|
206
223
|
return [speed, rewind, play, forward, sleep];
|
|
207
224
|
}, [
|
|
208
225
|
playerState,
|
|
@@ -220,6 +237,11 @@ export const Controls = (props: Props) => {
|
|
|
220
237
|
playbackSleepName,
|
|
221
238
|
controller,
|
|
222
239
|
isLiveShowFullControls,
|
|
240
|
+
isPlaylistControlsLayout,
|
|
241
|
+
hasNext,
|
|
242
|
+
hasPrevious,
|
|
243
|
+
goNext,
|
|
244
|
+
goPrevious,
|
|
223
245
|
]);
|
|
224
246
|
|
|
225
247
|
return (
|
|
@@ -227,8 +249,6 @@ export const Controls = (props: Props) => {
|
|
|
227
249
|
<VerticalGutter />
|
|
228
250
|
|
|
229
251
|
<AudioDescription
|
|
230
|
-
title={title}
|
|
231
|
-
summary={summary}
|
|
232
252
|
value={value}
|
|
233
253
|
layoutState={layoutState}
|
|
234
254
|
maxWidth={maxWidth}
|
|
@@ -103,3 +103,29 @@ export const speedButton = (props: ButtonsProps): ControlsButton =>
|
|
|
103
103
|
|
|
104
104
|
export const sleepButton = (props: ButtonsProps): ControlsButton =>
|
|
105
105
|
sleepButtonByName(props);
|
|
106
|
+
|
|
107
|
+
// Opt-in: a player plugin that does not declare this key returns undefined,
|
|
108
|
+
// which has to read as the default rather than as an opt-in.
|
|
109
|
+
export const usesPlaylistControls = (value: GetValue): boolean =>
|
|
110
|
+
value("player_controls_layout") === "playlist_controls";
|
|
111
|
+
|
|
112
|
+
type TrackButtonProps = ButtonsProps & { disabled?: boolean };
|
|
113
|
+
|
|
114
|
+
const trackButtonByName = ({
|
|
115
|
+
name,
|
|
116
|
+
onPress,
|
|
117
|
+
style,
|
|
118
|
+
disabled,
|
|
119
|
+
}: TrackButtonProps & { name: string }): ControlsButton => ({
|
|
120
|
+
size: "small",
|
|
121
|
+
icon: name,
|
|
122
|
+
onPress,
|
|
123
|
+
style,
|
|
124
|
+
disabled,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export const previousTrackButton = (props: TrackButtonProps): ControlsButton =>
|
|
128
|
+
trackButtonByName({ ...props, name: "previous_track" });
|
|
129
|
+
|
|
130
|
+
export const nextTrackButton = (props: TrackButtonProps): ControlsButton =>
|
|
131
|
+
trackButtonByName({ ...props, name: "next_track" });
|
|
@@ -18,7 +18,6 @@ import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Compo
|
|
|
18
18
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
19
19
|
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
|
|
20
20
|
import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
|
|
21
|
-
import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
|
|
22
21
|
import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
|
|
23
22
|
import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/utils";
|
|
24
23
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
@@ -200,12 +199,6 @@ export const DockedControls = (props: Props) => {
|
|
|
200
199
|
[...Object.values(configuration), playerState.seekableDuration]
|
|
201
200
|
);
|
|
202
201
|
|
|
203
|
-
const { title, summary } = useTitleSummaryOverlay(
|
|
204
|
-
entry,
|
|
205
|
-
playerState?.isLive,
|
|
206
|
-
configuration?.audio_live_player_update_interval
|
|
207
|
-
);
|
|
208
|
-
|
|
209
202
|
const isTablet = useIsTablet();
|
|
210
203
|
|
|
211
204
|
const liveButton = Boolean(value("live_badge_enabled"));
|
|
@@ -261,11 +254,6 @@ export const DockedControls = (props: Props) => {
|
|
|
261
254
|
};
|
|
262
255
|
}, [bottomTabsVisible, insets.bottom]);
|
|
263
256
|
|
|
264
|
-
const contentInfoContent = React.useMemo(
|
|
265
|
-
() => ({ ...entry, title, summary }),
|
|
266
|
-
[title, summary, entry]
|
|
267
|
-
);
|
|
268
|
-
|
|
269
257
|
return (
|
|
270
258
|
<>
|
|
271
259
|
<View
|
|
@@ -297,7 +285,7 @@ export const DockedControls = (props: Props) => {
|
|
|
297
285
|
testID="content-info"
|
|
298
286
|
style={[styles.contentInfo, backgroundColorStyle]}
|
|
299
287
|
>
|
|
300
|
-
<ContentInfo
|
|
288
|
+
<ContentInfo value={value} docked />
|
|
301
289
|
</View>
|
|
302
290
|
</View>
|
|
303
291
|
<View
|
|
@@ -78,8 +78,6 @@ export function MobileLayout({
|
|
|
78
78
|
onSpeedButtonPress,
|
|
79
79
|
playbackSpeedName,
|
|
80
80
|
configuration,
|
|
81
|
-
title,
|
|
82
|
-
summary,
|
|
83
81
|
}: Props) {
|
|
84
82
|
const { docked } = layoutState;
|
|
85
83
|
|
|
@@ -165,8 +163,6 @@ export function MobileLayout({
|
|
|
165
163
|
layoutState={layoutState}
|
|
166
164
|
playerState={playerState}
|
|
167
165
|
value={value}
|
|
168
|
-
title={title}
|
|
169
|
-
summary={summary}
|
|
170
166
|
playbackSpeedName={playbackSpeedName}
|
|
171
167
|
onPlaybackButtonPress={onPlaybackButtonPress}
|
|
172
168
|
onRewindButtonPress={onRewindButtonPress}
|
|
@@ -75,8 +75,6 @@ export function TabletLandscapeLayout({
|
|
|
75
75
|
onSpeedButtonPress,
|
|
76
76
|
playbackSpeedName,
|
|
77
77
|
configuration,
|
|
78
|
-
title,
|
|
79
|
-
summary,
|
|
80
78
|
}: Props) {
|
|
81
79
|
const [imageWidth, setImageWidth] = React.useState(0);
|
|
82
80
|
const { docked } = layoutState;
|
|
@@ -132,8 +130,6 @@ export function TabletLandscapeLayout({
|
|
|
132
130
|
layoutState={layoutState}
|
|
133
131
|
playerState={playerState}
|
|
134
132
|
value={value}
|
|
135
|
-
title={title}
|
|
136
|
-
summary={summary}
|
|
137
133
|
playbackSpeedName={playbackSpeedName}
|
|
138
134
|
onPlaybackButtonPress={onPlaybackButtonPress}
|
|
139
135
|
onRewindButtonPress={onRewindButtonPress}
|
|
@@ -81,8 +81,6 @@ export function TabletPortraitLayout({
|
|
|
81
81
|
onSpeedButtonPress,
|
|
82
82
|
playbackSpeedName,
|
|
83
83
|
configuration,
|
|
84
|
-
title,
|
|
85
|
-
summary,
|
|
86
84
|
}: Props) {
|
|
87
85
|
const { docked } = layoutState;
|
|
88
86
|
|
|
@@ -157,8 +155,6 @@ export function TabletPortraitLayout({
|
|
|
157
155
|
layoutState={layoutState}
|
|
158
156
|
playerState={playerState}
|
|
159
157
|
value={value}
|
|
160
|
-
title={title}
|
|
161
|
-
summary={summary}
|
|
162
158
|
playbackSpeedName={playbackSpeedName}
|
|
163
159
|
onPlaybackButtonPress={onPlaybackButtonPress}
|
|
164
160
|
onRewindButtonPress={onRewindButtonPress}
|
|
@@ -41,6 +41,10 @@ export class SleepTimerController {
|
|
|
41
41
|
return this.sleepTimerStateSubject.asObservable();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
get currentState(): SleepTimerState {
|
|
45
|
+
return this.sleepTimerStateSubject.getValue();
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
private async scheduleSleep(sleepDate): Promise<boolean> {
|
|
45
49
|
log_debug(`Setting sleep interval to ${sleepDate}`);
|
|
46
50
|
|
|
@@ -118,7 +122,7 @@ export class SleepTimerController {
|
|
|
118
122
|
|
|
119
123
|
log_debug("Sleep state restored");
|
|
120
124
|
|
|
121
|
-
this.setSleepTimer(item as SleepTimer, futureSleepDate);
|
|
125
|
+
await this.setSleepTimer(item as SleepTimer, futureSleepDate);
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
// Will be called if user closed the player explicitly.
|
|
@@ -33,6 +33,10 @@ export class SpeedController {
|
|
|
33
33
|
return this.playbackSpeedSubject.asObservable();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
get currentSpeed(): PlaybackSpeed {
|
|
37
|
+
return this.playbackSpeedSubject.getValue();
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
async loadSpeed() {
|
|
37
41
|
const speed = await localStorage.getItem(SPEED_PREFERENCE_KEY, NAMESPACE);
|
|
38
42
|
|