@applicaster/quick-brick-player 12.0.0-rc.7 → 12.0.0-rc.71

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": "12.0.0-rc.7",
3
+ "version": "12.0.0-rc.71",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -36,12 +36,13 @@
36
36
  },
37
37
  "homepage": "https://github.com/applicaster/quickbrick#readme",
38
38
  "dependencies": {
39
- "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.7",
40
- "@applicaster/quick-brick-tv-transport-controls": "11.0.0-rc.133",
41
- "@applicaster/zapp-react-native-tvos-app": "12.0.0-rc.7",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-rc.7",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-rc.7",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.68",
40
+ "@applicaster/quick-brick-tv-transport-controls": "12.0.0-rc.70",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-rc.71",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-rc.71",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-rc.71",
44
44
  "keymirror": "^0.1.1",
45
+ "query-string": "7.1.3",
45
46
  "react-native-web-linear-gradient": "1.1.2",
46
47
  "shaka-player": "4.3.5",
47
48
  "typeface-montserrat": "^0.0.54",
@@ -1,8 +1,9 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import { Image, StyleSheet, View } from "react-native";
2
+ import { StyleSheet, View } from "react-native";
3
3
  import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
4
4
  import { MUTED_USAGE } from "../const";
5
5
  import { LiveImageManager } from "@applicaster/zapp-react-native-ui-components/Components/VideoLive/LiveImageManager";
6
+ import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
6
7
 
7
8
  type Asset = string | undefined;
8
9
 
@@ -3,7 +3,8 @@ import { Text, TextStyle, View } from "react-native";
3
3
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
4
4
 
5
5
  type AudioDescriptionProps = {
6
- content: ZappEntry;
6
+ title: string | number;
7
+ summary: string | number;
7
8
  layoutState: {
8
9
  inline: boolean;
9
10
  docked: boolean;
@@ -78,8 +79,7 @@ const summaryStyles = (value: GetValue, prefix: string): TextStyle => ({
78
79
  });
79
80
 
80
81
  export const AudioDescription = (props: AudioDescriptionProps) => {
81
- const { content, layoutState, value } = props;
82
- const { title, summary } = content;
82
+ const { title, summary, layoutState, value } = props;
83
83
  const prefix = layoutState.docked ? "docked_player_" : "";
84
84
 
85
85
  return (
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { ViewStyle, View, ScrollView } from "react-native";
2
+ import { ViewStyle, View } 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";
@@ -15,7 +15,24 @@ 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 { useEffect, useState } from "react";
18
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";
27
+ import { addQueryParam, getLiveUrl } from "../Utils";
28
+ import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
29
+ import {
30
+ AnimationComponent,
31
+ ComponentAnimationType,
32
+ AnimatedScrollModal,
33
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
34
+ import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
35
+ import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
19
36
 
20
37
  const { log_debug } = createLogger({
21
38
  subsystem: "UIComponent",
@@ -32,6 +49,7 @@ type Dimensions = {
32
49
  type Configuration = {
33
50
  [key: string]: any;
34
51
  magic_background?: boolean;
52
+ audio_player_background_image_query?: string;
35
53
  tablet_landscape_sidebar_width?: string;
36
54
  tablet_landscape_player_container_background_color?: string;
37
55
  };
@@ -57,29 +75,20 @@ export const defaultAspectRatioWidth = (height: number): number =>
57
75
  (height / 9) * 16;
58
76
 
59
77
  const Styles = {
60
- mainContainer: (docked: boolean, style: ViewStyle) => {
78
+ mainContainer: (style: ViewStyle) => {
61
79
  const combinedStyle: ViewStyle = {
62
80
  flex: 1,
63
81
  };
64
82
 
65
- if (!docked) {
66
- style.paddingTop = 0;
67
- style.paddingHorizontal = 15;
68
- }
69
-
70
- return { ...combinedStyle, ...style };
71
- },
72
- playerContainer: (docked: boolean, style: ViewStyle) => {
73
- const combinedStyle: ViewStyle = {
74
- flex: 1,
83
+ return {
84
+ ...combinedStyle,
85
+ ...style,
75
86
  };
76
-
77
- if (docked) {
78
- combinedStyle.height = style.height;
79
- }
80
-
81
- return combinedStyle;
82
87
  },
88
+ playerContainer: {
89
+ flex: 1,
90
+ },
91
+ paddingHorizontal: { paddingTop: 0, paddingHorizontal: PADDING_HORIZONTAL },
83
92
 
84
93
  controlsMobile: {
85
94
  height: 50,
@@ -96,6 +105,17 @@ const Styles = {
96
105
  },
97
106
  };
98
107
 
108
+ const sleeps = [
109
+ // { label: "End of episode", value: "episode" },
110
+ { label: "5 minutes", value: 5 },
111
+ { label: "10 minutes", value: 10 },
112
+ { label: "15 minutes", value: 15 },
113
+ { label: "30 minutes", value: 30 },
114
+ { label: "45 minutes", value: 45 },
115
+ { label: "1 hour", value: 60 },
116
+ { label: "2 hours", value: 120 },
117
+ ];
118
+
99
119
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
100
120
  const player = usePlayer();
101
121
  const playerState = usePlayerState("audio-player-wrapper");
@@ -111,26 +131,141 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
111
131
  isTabletPortrait,
112
132
  } = props;
113
133
 
134
+ 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 },
140
+ ];
141
+
114
142
  const isTablet = useIsTablet();
115
143
 
116
144
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
117
145
 
146
+ // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
147
+
148
+ const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
149
+
150
+ const { title, summary } = useTitleSummaryOverlay(
151
+ feedUrl,
152
+ entry,
153
+ playerState?.isLive,
154
+ configuration?.audio_live_player_update_interval
155
+ );
156
+
157
+ const [playbackSpeed, setPlaybackSpeed] = useState({
158
+ asset: configuration["speed_1"],
159
+ value: 1.0,
160
+ });
161
+
162
+ const [sleepTimer, setSleepTimer] = useState({
163
+ label: "playback_sleep_off_label",
164
+ value: 0,
165
+ });
166
+
167
+ const [sleepTimerDate, setSleepTimerDate] = useState(0);
168
+
169
+ const [defaultDimensions, setDefaultDimensions] =
170
+ React.useState<Dimensions>(undefined);
171
+
118
172
  const [dimensions, setDimensions] = React.useState<Dimensions>({
119
173
  width: 0,
120
174
  height: 0,
121
175
  });
122
176
 
177
+ const navigator = useNavigation();
178
+
123
179
  const { maximiseVideoModal } = useNavigation();
124
180
 
125
181
  const onLayout = (event: any) => {
126
182
  const { width, height } = event.nativeEvent.layout;
127
183
 
128
184
  setDimensions({
129
- width: docked ? defaultAspectRatioWidth(height) : width,
185
+ width,
130
186
  height,
131
187
  });
188
+
189
+ if (!defaultDimensions) setDefaultDimensions({ width, height });
190
+ };
191
+
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
+ }
132
219
  };
133
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
+ 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
+ };
253
+
254
+ const loadSleep = async () => {
255
+ const sleep = await loadItemsFromStorage("sleep");
256
+
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
+ }
263
+ };
264
+
265
+ loadSpeed().then();
266
+ loadSleep().then();
267
+ }, [playerState.isReadyToPlay]);
268
+
134
269
  const getValue = (key: string, stylesObj: {}) =>
135
270
  R.propOr(null, key, stylesObj);
136
271
 
@@ -160,7 +295,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
160
295
  playerState.isPaused ? player?.play() : player.pause();
161
296
  }, [playerState, player]);
162
297
 
163
- const seekDuration = Number(value("seek_duration"));
298
+ const seekDuration = toNumberWithDefault(10, value("seek_duration"));
164
299
 
165
300
  const onRewindButtonPress = React.useCallback(() => {
166
301
  player?.rewind(seekDuration);
@@ -170,6 +305,60 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
170
305
  player?.forward(seekDuration);
171
306
  }, [seekDuration, player]);
172
307
 
308
+ const onSpeedSelect = React.useCallback(
309
+ async (item) => {
310
+ setPlaybackSpeed(item);
311
+ player.setPlaybackRate(item.value);
312
+ await localStorage.setItem("speed", item, NAMESPACE);
313
+ },
314
+ [player, setPlaybackSpeed]
315
+ );
316
+
317
+ const onSleepSelect = React.useCallback(
318
+ 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
+ );
328
+ },
329
+ [player, setSleepTimer]
330
+ );
331
+
332
+ const onSpeedButtonPress = React.useCallback(() => {
333
+ navigator.openModalBottomSheet({
334
+ modalBottomSheetContentProps: {
335
+ items: speeds,
336
+ current_selection: playbackSpeed,
337
+ onPress: onSpeedSelect,
338
+ title: configuration["playback_speed_title"],
339
+ },
340
+ options: {
341
+ supportedOrientations: ["portrait"],
342
+ },
343
+ });
344
+ }, [playbackSpeed, setPlaybackSpeed]);
345
+
346
+ const onSleepButtonPress = React.useCallback(() => {
347
+ navigator.openModalBottomSheet({
348
+ modalBottomSheetContentProps: {
349
+ items: [
350
+ { label: configuration["playback_sleep_off_label"], value: 0 },
351
+ ].concat(sleeps),
352
+ current_selection: sleepTimer,
353
+ onPress: onSleepSelect,
354
+ title: configuration["playback_sleep_title"],
355
+ },
356
+ options: {
357
+ supportedOrientations: ["portrait"],
358
+ },
359
+ });
360
+ }, [sleepTimer, setSleepTimer, configuration]);
361
+
173
362
  const onCloseVideoModal = React.useCallback(() => {
174
363
  playerManager.close();
175
364
  }, [playerManager]);
