@applicaster/quick-brick-player 13.0.0-rc.6 → 13.0.0-rc.61

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-rc.6",
3
+ "version": "13.0.0-rc.61",
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.6",
40
- "@applicaster/quick-brick-tv-transport-controls": "12.0.0-rc.112",
41
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.6",
42
- "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.6",
43
- "@applicaster/zapp-react-native-utils": "13.0.0-rc.6",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.55",
40
+ "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
41
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.61",
42
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.61",
43
+ "@applicaster/zapp-react-native-utils": "13.0.0-rc.61",
44
44
  "keymirror": "^0.1.1",
45
45
  "query-string": "7.1.3",
46
46
  "react-native-web-linear-gradient": "1.1.2",
@@ -51,12 +51,28 @@
51
51
  "videojs-contrib-quality-levels": "3.0.0"
52
52
  },
53
53
  "peerDependencies": {
54
+ "@applicaster/applicaster-types": "*",
55
+ "@applicaster/blur": "*",
54
56
  "@applicaster/quick-brick-xray": "*",
55
57
  "@applicaster/react-native-color-thief": "*",
56
- "@types/node": "*",
57
- "@types/react-native": "0.69.6",
58
- "react": "17.0.2",
59
- "react-native": "0.68.6",
60
- "react-native-linear-gradient": "*"
58
+ "@applicaster/zapp-pipes-v2-client": "*",
59
+ "@applicaster/zapp-react-dom-ui-components": "*",
60
+ "@applicaster/zapp-react-native-redux": "*",
61
+ "@applicaster/zapp-react-native-tvos-ui-components": "*",
62
+ "@babel/runtime": "*",
63
+ "@react-native-community/netinfo": "*",
64
+ "immer": "*",
65
+ "react": "*",
66
+ "react-native": "*",
67
+ "react-native-fs": "*",
68
+ "react-native-gesture-handler": "*",
69
+ "react-native-linear-gradient": "*",
70
+ "react-native-safe-area-context": "*",
71
+ "react-native-svg": "*",
72
+ "react-native-web": "*",
73
+ "react-native-webview": "*",
74
+ "uglify-js": "*",
75
+ "validate-color": "*",
76
+ "zustand": "*"
61
77
  }
62
78
  }
@@ -55,6 +55,11 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
55
55
  return;
56
56
  }
57
57
 
58
+ // initial state setup - show correct mute/unmute icon based on current state
59
+ if (player?.playerState?.isReadyToPlay) {
60
+ setImageUri(player.getIsMuted() ? asset.active : asset.inactive);
61
+ }
62
+
58
63
  // active action = muted, inactive = unmuted
59
64
  return player.addListener({
60
65
  id: liveImageButtonsListenerKey,
@@ -73,10 +78,17 @@ const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
73
78
  setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
74
79
  },
75
80
  onPlayerClose: () => {
76
- setImageUri(null);
81
+ if (isCurrentPlayer(playerId)) {
82
+ setImageUri(null);
83
+ }
77
84
  },
78
85
  onVideoEnd: () => {
79
- setImageUri(null);
86
+ if (isCurrentPlayer(playerId)) {
87
+ setImageUri(null);
88
+ }
89
+ },
90
+ onPlayerResume: () => {
91
+ setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
80
92
  },
81
93
  },
82
94
  });
@@ -93,7 +105,7 @@ const styles = StyleSheet.create({
93
105
  },
94
106
  });
95
107
 
