@applicaster/quick-brick-player 15.1.13-alpha.1034774223 → 15.1.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "15.1.13-alpha.1034774223",
3
+ "version": "15.1.13",
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": "15.1.13-alpha.1034774223",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "15.1.9",
39
39
  "@applicaster/quick-brick-tv-transport-controls": "15.1.9",
40
- "@applicaster/zapp-react-native-tvos-app": "15.1.13-alpha.1034774223",
41
- "@applicaster/zapp-react-native-ui-components": "15.1.13-alpha.1034774223",
42
- "@applicaster/zapp-react-native-utils": "15.1.13-alpha.1034774223",
40
+ "@applicaster/zapp-react-native-tvos-app": "15.1.13",
41
+ "@applicaster/zapp-react-native-ui-components": "15.1.13",
42
+ "@applicaster/zapp-react-native-utils": "15.1.13",
43
43
  "query-string": "7.1.3",
44
44
  "shaka-player": "4.3.5",
45
45
  "typeface-montserrat": "^0.0.54",
@@ -12,18 +12,23 @@ 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";
15
22
 
16
23
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
24
+
17
25
  import { DEFAULT_SPEED, PlaybackSpeed, SpeedController } from "./SpeedManager";
18
26
  import {
19
27
  dismissModal as dismissBottomSheetModal,
28
+ openBottomSheetModal,
20
29
  useModalStoreState,
21
30
  } from "@applicaster/zapp-react-native-utils/modalState";
22
- import { uiActionsRegistry } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
23
- import {
24
- PLAYER_ACTION_IDENTIFIERS,
25
- registerPlayerActions,
26
- } from "./playerActions";
31
+ import { speedOptions } from "../Const/speedOptions";
27
32
 
28
33
  const { log_debug } = createLogger({
29
34
  subsystem: "UIComponent",
@@ -113,14 +118,6 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
113
118
  setSpeedManager(player ? new SpeedController(player) : null);
114
119
  }, [player]);
115
120
 
116
- useEffect(() => {
117
- if (!speedManager || playerState.isLive) {
118
- return;
119
- }
120
-
121
- return registerPlayerActions({ speedController: speedManager });
122
- }, [speedManager, playerState.isLive]);
123
-
124
121
  useEffect(() => {
125
122
  if (!player) return;
126
123
 
@@ -219,11 +216,36 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
219
216
  player?.forward(seekDuration);
220
217
  }, [seekDuration, player]);
221
218
 
219
+ const onSpeedSelect = React.useCallback(
220
+ async (item) => {
221
+ await speedManager?.setPlaybackSpeed(item);
222
+ },
223
+ [speedManager]
224
+ );
225
+
222
226
  const onSpeedButtonPress = React.useCallback(() => {
223
- uiActionsRegistry
224
- .getAction(PLAYER_ACTION_IDENTIFIERS.PLAYBACK_SPEED)
225
- ?.invokeAction(entry);
226
- }, [entry]);
227
+ openBottomSheetModal({
228
+ modalBottomSheetContentProps: {
229
+ items: speedOptions,
230
+ current_selection: playbackSpeed,
231
+ onPress: onSpeedSelect,
232
+ title: configuration.playback_speed_title,
233
+ itemProps: getBottomSheetModalItemProps,
234
+ iconProps: getBottomSheetModalIconProps,
235
+ labelProps: getBottomSheetModalLabelProps,
236
+ getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
237
+ getDefaultItemIcon: getBottomSheetModalDefaultItemIcon,
238
+ iconPlacement: "right",
239
+ },
240
+ options: {
241
+ supportedOrientations: [
242
+ "portrait",
243
+ "landscape",
244
+ "portrait-upside-down",
245
+ ],
246
+ },
247
+ });
248
+ }, [playbackSpeed, onSpeedSelect, configuration.playback_speed_title]);
227
249
 