@@ -189,48 +378,64 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
378
  player?.seekTo(0);
190
379
  }, [player]);
191
380
 
192
- const edges = React.useMemo<Edge[]>(
193
- () => (docked ? [] : ["right", "left", "top"]),
194
- [docked]
195
- );
381
+ const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
196
382
 
197
- const ContainerView = docked ? View : SafeAreaView;
383
+ const ContainerView = SafeAreaView;
198
384
 
199
385
  const MainContent = React.useMemo(() => {
200
386
  return (
201
- <ContainerView style={Styles.mainContainer(docked, style)} edges={edges}>
202
- <ScrollView
203
- style={Styles.playerContainer(docked, style)}
204
- onLayout={onLayout}
387
+ <ContainerView style={Styles.mainContainer(style)} edges={edges}>
388
+ <AnimationComponent
389
+ animationType={ComponentAnimationType.moveUpComponent}
390
+ additionalData={{ saveArea: true }}
205
391
  >
206
- <Layout
207
- entry={entry}
208
- playerState={playerState}
209
- layoutState={layoutState}
210
- value={value}
211
- onPlaybackButtonPress={onPlaybackButtonPress}
212
- onRewindButtonPress={onRewindButtonPress}
213
- onForwardButtonPress={onForwardButtonPress}
214
- onPlayerSeek={onPlayerSeek}
215
- onSkipStart={onSkipStart}
216
- onSkipLive={onSkipLive}
217
- onCloseVideoModal={onCloseVideoModal}
218
- maximiseVideoModal={maximiseVideoModal}
219
- configuration={configuration}
220
- dimensions={dimensions}
221
- playNextData={playNextData}
222
- />
223
- {children}
224
- {!docked && (
225
- <PlayerDetails
226
- configuration={configuration}
227
- style={Styles.playerDetails}
228
- entry={entry}
229
- isTabletLandscape={isTabletLandscape}
230
- isAudioPlayer={true}
231
- />
232
- )}
233
- </ScrollView>
392
+ <AnimatedScrollModal>
393
+ <View style={Styles.playerContainer} onLayout={onLayout}>
394
+ <Layout
395
+ entry={entry}
396
+ playerState={playerState}
397
+ layoutState={layoutState}
398
+ value={value}
399
+ onPlaybackButtonPress={onPlaybackButtonPress}
400
+ onRewindButtonPress={onRewindButtonPress}
401
+ onForwardButtonPress={onForwardButtonPress}
402
+ onPlayerSeek={onPlayerSeek}
403
+ onSkipStart={onSkipStart}
404
+ onSpeedButtonPress={onSpeedButtonPress}
405
+ onSleepButtonPress={onSleepButtonPress}
406
+ playbackSpeedName={formatSpeed(playbackSpeed.value)}
407
+ playbackSleepName={
408
+ sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
409
+ }
410
+ sleepTimerDate={sleepTimerDate}
411
+ cancelSleepTimer={cancelSleepTimer}
412
+ onSkipLive={onSkipLive}
413
+ onCloseVideoModal={onCloseVideoModal}
414
+ maximiseVideoModal={maximiseVideoModal}
415
+ configuration={configuration}
416
+ dimensions={dimensions}
417
+ playNextData={playNextData}
418
+ title={title}
419
+ summary={summary}
420
+ defaultDimensions={defaultDimensions}
421
+ />
422
+ <View style={Styles.paddingHorizontal}>
423
+ {children}
424
+ <AnimationComponent
425
+ animationType={ComponentAnimationType.componentFade}
426
+ >
427
+ <PlayerDetails
428
+ configuration={configuration}
429
+ style={Styles.playerDetails}
430
+ entry={entry}
431
+ isTabletLandscape={isTabletLandscape}
432
+ isAudioPlayer={true}
433
+ />
434
+ </AnimationComponent>
435
+ </View>
436
+ </View>
437
+ </AnimatedScrollModal>
438
+ </AnimationComponent>
234
439
  </ContainerView>
235
440
  );
236
441
  }, [
@@ -252,12 +457,25 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
252
457
  playNextData,
253
458
  onSkipStart,
254
459
  onSkipLive,
460
+ sleepTimer.value,
461
+ sleepTimerDate,
255
462
  ]);
256
463
 
257
464
  if (configuration.magic_background) {
465
+ const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
466
+
467
+ const queryParamName =
468
+ configuration.audio_player_background_image_query || "width";
469
+
470
+ const queryParamValue = 15;
471
+
472
+ const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
473
+
258
474
  return (
259
475
  <MagicBG
260
- image={entry?.extensions?.audio_player_background_image}
476
+ image={url}
477
+ fallbackUrl={baseUrl}
478
+ colors={entry?.extensions?.backgroundColors}
261
479
  defaultColor="#000000"
262
480
  >
263
481
  {MainContent}