96
- export function AssetComponent(props: CellActionAssetComponentProps) {
108
+ function AssetImage(props: CellActionAssetComponentProps) {
97
109
  const { cellUUID: playerId, asset } = props;
98
110
  const [imageUri, setImageUri] = useState<Asset>(null);
99
111
 
@@ -108,3 +120,5 @@ export function AssetComponent(props: CellActionAssetComponentProps) {
108
120
 
109
121
  return <View />;
110
122
  }
123
+
124
+ export const AssetComponent = React.memo(AssetImage);
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { ViewStyle, View } from "react-native";
2
+ import { ViewStyle, View, Dimensions as ScreenDimensions } from "react-native";
3
3
 
4
4
  import { SafeAreaView, Edge } from "react-native-safe-area-context";
5
5
  import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
@@ -17,13 +17,7 @@ import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Comp
17
17
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
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,20 @@ 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";
43
+ import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
36
44
 
37
45
  const { log_debug } = createLogger({
38
46
  subsystem: "UIComponent",
@@ -71,8 +79,7 @@ type Props = {
71
79
  isTabletPortrait?: boolean;
72
80
  };
73
81
 
74
- export const defaultAspectRatioWidth = (height: number): number =>
75
- (height / 9) * 16;
82
+ const { width: SCREEN_WIDTH } = ScreenDimensions.get("screen");
76
83
 
77
84
  const Styles = {
78
85
  mainContainer: (style: ViewStyle) => {
@@ -98,7 +105,11 @@ const Styles = {
98
105
  height: 96,
99
106
  marginHorizontal: 24,
100
107
  },
101
- playerDetails: { marginTop: 60 },
108
+ playerDetails: {
109
+ marginTop: 60,
110
+ alignSelf: "center",
111
+ width: SCREEN_WIDTH - PADDING_HORIZONTAL * 2,
112
+ },
102
113
 
103
114
  linearGradient: {
104
115
  flex: 1,
@@ -132,11 +143,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
132
143
  } = props;
133
144
 
134
145
  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 },
146
+ { asset: configuration["speed_0_8"] || speed_0_8, value: 0.8 },
147
+ { asset: configuration["speed_1"] || speed_1, value: 1.0 },
148
+ { asset: configuration["speed_1_2"] || speed_1_2, value: 1.2 },
149
+ { asset: configuration["speed_1_5"] || speed_1_5, value: 1.5 },
150
+ { asset: configuration["speed_2"] || speed_2, value: 2.0 },
140
151
  ];
141
152
 
142
153
  const isTablet = useIsTablet();
@@ -154,17 +165,29 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
154
165
  configuration?.audio_live_player_update_interval
155
166
  );
156
167
 
157
- const [playbackSpeed, setPlaybackSpeed] = useState({
158
- asset: configuration["speed_1"],
159
- value: 1.0,
160
- });
168
+ const [sleepTimerManager, setSleepTimerManager] =
169
+ useState<SleepTimerController | null>(null);
161
170
 
162
- const [sleepTimer, setSleepTimer] = useState({
163
- label: "playback_sleep_off_label",
164
- value: 0,
165
- });
171
+ const [speedManager, setSpeedManager] = useState<SpeedController | null>(
172
+ null
173
+ );
174
+
175
+ useEffect(() => {
176
+ if (player) {
177
+ setSleepTimerManager(new SleepTimerController(player));
178
+ setSpeedManager(new SpeedController(player));
179
+ } else {
180
+ setSleepTimerManager(null);
181
+ setSpeedManager(null);
182
+ }
183
+ }, [player]);
184
+
185
+ const [playbackSpeed, setPlaybackSpeed] =
186
+ useState<PlaybackSpeed>(DEFAULT_SPEED);
166
187
 
167
- const [sleepTimerDate, setSleepTimerDate] = useState(0);
188
+ const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
189
+ new SleepTimerState(SLEEP_TIMER_OFF, 0)
190
+ );
168
191
 
169
192
  const [defaultDimensions, setDefaultDimensions] =
170
193
  React.useState<Dimensions>(undefined);
@@ -189,82 +212,27 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
212
  if (!defaultDimensions) setDefaultDimensions({ width, height });
190
213
  };
191
214
 
192
- const cancelSleepTimer = 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
- };
204
-
205
- const startSleepTimer = (timerValue: number) => {
206
- try {
207
- const currentDate = new Date();
208
- const futureDate = new Date(currentDate.getTime() + timerValue * 60000);
209
- const futureSleepDate = futureDate.getTime();
210
-
211
- player?.cancelSleepTimer();
212
-
213
- player?.startSleepTimer(futureSleepDate);
214
-
215
- return futureSleepDate;
216
- } catch (e) {
217
- return 0;
218
- }
219
- };
220
-
221
- const handleSleep = async (
222
- timerValue: number,
223
- currentFutureSleepDate = null
224
- ) => {
225
- if (currentFutureSleepDate) {
226
- if (hasDatePassed(currentFutureSleepDate)) {
227
- await cancelSleepTimer();
228
-
229
- return 0;
230
- }
231
-
232
- return currentFutureSleepDate;
233
- }
234
-
235
- if (timerValue === 0) {
236
- await cancelSleepTimer();
237
-
238
- return timerValue;
239
- }
240
-
241
- return startSleepTimer(timerValue);
242
- };
243
-
244
215
  useEffect(() => {
245
- const loadSpeed = async () => {
246
- const speed = await loadItemsFromStorage("speed");
247
-
248
- if (playerState.isReadyToPlay && speed) {
249
- setPlaybackSpeed(speed);
250
- player?.setPlaybackRate(speed.value);
251
- }
252
- };
216
+ const sleepSubscription =
217
+ sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
253
218
 
254
- const loadSleep = async () => {
255
- const sleep = await loadItemsFromStorage("sleep");
219
+ const speedSubscription =
220
+ speedManager?.playbackSpeed$.subscribe(setPlaybackSpeed);
256
221
 
257
- if (playerState.isReadyToPlay && sleep) {
258
- const { futureSleepDate, ...rest } = sleep;
259
- setSleepTimer(rest);
260
- const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
261
- setSleepTimerDate(sleepTimerDate);
262
- }
222
+ return () => {
223
+ sleepSubscription?.unsubscribe();
224
+ speedSubscription?.unsubscribe();
263
225
  };
226
+ }, [sleepTimerManager, speedManager]);
227
+
228
+ useEffect(() => {
229
+ if (!playerState.isReadyToPlay) {
230
+ return;
231
+ }
264
232
 
265
- loadSpeed().then();
266
- loadSleep().then();
267
- }, [playerState.isReadyToPlay]);
233
+ void speedManager?.loadSpeed();
234
+ void sleepTimerManager?.loadSleep();
235
+ }, [playerState.isReadyToPlay, speedManager, sleepTimerManager]);
268
236
 
