@applicaster/quick-brick-player 12.0.0-alpha.4534459584 → 12.0.0-alpha.5040434238

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-alpha.4534459584",
3
+ "version": "12.0.0-alpha.5040434238",
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-alpha.4534459584",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-alpha.5040434238",
40
40
  "@applicaster/quick-brick-tv-transport-controls": "11.0.0-rc.133",
41
- "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.4534459584",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.4534459584",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4534459584",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.5040434238",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.5040434238",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5040434238",
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,6 +48,7 @@ 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
  };
@@ -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);
132
202
  };
133
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
+ }
218
+ };
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]);
@@ -190,47 +378,67 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
190
378
  }, [player]);
191
379
 
192
380
  const edges = React.useMemo<Edge[]>(
193
- () => (docked ? [] : ["right", "left", "top"]),
381
+ () => (["right", "left", "top"]),
194
382
  [docked]
195
383
  );
196
384
 
197
- const ContainerView = docked ? View : SafeAreaView;
385
+ const ContainerView = SafeAreaView;
198
386
 
199
387
  const MainContent = React.useMemo(() => {
200
388
  return (
201
- <ContainerView style={Styles.mainContainer(docked, style)} edges={edges}>
202
- <ScrollView
203
- style={Styles.playerContainer(docked, style)}
204
- onLayout={onLayout}
389
+ <ContainerView style={Styles.mainContainer(style)} edges={edges}>
390
+ <AnimationComponent
391
+ animationType={ComponentAnimationType.moveUpComponent}
392
+ additionalData={{ saveArea: true }}
205
393
  >
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>
394
+ <AnimatedScrollModal>
395
+ <View
396
+ style={Styles.playerContainer}
397
+ onLayout={onLayout}
398
+ >
399
+ <Layout
400
+ entry={entry}
401
+ playerState={playerState}
402
+ layoutState={layoutState}
403
+ value={value}
404
+ onPlaybackButtonPress={onPlaybackButtonPress}
405
+ onRewindButtonPress={onRewindButtonPress}
406
+ onForwardButtonPress={onForwardButtonPress}
407
+ onPlayerSeek={onPlayerSeek}
408
+ onSkipStart={onSkipStart}
409
+ onSpeedButtonPress={onSpeedButtonPress}
410
+ onSleepButtonPress={onSleepButtonPress}
411
+ playbackSpeedName={formatSpeed(playbackSpeed.value)}
412
+ playbackSleepName={
413
+ sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
414
+ }
415
+ sleepTimerDate={sleepTimerDate}
416
+ cancelSleepTimer={cancelSleepTimer}
417
+ onSkipLive={onSkipLive}
418
+ onCloseVideoModal={onCloseVideoModal}
419
+ maximiseVideoModal={maximiseVideoModal}
420
+ configuration={configuration}
421
+ dimensions={dimensions}
422
+ playNextData={playNextData}
423
+ title={title}
424
+ summary={summary}
425
+ defaultDimensions={defaultDimensions}
426
+ />
427
+ <View style={Styles.paddingHorizontal}>
428
+ {children}
429
+ <AnimationComponent animationType={ComponentAnimationType.componentFade}>
430
+ <PlayerDetails
431
+ configuration={configuration}
432
+ style={Styles.playerDetails}
433
+ entry={entry}
434
+ isTabletLandscape={isTabletLandscape}
435
+ isAudioPlayer={true}
436
+ />
437
+ </AnimationComponent>
438
+ </View>
439
+ </View>
440
+ </AnimatedScrollModal>
441
+ </AnimationComponent>
234
442
  </ContainerView>
235
443
  );
236
444
  }, [
@@ -252,12 +460,25 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
252
460
  playNextData,
253
461
  onSkipStart,
254
462
  onSkipLive,
463
+ sleepTimer.value,
464
+ sleepTimerDate,
255
465
  ]);
256
466
 
257
467
  if (configuration.magic_background) {
468
+ const baseUrl = entry?.extensions?.audio_player_background_image ?? "";
469
+
470
+ const queryParamName =
471
+ configuration.audio_player_background_image_query || "width";
472
+
473
+ const queryParamValue = 15;
474
+
475
+ const url = addQueryParam(baseUrl, queryParamName, queryParamValue);
476
+
258
477
  return (
259
478
  <MagicBG
260
- image={entry?.extensions?.audio_player_background_image}
479
+ image={url}
480
+ fallbackUrl={baseUrl}
481
+ colors={entry?.extensions?.backgroundColors}
261
482
  defaultColor="#000000"
262
483
  >
263
484
  {MainContent}