@applicaster/quick-brick-player 13.0.0-alpha.3013227535 → 13.0.0-alpha.3098550192

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": "13.0.0-alpha.3013227535",
3
+ "version": "13.0.0-alpha.3098550192",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -36,11 +36,11 @@
36
36
  },
37
37
  "homepage": "https://github.com/applicaster/quickbrick#readme",
38
38
  "dependencies": {
39
- "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.25",
40
- "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.26",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.3013227535",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.3013227535",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.3013227535",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.28",
40
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.31",
41
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-alpha.3098550192",
42
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-alpha.3098550192",
43
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.3098550192",
44
44
  "keymirror": "^0.1.1",
45
45
  "query-string": "7.1.3",
46
46
  "react-native-web-linear-gradient": "1.1.2",
@@ -15,15 +15,9 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
15
15
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
16
16
  import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/PlayerDetails";
17
17
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
- import { useCallback, useEffect, useState } from "react";
18
+ import { useEffect, useState } from "react";
19
19
  import { MagicBG } from "./MagicBG";
20
- import {
21
- formatSpeed,
22
- hasDatePassed,
23
- loadItemsFromStorage,
24
- NAMESPACE,
25
- } from "./utils";
26
- import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
20
+ import { formatSpeed } from "./utils";
27
21
  import { addQueryParam, getLiveUrl } from "../Utils";
28
22
  import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
29
23
  import {
@@ -33,6 +27,19 @@ import {
33
27
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
34
28
  import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
35
29
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
30
+ import {
31
+ speed_0_8,
32
+ speed_1,
33
+ speed_1_2,
34
+ speed_1_5,
35
+ speed_2,
36
+ } from "@applicaster/quick-brick-mobile-transport-controls";
37
+ import {
38
+ SleepTimerController,
39
+ SleepTimerState,
40
+ SLEEP_TIMER_OFF,
41
+ } from "./SleepTimerManager";
42
+ import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
36
43
 
37
44
  const { log_debug } = createLogger({
38
45
  subsystem: "UIComponent",
@@ -132,11 +139,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
132
139
  } = props;
133
140
 
134
141
  const speeds = [
135
- { asset: configuration["speed_0_8"], value: 0.8 },
136
- { asset: configuration["speed_1"], value: 1.0 },
137
- { asset: configuration["speed_1_2"], value: 1.2 },
138
- { asset: configuration["speed_1_5"], value: 1.5 },
139
- { asset: configuration["speed_2"], value: 2.0 },
142
+ { asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
143
+ { asset: configuration["speed_1"] || speed_1, value: 1.0 },
144
+ { asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
145
+ { asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
146
+ { asset: configuration["speed_2"] || speed_2, value: 2.0 },
140
147
  ];
141
148
 
142
149
  const isTablet = useIsTablet();
@@ -154,17 +161,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
154
161
  configuration?.audio_live_player_update_interval
155
162
  );
156
163
 
157
- const [playbackSpeed, setPlaybackSpeed] = useState({
158
- asset: configuration["speed_1"],
159
- value: 1.0,
160
- });
164
+ const [sleepTimerManager, setSleepTimerManager] =
165
+ useState<SleepTimerController | null>(null);
161
166
 
162
- const [sleepTimer, setSleepTimer] = useState({
163
- label: "playback_sleep_off_label",
164
- value: 0,
165
- });
167
+ const [speedManager, setSpeedManager] = useState<SpeedController | null>(
168
+ null
169
+ );
166
170
 
167
- const [sleepTimerDate, setSleepTimerDate] = useState(0);
171
+ useEffect(() => {
172
+ if (player) {
173
+ setSleepTimerManager(new SleepTimerController(player));
174
+ setSpeedManager(new SpeedController(player));
175
+ } else {
176
+ setSleepTimerManager(null);
177
+ setSpeedManager(null);
178
+ }
179
+ }, [player]);
180
+
181
+ const [playbackSpeed, setPlaybackSpeed] =
182
+ useState<PlaybackSpeed>(DEFAULT_SPEED);
183
+
184
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
185
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
186
+ );
168
187
 
169
188
  const [defaultDimensions, setDefaultDimensions] =
170
189
  React.useState<Dimensions>(undefined);
@@ -189,90 +208,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
208
  if (!defaultDimensions) setDefaultDimensions({ width, height });
190
209
  };
191
210
 
192
- const cancelSleepTimer = useCallback(async () => {
193
- player?.cancelSleepTimer();
194
-
195
- setSleepTimer({
196
- label: "playback_sleep_off_label",
197
- value: 0,
198
- });
199
-
200
- setSleepTimerDate(0);
201
-
202
- await localStorage.removeItem("sleep", NAMESPACE);
203
- }, [player, setSleepTimer, setSleepTimerDate]);
204
-
205
- const startSleepTimer = useCallback(
206
- (timerValue: number) => {
207
- try {
208
- const futureSleepDate = Date.now() + timerValue * 60000;
209
-
210
- player?.cancelSleepTimer?.();
211
-
212
- player?.startSleepTimer?.(futureSleepDate);
213
-
214
- return futureSleepDate;
215
- } catch (e) {
216
- return 0;
217
- }
218
- },
219
- [player]
220
- );
221
-
222
- const handleSleep = useCallback(
223
- async (timerValue: number, currentFutureSleepDate = null) => {
224
- if (currentFutureSleepDate) {
225
- if (hasDatePassed(currentFutureSleepDate)) {
226
- await cancelSleepTimer();
227
-
228
- return 0;
229
- }
230
-
231
- return currentFutureSleepDate;
232
- }
233
-
234
- if (timerValue === 0) {
235
- await cancelSleepTimer();
236
-
237
- return timerValue;
238
- }
239
-
240
- return startSleepTimer(timerValue);
241
- },
242
- [startSleepTimer, cancelSleepTimer]
243
- );
244
-
245
211
  useEffect(() => {
246
- const loadSpeed = async () => {
247
- const speed = await loadItemsFromStorage("speed");
212
+ const sleepSubscription =
213
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
248
214
 
249
- if (playerState.isReadyToPlay && speed) {
250
- setPlaybackSpeed(speed);
251
- player?.setPlaybackRate(speed.value);
252
- }
253
- };
254
-
255
- const loadSleep = async () => {
256
- const sleep = await loadItemsFromStorage("sleep");
215
+ const speedSubscription =
216
+ speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
257
217
 
258
- if (playerState.isReadyToPlay && sleep) {
259
- const { futureSleepDate, ...rest } = sleep;
260
- setSleepTimer(rest);
261
- const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
262
- setSleepTimerDate(sleepTimerDate);
263
- }
218
+ return () => {
219
+ sleepSubscription?.unsubscribe();
220
+ speedSubscription?.unsubscribe();
264
221
  };
222
+ }, [sleepTimerManager, speedManager]);
265
223
 
266
- void loadSpeed();
267
- void loadSleep();
268
- }, [
269
- playerState.isReadyToPlay,
270
- handleSleep,
271
- setSleepTimer,
272
- setSleepTimerDate,
273
- setPlaybackSpeed,
274
- player,
275
- ]);
224
+ useEffect(() => {
225
+ if (!playerState.isReadyToPlay) {
226
+ return;
227
+ }
228
+
229
+ void speedManager?.loadSpeed();
230
+ void sleepTimerManager?.loadSleep();
231
+ }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
276
232
 
277
233
  const getValue = (key: string, stylesObj: {}) =>
278
234
  R.propOr(null, key, stylesObj);
@@ -315,26 +271,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
315
271
 
316
272
  const onSpeedSelect = React.useCallback(
317
273
  async (item) => {
318
- setPlaybackSpeed(item);
319
- player?.setPlaybackRate(item.value);
320
- await localStorage.setItem("speed", item, NAMESPACE);
274
+ await speedManager?.setPlaybackSpeed(item);
321
275
  },
322
- [player, setPlaybackSpeed]
276
+ [speedManager]
323
277
  );
324
278
 
325
279
  const onSleepSelect = React.useCallback(
326
280
  async (item) => {
327
- setSleepTimer(item);
328
- const futureSleepDate = await handleSleep(item.value);
329
- setSleepTimerDate(futureSleepDate);
330
-
331
- await localStorage.setItem(
332
- "sleep",
333
- { ...item, futureSleepDate },
334
- NAMESPACE
335
- );
281
+ await sleepTimerManager?.setSleepTimer(item);
336
282
  },
337
- [setSleepTimer, setSleepTimerDate, handleSleep]
283
+ [sleepTimerManager]
338
284
  );
339
285
 
340
286
  const onSpeedButtonPress = React.useCallback(() => {
@@ -361,9 +307,12 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
361
307
  navigator.openModalBottomSheet({
362
308
  modalBottomSheetContentProps: {
363
309
  items: [
364
- { label: configuration["playback_sleep_off_label"], value: 0 },
310
+ {
311
+ label: configuration["playback_sleep_off_label"],
312
+ value: 0,
313
+ },
365
314
  ].concat(sleeps),
366
- current_selection: sleepTimer,
315
+ current_selection: sleepTimerState.timer,
367
316
  onPress: onSleepSelect,
368
317
  title: configuration["playback_sleep_title"],
369
318
  },
@@ -373,12 +322,20 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
373
322
  });
374
323
  }, [
375
324
  navigator,
376
- sleepTimer,
325
+ sleepTimerState.timer,
377
326
  configuration["playback_sleep_title"],
378
327
  configuration["playback_sleep_off_label"],
379
328
  onSleepSelect,
380
329
  ]);
381
330
 
331
+ // This method does not actually cancel the sleep timer
332
+ // (and should not to avoid race condition),
333
+ // it just performs the cleanup when timer expires
334
+ // TODO: rename it in code above
335
+ const cancelSleepTimer = React.useCallback(() => {
336
+ sleepTimerManager?.onTimerExpired();
337
+ }, [sleepTimerManager]);
338
+
382
339
  const onCloseVideoModal = React.useCallback(() => {
383
340
  playerManager.close();
384
341
  }, [playerManager]);
@@ -426,9 +383,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
426
383
  onSleepButtonPress={onSleepButtonPress}
427
384
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
428
385
  playbackSleepName={
429
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
386
+ sleepTimerState.timer.value === 0
387
+ ? "sleep_timer"
388
+ : "sleep_timer_active"
430
389
  }
431
- sleepTimerDate={sleepTimerDate}
390
+ sleepTimerDate={sleepTimerState.timerDate}
432
391
  cancelSleepTimer={cancelSleepTimer}
433
392
  onSkipLive={onSkipLive}
434
393
  onCloseVideoModal={onCloseVideoModal}
@@ -478,8 +437,8 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
478
437
  playNextData,
479
438
  onSkipStart,
480
439
  onSkipLive,
481
- sleepTimer.value,
482
- sleepTimerDate,
440
+ sleepTimerState.timer.value,
441
+ sleepTimerState.timerDate,
483
442
  ]);
484
443
 
485
444
  if (configuration.magic_background) {
@@ -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
+ }