269
237
  const getValue = (key: string, stylesObj: {}) =>
270
238
  R.propOr(null, key, stylesObj);
@@ -292,7 +260,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
292
260
 
293
261
  const onPlaybackButtonPress = React.useCallback(() => {
294
262
  if (playerState.isBuffering) return;
295
- playerState.isPaused ? player?.play() : player.pause();
263
+ playerState.isPaused ? player?.play() : player?.pause();
296
264
  }, [playerState, player]);
297
265
 
298
266
  const seekDuration = toNumberWithDefault(10, value("seek_duration"));
@@ -307,26 +275,16 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
307
275
 
308
276
  const onSpeedSelect = React.useCallback(
309
277
  async (item) => {
310
- setPlaybackSpeed(item);
311
- player.setPlaybackRate(item.value);
312
- await localStorage.setItem("speed", item, NAMESPACE);
278
+ await speedManager?.setPlaybackSpeed(item);
313
279
  },
314
- [player, setPlaybackSpeed]
280
+ [speedManager]
315
281
  );
316
282
 
317
283
  const onSleepSelect = React.useCallback(
318
284
  async (item) => {
319
- setSleepTimer(item);
320
- const futureSleepDate = await handleSleep(item.value);
321
- setSleepTimerDate(futureSleepDate);
322
-
323
- await localStorage.setItem(
324
- "sleep",
325
- { ...item, futureSleepDate },
326
- NAMESPACE
327
- );
285
+ await sleepTimerManager?.setSleepTimer(item);
328
286
  },
329
- [player, setSleepTimer]
287
+ [sleepTimerManager]
330
288
  );
331
289
 
332
290
  const onSpeedButtonPress = React.useCallback(() => {
@@ -341,15 +299,24 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
341
299
  supportedOrientations: ["portrait"],
342
300
  },
343
301
  });
344
- }, [playbackSpeed, setPlaybackSpeed]);
302
+ }, [
303
+ playbackSpeed,
304
+ setPlaybackSpeed,
305
+ navigator,
306
+ onSpeedSelect,
307
+ configuration["playback_speed_title"],
308
+ ]);
345
309
 
