@applicaster/quick-brick-player 12.0.0-rc.5 → 12.0.0-rc.50

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.5",
3
+ "version": "12.0.0-rc.50",
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.5",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.49",
40
40
  "@applicaster/quick-brick-tv-transport-controls": "11.0.0-rc.133",
41
- "@applicaster/zapp-react-native-tvos-app": "12.0.0-rc.5",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-rc.5",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-rc.5",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-rc.50",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-rc.50",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-rc.50",
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,23 @@ 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";
19
35
 
20
36
  const { log_debug } = createLogger({
21
37
  subsystem: "UIComponent",
@@ -32,12 +48,13 @@ type Dimensions = {
32
48
  type Configuration = {
33
49
  [key: string]: any;
34
50
  magic_background?: boolean;
51
+ audio_player_background_image_query?: string;
35
52
  tablet_landscape_sidebar_width?: string;
36
53
  tablet_landscape_player_container_background_color?: string;
37
54
  };
38
55
 
39
56
  type Props = {
40
- children: React.ReactNode;
57
+ children?: React.ReactNode;
41
58
  inline?: boolean;
42
59
  docked?: boolean;
43
60
  configuration: Configuration;
@@ -57,29 +74,20 @@ export const defaultAspectRatioWidth = (height: number): number =>
57
74
  (height / 9) * 16;
58
75
 
59
76
  const Styles = {
60
- mainContainer: (docked: boolean, style: ViewStyle) => {
77
+ mainContainer: (style: ViewStyle) => {
61
78
  const combinedStyle: ViewStyle = {
62
79
  flex: 1,
63
80
  };
64
81
 
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,
82
+ return {
83
+ ...combinedStyle,
84
+ ...style,
75
85
  };
76
-
77
- if (docked) {
78
- combinedStyle.height = style.height;
79
- }
80
-
81
- return combinedStyle;
82
86
  },
87
+ playerContainer: {
88
+ flex: 1,
89
+ },
90
+ paddingHorizontal: { paddingTop: 0, paddingHorizontal: PADDING_HORIZONTAL },
83
91
 
84
92
  controlsMobile: {
85
93
  height: 50,
@@ -96,6 +104,17 @@ const Styles = {
96
104
  },
97
105
  };
98
106
 
107
+ const sleeps = [
108
+ // { label: "End of episode", value: "episode" },
109
+ { label: "5 minutes", value: 5 },
110
+ { label: "10 minutes", value: 10 },
111
+ { label: "15 minutes", value: 15 },
112
+ { label: "30 minutes", value: 30 },
113
+ { label: "45 minutes", value: 45 },
114
+ { label: "1 hour", value: 60 },
115
+ { label: "2 hours", value: 120 },
116
+ ];
117
+
99
118
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
100
119
  const player = usePlayer();
101
120
  const playerState = usePlayerState("audio-player-wrapper");
@@ -111,26 +130,141 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
111
130
  isTabletPortrait,
112
131
  } = props;
113
132
 
133
+ const speeds = [
134
+ { asset: configuration["speed_0_8"], value: 0.8 },
135
+ { asset: configuration["speed_1"], value: 1.0 },
136
+ { asset: configuration["speed_1_2"], value: 1.2 },
137
+ { asset: configuration["speed_1_5"], value: 1.5 },
138
+ { asset: configuration["speed_2"], value: 2.0 },
139
+ ];
140
+
114
141
  const isTablet = useIsTablet();
115
142
 
116
143
  const isTabletLandscape = !isTV() && isTablet && !isTabletPortrait;
117
144
 
145
+ // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
146
+
147
+ const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
148
+
149
+ const { title, summary } = useTitleSummaryOverlay(
150
+ feedUrl,
151
+ entry,
152
+ playerState?.isLive,
153
+ configuration?.audio_live_player_update_interval
154
+ );
155
+
156
+ const [playbackSpeed, setPlaybackSpeed] = useState({
157
+ asset: configuration["speed_1"],
158
+ value: 1.0,
159
+ });
160
+
161
+ const [sleepTimer, setSleepTimer] = useState({
162
+ label: "playback_sleep_off_label",
163
+ value: 0,
164
+ });
165
+
166
+ const [sleepTimerDate, setSleepTimerDate] = useState(0);
167
+
168
+ const [defaultDimensions, setDefaultDimensions] =
169
+ React.useState<Dimensions>(undefined);
170
+
118
171
  const [dimensions, setDimensions] = React.useState<Dimensions>({
119
172
  width: 0,
120
173
  height: 0,
121
174
  });
122
175
 
176
+ const navigator = useNavigation();
177
+
123
178
  const { maximiseVideoModal } = useNavigation();
124
179
 
125
180
  const onLayout = (event: any) => {
126
181
  const { width, height } = event.nativeEvent.layout;
127
182
 
128
183
  setDimensions({
129
- width: docked ? defaultAspectRatioWidth(height) : width,
184
+ width,
130
185
  height,
131
186
  });
187
+
188
+ if (!defaultDimensions) setDefaultDimensions({ width, height });
189
+ };
190
+
191
+ const cancelSleepTimer = async () => {
192
+ player?.cancelSleepTimer();
193
+
194
+ setSleepTimer({
195
+ label: "playback_sleep_off_label",
196
+ value: 0,
197
+ });
198
+
199
+ setSleepTimerDate(0);
200
+
201
+ await localStorage.removeItem("sleep", NAMESPACE);
202
+ };
203
+
204
+ const startSleepTimer = (timerValue: number) => {
205
+ try {
206
+ const currentDate = new Date();
207
+ const futureDate = new Date(currentDate.getTime() + timerValue * 60000);
208
+ const futureSleepDate = futureDate.getTime();
209
+
210
+ player?.cancelSleepTimer();
211
+
212
+ player?.startSleepTimer(futureSleepDate);
213
+
214
+ return futureSleepDate;
215
+ } catch (e) {
216
+ return 0;
217
+ }
132
218
  };
133
219
 
220
+ const handleSleep = async (
221
+ timerValue: number,
222
+ currentFutureSleepDate = null
223
+ ) => {
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
+
243
+ useEffect(() => {
244
+ const loadSpeed = async () => {
245
+ const speed = await loadItemsFromStorage("speed");
246
+
247
+ if (playerState.isReadyToPlay && speed) {
248
+ setPlaybackSpeed(speed);
249
+ player?.setPlaybackRate(speed.value);
250
+ }
251
+ };
252
+
253
+ const loadSleep = async () => {
254
+ const sleep = await loadItemsFromStorage("sleep");
255
+
256
+ if (playerState.isReadyToPlay && sleep) {
257
+ const { futureSleepDate, ...rest } = sleep;
258
+ setSleepTimer(rest);
259
+ const sleepTimerDate = await handleSleep(sleep.value, futureSleepDate);
260
+ setSleepTimerDate(sleepTimerDate);
261
+ }
262
+ };
263
+
264
+ loadSpeed().then();
265
+ loadSleep().then();
266
+ }, [playerState.isReadyToPlay]);
267
+
134
268
  const getValue = (key: string, stylesObj: {}) =>
135
269
  R.propOr(null, key, stylesObj);
136
270
 
@@ -170,6 +304,60 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
170
304
  player?.forward(seekDuration);
171
305
  }, [seekDuration, player]);
172
306
 
307
+ const onSpeedSelect = React.useCallback(
308
+ async (item) => {
309
+ setPlaybackSpeed(item);
310
+ player.setPlaybackRate(item.value);
311
+ await localStorage.setItem("speed", item, NAMESPACE);
312
+ },
313
+ [player, setPlaybackSpeed]
314
+ );
315
+
316
+ const onSleepSelect = React.useCallback(
317
+ async (item) => {
318
+ setSleepTimer(item);
319
+ const futureSleepDate = await handleSleep(item.value);
320
+ setSleepTimerDate(futureSleepDate);
321
+
322
+ await localStorage.setItem(
323
+ "sleep",
324
+ { ...item, futureSleepDate },
325
+ NAMESPACE
326
+ );
327
+ },
328
+ [player, setSleepTimer]
329
+ );
330
+
331
+ const onSpeedButtonPress = React.useCallback(() => {
332
+ navigator.openModalBottomSheet({
333
+ modalBottomSheetContentProps: {
334
+ items: speeds,
335
+ current_selection: playbackSpeed,
336
+ onPress: onSpeedSelect,
337
+ title: configuration["playback_speed_title"],
338
+ },
339
+ options: {
340
+ supportedOrientations: ["portrait"],
341
+ },
342
+ });
343
+ }, [playbackSpeed, setPlaybackSpeed]);
344
+
345
+ const onSleepButtonPress = React.useCallback(() => {
346
+ navigator.openModalBottomSheet({
347
+ modalBottomSheetContentProps: {
348
+ items: [
349
+ { label: configuration["playback_sleep_off_label"], value: 0 },
350
+ ].concat(sleeps),
351
+ current_selection: sleepTimer,
352
+ onPress: onSleepSelect,
353
+ title: configuration["playback_sleep_title"],
354
+ },
355
+ options: {
356
+ supportedOrientations: ["portrait"],
357
+ },
358
+ });
359
+ }, [sleepTimer, setSleepTimer, configuration]);
360
+
173
361
  const onCloseVideoModal = React.useCallback(() => {
174
362
  playerManager.close();
175
363
  }, [playerManager]);
@@ -189,48 +377,64 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
189
377
  player?.seekTo(0);
190
378
  }, [player]);
191
379
 
192
- const edges = React.useMemo<Edge[]>(
193
- () => (docked ? [] : ["right", "left", "top"]),
194
- [docked]
195
- );
380
+ const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
196
381
 
197
- const ContainerView = docked ? View : SafeAreaView;
382
+ const ContainerView = SafeAreaView;
198
383
 
199
384
  const MainContent = React.useMemo(() => {
200
385
  return (
201
- <ContainerView style={Styles.mainContainer(docked, style)} edges={edges}>
202
- <ScrollView
203
- style={Styles.playerContainer(docked, style)}
204
- onLayout={onLayout}
386
+ <ContainerView style={Styles.mainContainer(style)} edges={edges}>
387
+ <AnimationComponent
388
+ animationType={ComponentAnimationType.moveUpComponent}
389
+ additionalData={{ saveArea: true }}
205
390
  >
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>
391
+ <AnimatedScrollModal>
392
+ <View style={Styles.playerContainer} onLayout={onLayout}>
393
+ <Layout
394
+ entry={entry}
395
+ playerState={playerState}
396
+ layoutState={layoutState}
397
+ value={value}
398
+ onPlaybackButtonPress={onPlaybackButtonPress}
399
+ onRewindButtonPress={onRewindButtonPress}
400
+ onForwardButtonPress={onForwardButtonPress}
401
+ onPlayerSeek={onPlayerSeek}
402
+ onSkipStart={onSkipStart}
403
+ onSpeedButtonPress={onSpeedButtonPress}
404
+ onSleepButtonPress={onSleepButtonPress}
405
+ playbackSpeedName={formatSpeed(playbackSpeed.value)}
406
+ playbackSleepName={
407
+ sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
408
+ }
409
+ sleepTimerDate={sleepTimerDate}
410
+ cancelSleepTimer={cancelSleepTimer}
411
+ onSkipLive={onSkipLive}
412
+ onCloseVideoModal={onCloseVideoModal}
413
+ maximiseVideoModal={maximiseVideoModal}
414
+ configuration={configuration}
415
+ dimensions={dimensions}
416
+ playNextData={playNextData}
417
+ title={title}
418
+ summary={summary}
419
+ defaultDimensions={defaultDimensions}
420
+ />
421
+ <View style={Styles.paddingHorizontal}>
422
+ {children}
423
+ <AnimationComponent
424
+ animationType={ComponentAnimationType.componentFade}
425
+ >
426
+ <PlayerDetails
427
+ configuration={configuration}
428
+ style={Styles.playerDetails}
429
+ entry={entry}
430
+ isTabletLandscape={isTabletLandscape}
431
+ isAudioPlayer={true}
432
+ />
433
+ </AnimationComponent>
434
+ </View>
435
+ </View>
436
+ </AnimatedScrollModal>
437
+ </AnimationComponent>
234
438
  </ContainerView>
235
439
  );
236
440
  }, [
@@ -252,12 +456,25 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
252
456
  playNextData,
253
457
  onSkipStart,
254
458
  onSkipLive,
459
+ sleepTimer.value,
460
+ sleepTimerDate,
255
461
  ]);
256
462
 
257
463
  if (configuration.magic_background) {
464
+ const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
465
+
466
+ const queryParamName =
467
+ configuration.audio_player_background_image_query || "width";
468
+
469
+ const queryParamValue = 15;
470
+
471
+ const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
472
+
258
473
  return (
259
474
  <MagicBG
260
- image={entry?.extensions?.audio_player_background_image}
475
+ image={url}
476
+ fallbackUrl={baseUrl}
477
+ colors={entry?.extensions?.backgroundColors}
261
478
  defaultColor="#000000"
262
479
  >
263
480
  {MainContent}