228
250
  const onPlayerSeek = React.useCallback(
229
251
  (targetTime: number) => {
@@ -1,16 +1,14 @@
1
1
  import {
2
- controlsRowActionIdentifiers,
3
2
  nextTrackButton,
4
3
  previousTrackButton,
5
4
  usesPlaylistControls,
6
5
  } from "../utils";
7
- import { PLAYER_ACTION_IDENTIFIERS } from "../../../playerActions";
8
-
9
- const configuredAs = (layout?: unknown): GetValue =>
10
- ((key: string) =>
11
- key === "player_controls_layout" ? layout : null) as unknown as GetValue;
12
6
 
13
7
  describe("usesPlaylistControls", () => {
8
+ const configuredAs = (layout?: unknown): GetValue =>
9
+ ((key: string) =>
10
+ key === "player_controls_layout" ? layout : null) as unknown as GetValue;
11
+
14
12
  it("opts in only for the playlist controls value", () => {
15
13
  expect(usesPlaylistControls(configuredAs("playlist_controls"))).toBe(true);
16
14
  });
@@ -31,25 +29,6 @@ describe("usesPlaylistControls", () => {
31
29
  });
32
30
  });
33
31
 
34
- describe("controlsRowActionIdentifiers", () => {
35
- it("claims playback speed and sleep timer on the default layout", () => {
36
- expect(
37
- controlsRowActionIdentifiers(
38
- configuredAs("playback_speed_and_sleep_timer")
39
- )
40
- ).toEqual([
41
- PLAYER_ACTION_IDENTIFIERS.PLAYBACK_SPEED,
42
- PLAYER_ACTION_IDENTIFIERS.SLEEP_TIMER,
43
- ]);
44
- });
45
-
46
- it("claims nothing when the row shows playlist controls", () => {
47
- expect(
48
- controlsRowActionIdentifiers(configuredAs("playlist_controls"))
49
- ).toEqual([]);
50
- });
51
- });
52
-
53
32
  describe("track control buttons", () => {
54
33
  it("builds a previous button using the previous_track icon", () => {
55
34
  const onPress = jest.fn();
@@ -2,7 +2,10 @@ 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 { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+ import {
6
+ useIsTablet,
7
+ useZStore,
8
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
6
9
  import {
7
10
  Controls as MobileTransportControls,
8
11
  ProgressBar,
@@ -97,8 +100,11 @@ export const Controls = (props: Props) => {
97
100
  maxWidth = 320,
98
101
  } = props;
99
102
 
103
+ const configuration = useZStore("playerConfiguration");
104
+
100
105
  const { sleepTimerState, onSleepButtonPress } = useSleepTimerModal(
101
- playerManager.getActivePlayer()
106
+ playerManager.getActivePlayer(),
107
+ configuration
102
108
  );
103
109
 
104
110
  const { hasNext, hasPrevious, goNext, goPrevious } = usePlaylistNavigation(
@@ -1,5 +1,3 @@
1
- import { PLAYER_ACTION_IDENTIFIERS } from "../../playerActions";
2
-
3
1
  const playbackButtonMargins = (
4
2
  isTablet: boolean,
5
3
  isLiveShowFullControls: boolean
@@ -111,18 +109,6 @@ export const sleepButton = (props: ButtonsProps): ControlsButton =>
111
109
  export const usesPlaylistControls = (value: GetValue): boolean =>
112
110
  value("player_controls_layout") === "playlist_controls";
113
111
 
114
- /**
115
- * Player actions this row renders as its own buttons. Consumers of the shared
116
- * player action list have to filter these out to avoid showing them twice.
117
- */
118
- export const controlsRowActionIdentifiers = (value: GetValue): string[] =>
119
- usesPlaylistControls(value)
120
- ? []
121
- : [
122
- PLAYER_ACTION_IDENTIFIERS.PLAYBACK_SPEED,
123
- PLAYER_ACTION_IDENTIFIERS.SLEEP_TIMER,
124
- ];
125
-
126
112
  type TrackButtonProps = ButtonsProps & { disabled?: boolean };
127
113
 
128
114
  const trackButtonByName = ({
@@ -3,8 +3,7 @@ import { LayoutChangeEvent, StyleSheet, View } from "react-native";
3
3
 
4
4
  import { TransportInfo as MobileTransportInfo } from "@applicaster/quick-brick-mobile-transport-controls";
5
5
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
6
- import { useRegistryRevision } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
7
- import { useEntryActionsExpander } from "@applicaster/zapp-react-native-ui-components/Hooks/useEntryActionsExpander";
6
+ import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
8
7
 
9
8
  import { toPositiveNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
10
9
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
@@ -13,22 +12,7 @@ import { NAVBAR_HEIGHT, TABLET_LANDSCAPE } from "../const";
13
12
  import { withDebug } from "../debug";
14
13
  import { useSafeAreaFrame } from "react-native-safe-area-context";
15
14
  import { isSmallTabletHeight } from "../utils";
16
- import {
17
- ActionButtonDescriptor,
18
- resolvePlayerActions,
19
- } from "@applicaster/zapp-react-native-utils/playerUtils";
20
- import {
21
- RegisteredAction,
22
- uiActionsRegistry,
23
- } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
24
-
25
- import { PLAYER_ACTION_IDENTIFIERS } from "../../playerActions";
26
- import { controlsRowActionIdentifiers } from "../Controls/utils";
27
-
28
- /** Player-owned actions that join the shared action list of the overlay. */
29
- const AUDIO_PLAYER_ACTION_IDENTIFIERS: string[] = Object.values(
30
- PLAYER_ACTION_IDENTIFIERS
31
- );
15
+ import { getPlayerActionButtons } from "@applicaster/zapp-react-native-utils/playerUtils";
32
16
 
33
17
  const styles = StyleSheet.create({
34
18
  container: {
@@ -68,8 +52,7 @@ export const TransportInfo = ({
68
52
  }: Props) => {
69
53
  const isTablet = useIsTablet();
70
54
  const safeAreaFrame = useSafeAreaFrame();
71
- const registryRevision = useRegistryRevision();
72
- const expandEntryActions = useEntryActionsExpander();
55
+ const languageData = useLanguageData("audio-player");
73
56
 
74
57
  const [marginBottom, setMarginBottom] = React.useState(0);
75
58
 
@@ -98,56 +81,11 @@ export const TransportInfo = ({
98
81
  [onLayout]
99
82
  );
100
83
 
101
- // Joined so the memo below is not invalidated on every render.
102
- const controlsRowIdentifiersKey =
103
- controlsRowActionIdentifiers(value).join(",");
104
-
105
- const overlayActionIdentifiers = React.useMemo(() => {
106
- const renderedByControlsRow = controlsRowIdentifiersKey.split(",");
107
-
108
- return AUDIO_PLAYER_ACTION_IDENTIFIERS.filter(
109
- (identifier) => !renderedByControlsRow.includes(identifier)
110
- );
111
- }, [controlsRowIdentifiersKey]);
112
-
113
- const readOwnPlayerActions = React.useCallback(
114
- (): RegisteredAction[] =>
115
- overlayActionIdentifiers.flatMap((identifier) =>
116
- uiActionsRegistry.getActions(identifier, entry)
117
- ),
118
- [entry, overlayActionIdentifiers]
119
- );
120
-
121
- // Playback speed and the sleep timer register in a later effect than our
122
- // first render - speed waits for SpeedController state - so re-read whenever
123
- // the registry changes instead of guessing when they will be there.
124
- const playerActions = React.useMemo(
125
- () => readOwnPlayerActions(),
126
- // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
127
- [readOwnPlayerActions, registryRevision]
84
+ const actionButtons = React.useMemo(
85
+ () => getPlayerActionButtons(configuration),
86
+ [configuration]
128
87
  );
129
88
 
130
- const actionButtonDescriptors = React.useMemo<
131
- ActionButtonDescriptor[]
132
- >(() => {
133
- const content = resolvePlayerActions({
134
- configuration,
135
- entry,
136
- expandEntryActions,
137
- });
138
-
139
- const seen = new Set(content.map((action) => action.identifier));
140
-
141
- const extras = playerActions.filter(
142
- (action) => action?.identifier && !seen.has(action.identifier)
143
- );
144
-
145
- return [...content, ...extras].map((action) => ({
146
- kind: "action" as const,
147
- action,
148
- }));
149
- }, [configuration, entry, playerActions, expandEntryActions]);
150
-
151
89
  return (
152
90
  <View
153
91
  style={[
@@ -168,8 +106,9 @@ export const TransportInfo = ({
168
106
  content={entry}
169
107
  value={value}
170
108
  visible
109
+ languageData={languageData}
171
110
  layoutState={layoutState}
172
- actionButtonDescriptors={actionButtonDescriptors}
111
+ actionButtons={actionButtons}
173
112
  />
174
113
  </View>
175
114
  </View>
@@ -41,10 +41,6 @@ export class SleepTimerController {
41
41
  return this.sleepTimerStateSubject.asObservable();
42
42
  }
43
43
 
44
- get currentState(): SleepTimerState {
45
- return this.sleepTimerStateSubject.getValue();
46
- }
47
-
48
44
  private async scheduleSleep(sleepDate): Promise<boolean> {
49
45
  log_debug(`Setting sleep interval to ${sleepDate}`);
50
46
 
@@ -122,7 +118,7 @@ export class SleepTimerController {
122
118
 
123
119
  log_debug("Sleep state restored");
124
120
 
125
- await this.setSleepTimer(item as SleepTimer, futureSleepDate);
121
+ this.setSleepTimer(item as SleepTimer, futureSleepDate);
126
122
  }
127
123
 
128
124
  // Will be called if user closed the player explicitly.
@@ -33,10 +33,6 @@ export class SpeedController {
33
33
  return this.playbackSpeedSubject.asObservable();
34
34
  }
35
35
 
36
- get currentSpeed(): PlaybackSpeed {
37
- return this.playbackSpeedSubject.getValue();
38
- }
39
-
40
36
  async loadSpeed() {
41
37
  const speed = await localStorage.getItem(SPEED_PREFERENCE_KEY, NAMESPACE);
42
38
 
@@ -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 "@applicaster/zapp-react-native-utils/utils";
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";
@@ -1,19 +1,30 @@
1
1
  import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
2
- import { uiActionsRegistry } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
3
2
  import { useCallback, useEffect, useMemo, useState } from "react";
4
3
  import {
5
4
  SLEEP_TIMER_OFF,
6
5
  SleepTimerController,
7
6
  SleepTimerState,
8
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";
9
13
  import {
10
- PLAYER_ACTION_IDENTIFIERS,
11
- registerPlayerActions,
12
- } from "./playerActions";
14
+ getBottomSheetModalLabelProps,
15
+ getBottomSheetModalIconProps,
16
+ getBottomSheetModalItemProps,
17
+ getBottomSheetModalSelectedItemIcon,
18
+ selectSleepTitle,
19
+ selectSleepOffLabel,
20
+ } from "../Utils";
13
21
 
14
- export const useSleepTimerModal = (player) => {
22
+ export const useSleepTimerModal = (player, configuration) => {
15
23
  const playerState = usePlayerState("audio-player-sleep-timer-modal");
16
24
 
25
+ const title = configuration(selectSleepTitle);
26
+ const offLabel = configuration(selectSleepOffLabel);
27
+
17
28
  const [sleepTimerManager, setSleepTimerManager] =
18
29
  useState<SleepTimerController | null>(null);
19
30
 
@@ -22,22 +33,19 @@ export const useSleepTimerModal = (player) => {
22
33
  );
23
34
 
24
35
  useEffect(() => {
25
- setSleepTimerManager(player ? new SleepTimerController(player) : null);
36
+ if (player) {
37
+ setSleepTimerManager(new SleepTimerController(player));
38
+ } else {
39
+ setSleepTimerManager(null);
40
+ }
26
41
  }, [player]);
27
42
 
28
43
  useEffect(() => {
29
44
  if (playerState?.isLive) {
30
- sleepTimerManager?.setSleepTimer(SLEEP_TIMER_OFF).catch(() => undefined);
31
- }
32
- }, [playerState?.isLive, sleepTimerManager]);
33
-
34
- useEffect(() => {
35
- if (!sleepTimerManager || playerState.isLive) {
36
- return;
45
+ // Resetting a sleep timer when audio content is live (Timer not supported on live video)
46
+ sleepTimerManager.setSleepTimer(SLEEP_TIMER_OFF).catch();
37
47
  }
38
-
39
- return registerPlayerActions({ sleepController: sleepTimerManager });
40
- }, [sleepTimerManager, playerState.isLive]);
48
+ }, [playerState?.isLive]);
41
49
 
42
50
  useEffect(() => {
43
51
  if (!playerState.isReadyToPlay) {
@@ -56,11 +64,40 @@ export const useSleepTimerModal = (player) => {
56
64
  };
57
65
  }, [sleepTimerManager]);
58
66
 
67
+ const onSleepSelect = useCallback(
68
+ async (item) => {
69
+ await sleepTimerManager?.setSleepTimer(item);
70
+ },
71
+ [sleepTimerManager]
72
+ );
73
+
59
74
  const onSleepButtonPress = useCallback(() => {
60
- uiActionsRegistry
61
- .getAction(PLAYER_ACTION_IDENTIFIERS.SLEEP_TIMER)
62
- ?.invokeAction(player?.entry);
63
- }, [player]);
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]);
64
101
 
65
102
  return useMemo(
66
103
  () => ({