346
310
  const onSleepButtonPress = React.useCallback(() => {
347
311
  navigator.openModalBottomSheet({
348
312
  modalBottomSheetContentProps: {
349
313
  items: [
350
- { label: configuration["playback_sleep_off_label"], value: 0 },
314
+ {
315
+ label: configuration["playback_sleep_off_label"],
316
+ value: 0,
317
+ },
351
318
  ].concat(sleeps),
352
- current_selection: sleepTimer,
319
+ current_selection: sleepTimerState.timer,
353
320
  onPress: onSleepSelect,
354
321
  title: configuration["playback_sleep_title"],
355
322
  },
@@ -357,7 +324,21 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
357
324
  supportedOrientations: ["portrait"],
358
325
  },
359
326
  });
360
- }, [sleepTimer, setSleepTimer, configuration]);
327
+ }, [
328
+ navigator,
329
+ sleepTimerState.timer,
330
+ configuration["playback_sleep_title"],
331
+ configuration["playback_sleep_off_label"],
332
+ onSleepSelect,
333
+ ]);
334
+
335
+ // This method does not actually cancel the sleep timer
336
+ // (and should not to avoid race condition),
337
+ // it just performs the cleanup when timer expires
338
+ // TODO: rename it in code above
339
+ const cancelSleepTimer = React.useCallback(() => {
340
+ sleepTimerManager?.onTimerExpired();
341
+ }, [sleepTimerManager]);
361
342
 
362
343
  const onCloseVideoModal = React.useCallback(() => {
363
344
  playerManager.close();
@@ -406,9 +387,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
406
387
  onSleepButtonPress={onSleepButtonPress}
407
388
  playbackSpeedName={formatSpeed(playbackSpeed.value)}
408
389
  playbackSleepName={
409
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
390
+ sleepTimerState.timer.value === 0
391
+ ? "sleep_timer"
392
+ : "sleep_timer_active"
410
393
  }
411
- sleepTimerDate={sleepTimerDate}
394
+ sleepTimerDate={sleepTimerState.timerDate}
412
395
  cancelSleepTimer={cancelSleepTimer}
413
396
  onSkipLive={onSkipLive}
414
397
  onCloseVideoModal={onCloseVideoModal}
@@ -458,12 +441,12 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
458
441
  playNextData,
459
442
  onSkipStart,
460
443
  onSkipLive,
461
- sleepTimer.value,
462
- sleepTimerDate,
444
+ sleepTimerState.timer.value,
445
+ sleepTimerState.timerDate,
463
446
  ]);
464
447
 
465
448
  if (configuration.magic_background) {
466
- const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
449
+ const baseUrl = imageSrcFromMediaItem(entry, null);
467
450
 
468
451
  const queryParamName =
469
452
  configuration.audio_player_background_image_query || "width";
@@ -1,13 +1,14 @@
1
1
  import * as React from "react";
2
+ import { useEffect, useMemo, useState } from "react";
2
3
  import * as R from "ramda";
3
4
  import { View } from "react-native";
4
5
  import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
5
6
 
6
7
  import {
7
- ProgressBar,
8
8
  Controls,
9
- TransportInfo,
10
9
  CountDown,
10
+ ProgressBar,
11
+ TransportInfo,
11
12
  } from "@applicaster/quick-brick-mobile-transport-controls";
12
13
 
13
14
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
@@ -15,23 +16,22 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
15
16
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
16
17
  import {
17
18
  forwardButton,
19
+ PADDING_HORIZONTAL,
18
20
  playbackButton,
19
21
  rewindButton,
20
- skipToStartButton,
21
22
  skipToLiveButton,
22
- PADDING_HORIZONTAL,
23
- speedButton,
23
+ skipToStartButton,
24
24
  sleepButton,
25
+ speedButton,
25
26
  } from "./defaultLayoutsConts";
26
27
  import { AudioDescription } from "../AudioDescription";
27
28
  import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
28
29
  import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
29
30
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
30
- import { useEffect, useMemo, useState } from "react";
31
31
  import {
32
- useModalAnimationContext,
33
32
  AnimationComponent,
34
33
  ComponentAnimationType,
34
+ useModalAnimationContext,
35
35
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
36
36
  import DockedLayout from "./DockedLayout";
37
37
  import { isValidDate } from "../../Utils";
@@ -120,8 +120,8 @@ const Styles = {
120
120
  (startComponentsAnimation && isActiveGesture) || docked
121
121
  ? 0
122
122
  : isTablet
123
- ? 24
124
- : 10,
123
+ ? 24
124
+ : 10,
125
125
  }),
126
126
 
127
127
  paddingHorizontal: {
@@ -375,6 +375,7 @@ export default function DefaultLayout(props: LayoutProps) {
375
375
  >
376
376
  <PlayerImageBackground
377
377
  entry={entry}
378
+ docked={docked}
378
379
  style={Styles.backgroundImageContainer(dimensions, isTablet)}
379
380
  imageStyle={Styles.backgroundImageContainerImageStyle(
380
381
  isTablet,
@@ -405,9 +406,10 @@ export default function DefaultLayout(props: LayoutProps) {
405
406
  layoutState={layoutState}
406
407
  />
407
408
  <Separator />
408
- {needsToShowProgressBar && (
409
+ {needsToShowProgressBar ? (
409
410
  <View>
410
411
  <ProgressBar
412
+ name={"audio-default-layout"}
411
413
  content={entry}
412
414
  value={value}
413
415
  visible={true}
@@ -422,17 +424,17 @@ export default function DefaultLayout(props: LayoutProps) {
422
424
  onSkipLive={onSkipLive}
423
425
  />
424
426
 
425
- {showCounter && timeLeft >= 0 && (
427
+ {showCounter && timeLeft >= 0 ? (
426
428
  <CountDown
427
429
  timeLeft={timeLeft}
428
430
  value={value}
429
431
  title={sleepTitle}
430
432
  />
431
- )}
433
+ ) : null}
432
434
 
433
435
  <Separator />
434
436
  </View>
435
- )}
437
+ ) : null}
436
438
  <Controls
437
439
  value={value}
438
440
  visible={true}
@@ -9,18 +9,18 @@ import * as R from "ramda";
9
9
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
10
10
 
11
11
  import {
12
+ Button,
12
13
  ContentInfo,
13
- ProgressBar,
14
14
  Controls,
15
- Button,
15
+ ProgressBar,
16
16
  } from "@applicaster/quick-brick-mobile-transport-controls";
17
17
 
18
18
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
19
19
  import {
20
- useModalAnimationContext,
21
20
  AnimationComponent,
22
21
  ComponentAnimationType,
23
22
  PlayerAnimationStateEnum,
23
+ useModalAnimationContext,
24
24
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
25
25
 
26
26
  type Dimensions = {
@@ -245,18 +245,19 @@ export default function DockedLayout(props: LayoutProps) {
245
245
  ]}
246
246
  >
247
247
  {!playerState.isLive &&
248
- (layoutState.docked || startComponentsAnimation) && (
249
- <ProgressBar
250
- content={entry}
251
- value={value}
252
- visible={visible}
253
- layoutState={layoutState}
254
- onPlayerSeek={onPlayerSeek}
255
- playerState={playerState}
256
- startComponentsAnimation={startComponentsAnimation}
257
- docked
258
- />
259
- )}
248
+ (layoutState.docked || startComponentsAnimation) ? (
249
+ <ProgressBar
250
+ name={"audio-docked-layout"}
251
+ content={entry}
252
+ value={value}
253
+ visible={visible}
254
+ layoutState={layoutState}
255
+ onPlayerSeek={onPlayerSeek}
256
+ playerState={playerState}
257
+ startComponentsAnimation={startComponentsAnimation}
258
+ docked
259
+ />
260
+ ) : null}
260
261
 
261
262
  <View style={styles.content}>
262
263
  <View
@@ -37,8 +37,8 @@ export const playbackButton = ({
37
37
  const icon = playerState.isPaused
38
38
  ? "play"
39
39
  : playerState.isLive && liveButton
40
- ? "stop"
41
- : "pause";
40
+ ? "stop"
41
+ : "pause";
42
42
 
43
43
  return {
44
44
  size: playbackButtonSize(isTablet),
@@ -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
+ }
@@ -253,12 +253,7 @@ export default class VideoPlayer extends React.Component<
253
253
  };
254
254
 
255
255
  stopBackgroundPlayback = () => {
256
- if (
257
- this.isReactNativeMethodsSupported() &&
258
- RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback
259
- ) {
260
- RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback();
261
- }
256
+ RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback?.();
262
257
  };
263
258
 
264
259
  forward = (seekTime = SEEK_TIME) => {
@@ -811,7 +806,6 @@ export default class VideoPlayer extends React.Component<
811
806
  nowPlayingEnabled,
812
807
  paused,
813
808
  seek,
814
- rate,
815
809
  textTrackId,
816
810
  liveSeekingEnabled,
817
811
  liveCatchUpEnabled,
@@ -822,7 +816,14 @@ export default class VideoPlayer extends React.Component<
822
816
  accessibilityProps: getAllAccessibilityProps(
823
817
  this.getPluginConfiguration()
824
818
  ),
825
- }; // rate temporary removed, because of bug with subtitles
819
+ };
820
+
821
+ if (!isAppleTV()) {
822
+ // TODO: Move to RN method and remove from props.
823
+ // Causes tvOS bug: native player passes rate on fast seek,
824
+ // RN returns it and it breaks playback.
825
+ nativeProps.rate = rate;
826
+ }
826
827
 
827
828
  if (
828
829
  this.isReactNativeMethodsSupported() &&
@@ -923,8 +924,8 @@ export default class VideoPlayer extends React.Component<
923
924
  const playerContainerStyles = isApplePlatform()
924
925
  ? styles.playerContainerBlack
925
926
  : isTV() && isAudioItem
926
- ? styles.playerContainerTransparent
927
- : styles.playerContainerBlack;
927
+ ? styles.playerContainerTransparent
928
+ : styles.playerContainerBlack;
928
929
 
929
930
  const isInline = () => {
930
931
  if (isTV()) {
@@ -995,14 +996,14 @@ export default class VideoPlayer extends React.Component<
995
996
  const renderMobilePlayer = () => {
996
997
  return isFullScreenAudioPlayer && isAudioItem ? (
997
998
  <View style={containerStyle}>
998
- {(!castAction?.state?.connected || supportNativeCast) && (
999
+ {!castAction?.state?.connected || supportNativeCast ? (
999
1000
  <this.props.PlayerComponent
1000
1001
  ref={this._assignRoot}
1001
1002
  {...nativeEvents}
1002
1003
  {...nativeProps}
1003
1004
  listener={this.props.listener}
1004
1005
  />
1005
- )}
1006
+ ) : null}
1006
1007
  <AudioPlayerWrapper {...sharedWrapperViewProps} />
1007
1008
  </View>
1008
1009
  ) : (
@@ -1017,7 +1018,7 @@ export default class VideoPlayer extends React.Component<
1017
1018
  return (
1018
1019
  <View style={styles.container}>
1019
1020
  <View style={[playerContainerStyles, playerDimensions]}>
1020
- {isAudioItem && (
1021
+ {isAudioItem ? (
1021
1022
  <AnimationComponent
1022
1023
  animationType={ComponentAnimationType.player}
1023
1024
  additionalData={{
@@ -1036,18 +1037,18 @@ export default class VideoPlayer extends React.Component<
1036
1037
  plugin_configuration={this.getPluginConfiguration()}
1037
1038
  />
1038
1039
  </AnimationComponent>
1039
- )}
1040
+ ) : null}
1040
1041
 
1041
- {shouldRenderBackground && (
1042
+ {shouldRenderBackground ? (
1042
1043
  <PlayerImageBackground
1043
1044
  imageKey={backgroundImageKey}
1044
1045
  configuration={this.getPluginConfiguration()}
1045
1046
  entry={entry}
1046
1047
  style={getBackgroundImageStyle(playerDimensions)}
1047
1048
  />
1048
- )}
1049
+ ) : null}
1049
1050
 
1050
- {(!castAction?.state?.connected || supportNativeCast) && (
1051
+ {!castAction?.state?.connected || supportNativeCast ? (
1051
1052
  <AnimationComponent
1052
1053
  animationType={ComponentAnimationType.player}
1053
1054
  additionalData={playerDimensions}
@@ -1069,7 +1070,7 @@ export default class VideoPlayer extends React.Component<
1069
1070
  {this.childrenWithPlayNext()}
1070
1071
  </this.props.PlayerComponent>
1071
1072
  </AnimationComponent>
1072
- )}
1073
+ ) : null}
1073
1074
  </View>
1074
1075
 
1075
1076
  {renderTransportControls(playerDimensions)}