@applicaster/quick-brick-player 12.0.0-alpha.3208346089 → 12.0.0-alpha.3262004353

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.3208346089",
3
+ "version": "12.0.0-alpha.3262004353",
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": "12.0.0-rc.33",
40
- "@applicaster/quick-brick-tv-transport-controls": "11.0.0-rc.133",
41
- "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.3208346089",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.3208346089",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.3208346089",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.81",
40
+ "@applicaster/quick-brick-tv-transport-controls": "12.0.0-rc.70",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.3262004353",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.3262004353",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.3262004353",
44
44
  "keymirror": "^0.1.1",
45
45
  "query-string": "7.1.3",
46
46
  "react-native-web-linear-gradient": "1.1.2",
@@ -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";
@@ -26,6 +26,13 @@ import {
26
26
  import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
27
27
  import { addQueryParam, getLiveUrl } from "../Utils";
28
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";
29
36
 
30
37
  const { log_debug } = createLogger({
31
38
  subsystem: "UIComponent",
@@ -68,29 +75,20 @@ export const defaultAspectRatioWidth = (height: number): number =>
68
75
  (height / 9) * 16;
69
76
 
70
77
  const Styles = {
71
- mainContainer: (docked: boolean, style: ViewStyle) => {
78
+ mainContainer: (style: ViewStyle) => {
72
79
  const combinedStyle: ViewStyle = {
73
80
  flex: 1,
74
81
  };
75
82
 
76
- if (!docked) {
77
- style.paddingTop = 0;
78
- style.paddingHorizontal = 15;
79
- }
80
-
81
- return { ...combinedStyle, ...style };
82
- },
83
- playerContainer: (docked: boolean, style: ViewStyle) => {
84
- const combinedStyle: ViewStyle = {
85
- flex: 1,
83
+ return {
84
+ ...combinedStyle,
85
+ ...style,
86
86
  };
87
-
88
- if (docked) {
89
- combinedStyle.height = style.height;
90
- }
91
-
92
- return combinedStyle;
93
87
  },
88
+ playerContainer: {
89
+ flex: 1,
90
+ },
91
+ paddingHorizontal: { paddingTop: 0, paddingHorizontal: PADDING_HORIZONTAL },
94
92
 
95
93
  controlsMobile: {
96
94
  height: 50,
@@ -168,6 +166,9 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
168
166
 
169
167
  const [sleepTimerDate, setSleepTimerDate] = useState(0);
170
168
 
169
+ const [defaultDimensions, setDefaultDimensions] =
170
+ React.useState<Dimensions>(undefined);
171
+
171
172
  const [dimensions, setDimensions] = React.useState<Dimensions>({
172
173
  width: 0,
173
174
  height: 0,
@@ -181,9 +182,11 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
181
182
  const { width, height } = event.nativeEvent.layout;
182
183
 
183
184
  setDimensions({
184
- width: docked ? defaultAspectRatioWidth(height) : width,
185
+ width,
185
186
  height,
186
187
  });
188
+
189
+ if (!defaultDimensions) setDefaultDimensions({ width, height });
187
190
  };
188
191
 
189
192
  const cancelSleepTimer = async () => {
@@ -292,7 +295,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
292
295
  playerState.isPaused ? player?.play() : player.pause();
293
296
  }, [playerState, player]);
294
297
 
295
- const seekDuration = Number(value("seek_duration"));
298
+ const seekDuration = toNumberWithDefault(10, value("seek_duration"));
296
299
 
297
300
  const onRewindButtonPress = React.useCallback(() => {
298
301
  player?.rewind(seekDuration);
@@ -375,58 +378,64 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
375
378
  player?.seekTo(0);
376
379
  }, [player]);
377
380
 
378
- const edges = React.useMemo<Edge[]>(
379
- () => (docked ? [] : ["right", "left", "top"]),
380
- [docked]
381
- );
381
+ const edges = React.useMemo<Edge[]>(() => ["right", "left", "top"], [docked]);
382
382
 
383
- const ContainerView = docked ? View : SafeAreaView;
383
+ const ContainerView = SafeAreaView;
384
384
 
385
385
  const MainContent = React.useMemo(() => {
386
386
  return (
387
- <ContainerView style={Styles.mainContainer(docked, style)} edges={edges}>
388
- <ScrollView
389
- style={Styles.playerContainer(docked, style)}
390
- onLayout={onLayout}
387
+ <ContainerView style={Styles.mainContainer(style)} edges={edges}>
388
+ <AnimationComponent
389
+ animationType={ComponentAnimationType.moveUpComponent}
390
+ additionalData={{ saveArea: true }}
391
391
  >
392
- <Layout
393
- entry={entry}
394
- playerState={playerState}
395
- layoutState={layoutState}
396
- value={value}
397
- onPlaybackButtonPress={onPlaybackButtonPress}
398
- onRewindButtonPress={onRewindButtonPress}
399
- onForwardButtonPress={onForwardButtonPress}
400
- onPlayerSeek={onPlayerSeek}
401
- onSkipStart={onSkipStart}
402
- onSpeedButtonPress={onSpeedButtonPress}
403
- onSleepButtonPress={onSleepButtonPress}
404
- playbackSpeedName={formatSpeed(playbackSpeed.value)}
405
- playbackSleepName={
406
- sleepTimer.value === 0 ? "sleep_timer" : "sleep_timer_active"
407
- }
408
- sleepTimerDate={sleepTimerDate}
409
- cancelSleepTimer={cancelSleepTimer}
410
- onSkipLive={onSkipLive}
411
- onCloseVideoModal={onCloseVideoModal}
412
- maximiseVideoModal={maximiseVideoModal}
413
- configuration={configuration}
414
- dimensions={dimensions}
415
- playNextData={playNextData}
416
- title={title}
417
- summary={summary}
418
- />
419
- {children}
420
- {!docked && (
421
- <PlayerDetails
422
- configuration={configuration}
423
- style={Styles.playerDetails}
424
- entry={entry}
425
- isTabletLandscape={isTabletLandscape}
426
- isAudioPlayer={true}
427
- />
428
- )}
429
- </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>
430
439
  </ContainerView>
431
440
  );
432
441
  }, [
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
3
  import { View } from "react-native";
4
+ import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
4
5
 
5
6
  import {
6
7
  ProgressBar,
@@ -18,13 +19,22 @@ import {
18
19
  rewindButton,
19
20
  skipToStartButton,
20
21
  skipToLiveButton,
22
+ PADDING_HORIZONTAL,
21
23
  speedButton,
22
24
  sleepButton,
23
25
  } from "./defaultLayoutsConts";
24
26
  import { AudioDescription } from "../AudioDescription";
25
27
  import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
26
28
  import { useLanguageData } from "@applicaster/zapp-react-native-utils/reactHooks/player";
29
+ import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
27
30
  import { useEffect, useMemo, useState } from "react";
31
+ import {
32
+ useModalAnimationContext,
33
+ AnimationComponent,
34
+ ComponentAnimationType,
35
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
36
+ import DockedLayout from "./DockedLayout";
37
+ import { isValidDate } from "../../Utils";
28
38
 
29
39
  type Dimensions = {
30
40
  width: number;
@@ -60,22 +70,29 @@ type LayoutProps = {
60
70
  onPlayerSeek: (targetTime: number) => void;
61
71
  onSkipLive: () => void;
62
72
  onSkipStart: () => void;
73
+ onCloseVideoModal: () => void;
74
+ maximiseVideoModal: () => void;
63
75
  dimensions: Dimensions;
76
+ playNextData: PlayNextData;
64
77
  title: string | number;
65
78
  summary: string | number;
79
+ defaultDimensions: Dimensions;
66
80
  };
67
81
 
68
82
  const Styles = {
69
83
  container: { flex: 1 },
70
- tabletContainer: (isPortraitMode) => ({
84
+ tabletContainer: (isPortraitMode: boolean) => ({
71
85
  flex: 1,
72
86
  alignItems: "center",
73
87
  justifyContent: isPortraitMode ? "center" : "flex-start",
74
88
  }),
75
- tabletLayout: {
76
- width: 463,
89
+ playerImageWrapper: {
90
+ width: "100%",
77
91
  maxHeight: 750,
78
92
  marginTop: 80,
93
+ },
94
+ tabletLayout: {
95
+ width: 463,
79
96
  marginBottom: 80,
80
97
  },
81
98
  controls: (isTablet: boolean, isLiveShowFullControls: boolean) => ({
@@ -84,24 +101,31 @@ const Styles = {
84
101
  justifyContent: isLiveShowFullControls ? "space-between" : "space-evenly",
85
102
  }),
86
103
  backgroundImageContainer: (dimensions: Dimensions, isTablet: boolean) => {
104
+ const mobileValue = dimensions.width
105
+ ? dimensions.width - PADDING_HORIZONTAL * 2
106
+ : 330;
107
+
87
108
  return {
88
- width: isTablet ? 463 : dimensions.width || 330,
89
- height: isTablet ? 463 : dimensions.width || 330,
109
+ width: isTablet ? 463 : mobileValue,
110
+ height: isTablet ? 463 : mobileValue,
90
111
  };
91
112
  },
92
- backgroundImageContainerImageStyle: (isTablet) => ({
93
- borderRadius: isTablet ? 24 : 10,
113
+ backgroundImageContainerImageStyle: (
114
+ isTablet: boolean,
115
+ docked: boolean,
116
+ startComponentsAnimation: boolean,
117
+ isActiveGesture: boolean
118
+ ) => ({
119
+ borderRadius:
120
+ (startComponentsAnimation && isActiveGesture) || docked
121
+ ? 0
122
+ : isTablet
123
+ ? 24
124
+ : 10,
94
125
  }),
95
- playerContainer: (docked: boolean, style) => {
96
- const combinedStyle = {
97
- flex: 1,
98
- };
99
126
 
100
- if (docked) {
101
- combinedStyle["height"] = style.height;
102
- }
103
-
104
- return combinedStyle;
127
+ paddingHorizontal: {
128
+ paddingHorizontal: PADDING_HORIZONTAL,
105
129
  },
106
130
 
107
131
  controlsMobile: {
@@ -115,6 +139,12 @@ const Styles = {
115
139
  separator: {
116
140
  height: 20,
117
141
  },
142
+ flexDirectionRow: {
143
+ flexDirection: "row",
144
+ },
145
+ alignItemsCenter: {
146
+ alignItems: "center",
147
+ },
118
148
  };
119
149
 
120
150
  export default function DefaultLayout(props: LayoutProps) {
@@ -139,12 +169,14 @@ export default function DefaultLayout(props: LayoutProps) {
139
169
  sleepTimerDate,
140
170
  title,
141
171
  summary,
172
+ defaultDimensions,
142
173
  } = props;
143
174
 
144
175
  const isTablet = useIsTablet();
145
176
  const { width, height } = dimensions;
146
177
  const isPortraitMode = width < height;
147
178
  const isLiveContent = playerState.isLive;
179
+ const docked = layoutState.docked;
148
180
 
149
181
  const languageData = useLanguageData("audio-player");
150
182
 
@@ -160,8 +192,31 @@ export default function DefaultLayout(props: LayoutProps) {
160
192
 
161
193
  let showFullControls = !controller?.isAd();
162
194
 
195
+ const startTime = entry.extensions?.live?.start_time;
196
+ const endTime = entry.extensions?.live?.end_time;
197
+
198
+ const hasValidStartTime = isValidDate(startTime);
199
+ const hasValidEndTime = isValidDate(endTime);
200
+
201
+ const isDateBasedLiveContent =
202
+ isLiveContent && hasValidStartTime && hasValidEndTime;
203
+
204
+ const needsToShowProgressBar =
205
+ (isLiveContent &&
206
+ (Boolean(value("liveSeekingEnabled")) || isDateBasedLiveContent)) ||
207
+ !isLiveContent;
208
+
209
+ const startTimestamp = hasValidStartTime
210
+ ? dayjs.utc(startTime!).valueOf()
211
+ : undefined;
212
+
213
+ const endTimestamp = hasValidEndTime
214
+ ? dayjs.utc(endTime!).valueOf()
215
+ : undefined;
216
+
163
217
  if (isLiveContent) {
164
218
  const seekEnabled = Boolean(value("liveSeekingEnabled"));
219
+
165
220
  showFullControls = !!(controller?.hasSeekableDuration?.() && seekEnabled);
166
221
  }
167
222
 
@@ -191,12 +246,14 @@ export default function DefaultLayout(props: LayoutProps) {
191
246
 
192
247
  const rewind = rewindButton({
193
248
  isTablet,
194
- onPress: controller?.hasContentDuration?.() ? onRewindButtonPress : null,
249
+ onPress: controller?.hasSeekableDuration?.() ? onRewindButtonPress : null,
195
250
  });
196
251
 
197
252
  const forward = forwardButton({
198
253
  isTablet,
199
- onPress: controller?.hasContentDuration?.() ? onForwardButtonPress : null,
254
+ onPress: controller?.hasSeekableDuration?.()
255
+ ? onForwardButtonPress
256
+ : null,
200
257
  });
201
258
 
202
259
  const speed = speedButton({
@@ -225,7 +282,6 @@ export default function DefaultLayout(props: LayoutProps) {
225
282
  return [skipToStart, rewind, play, forward, skipToLive];
226
283
  }
227
284
 
228
- // Temporary empty button until we add sleep button. It will ensure the buttons will not break in their position
229
285
  return [speed, rewind, play, forward, sleep];
230
286
  }, [
231
287
  playerState,
@@ -246,15 +302,12 @@ export default function DefaultLayout(props: LayoutProps) {
246
302
 
247
303
  useEffect(() => {
248
304
  if (showCounter && sleepTimerDate !== 0) {
249
- // Setting the initial timeLeft based on sleepTimerDate
250
305
  setTimeLeft(sleepTimerDate - Date.now());
251
306
 
252
- // Setting up an interval to update the remaining time every second
253
307
  const intervalId = setInterval(() => {
254
308
  setTimeLeft(() => {
255
309
  const newTimeLeft = sleepTimerDate - Date.now();
256
310
 
257
- // If the new remaining time is less than or equal to 0, clear the interval and trigger the cancelSleepTimer function
258
311
  if (newTimeLeft <= 0) {
259
312
  clearInterval(intervalId);
260
313
 
@@ -262,26 +315,28 @@ export default function DefaultLayout(props: LayoutProps) {
262
315
 
263
316
  return 0;
264
317
  } else {
265
- // Otherwise, update the state with the new remaining time
266
318
  return newTimeLeft;
267
319
  }
268
320
  });
269
321
  }, 1000);
270
322
 
271
- // Clearing the interval when the component unmounts or dependencies change
272
323
  return () => clearInterval(intervalId);
273
324
  } else {
274
- // Ensure the timeLeft state is set correctly if showCounter is false
275
325
  setTimeLeft(0);
276
326
  }
277
327
  }, [sleepTimerDate, showCounter, cancelSleepTimer]);
278
328
 
279
329
  const Separator = () => <View style={Styles.separator} />;
280
330
 
281
- const liveSeekingEnabled = value("liveSeekingEnabled");
331
+ const defaultImageDimensions = React.useMemo(
332
+ () =>
333
+ defaultDimensions &&
334
+ Styles.backgroundImageContainer(defaultDimensions, isTablet),
335
+ [defaultDimensions, isTablet]
336
+ );
282
337
 
283
- const needsToShowProgressBar =
284
- (isLiveContent && liveSeekingEnabled) || !isLiveContent;
338
+ const { isActiveGesture, startComponentsAnimation } =
339
+ useModalAnimationContext();
285
340
 
286
341
  return (
287
342
  <View
@@ -289,65 +344,108 @@ export default function DefaultLayout(props: LayoutProps) {
289
344
  isTablet ? Styles.tabletContainer(isPortraitMode) : Styles.container
290
345
  }
291
346
  >
292
- <TransportInfo
293
- content={entry}
294
- value={value}
295
- visible={true}
296
- languageData={languageData}
297
- layoutState={layoutState}
298
- actionButtons={actionButtons}
299
- />
300
- <View
301
- style={
302
- isTablet ? Styles.tabletContainer(isPortraitMode) : Styles.container
303
- }
347
+ <AnimationComponent animationType={ComponentAnimationType.componentFade}>
348
+ <TransportInfo
349
+ content={entry}
350
+ value={value}
351
+ visible={true}
352
+ languageData={languageData}
353
+ layoutState={layoutState}
354
+ actionButtons={actionButtons}
355
+ />
356
+ </AnimationComponent>
357
+ <AnimationComponent
358
+ animationType={ComponentAnimationType.moveUpComponent}
359
+ style={isTablet ? Styles.playerImageWrapper : undefined}
304
360
  >
305
- <View style={isTablet ? Styles.tabletLayout : null}>
361
+ <AnimationComponent
362
+ animationType={ComponentAnimationType.moveComponentHorizontal}
363
+ additionalData={{
364
+ disableAnimatedComponent: !isTablet,
365
+ moveValue: (dimensions.width - 463) / 2,
366
+ extraAnimation: !isTablet
367
+ ? {
368
+ [ComponentAnimationType.audioPlayerPaddingHorizontal]: {
369
+ style: Styles.paddingHorizontal,
370
+ },
371
+ }
372
+ : null,
373
+ }}
374
+ style={isTablet ? Styles.alignItemsCenter : undefined}
375
+ >
306
376
  <PlayerImageBackground
307
377
  entry={entry}
308
378
  style={Styles.backgroundImageContainer(dimensions, isTablet)}
309
- imageStyle={Styles.backgroundImageContainerImageStyle(isTablet)}
310
- />
311
- <AudioDescription
312
- title={title}
313
- summary={summary}
314
- value={value}
315
- layoutState={layoutState}
379
+ imageStyle={Styles.backgroundImageContainerImageStyle(
380
+ isTablet,
381
+ docked,
382
+ startComponentsAnimation,
383
+ isActiveGesture
384
+ )}
385
+ defaultImageDimensions={defaultImageDimensions}
316
386
  />
317
- <Separator />
318
- {needsToShowProgressBar && (
319
- <View>
320
- <ProgressBar
321
- content={entry}
322
- value={value}
323
- visible={true}
324
- layoutState={layoutState}
325
- onPlayerSeek={onPlayerSeek}
326
- playerState={playerState}
327
- disableFullscreen={true}
328
- sliderStyle={2}
329
- />
330
-
331
- {showCounter && timeLeft >= 0 && (
332
- <CountDown
333
- timeLeft={timeLeft}
387
+ </AnimationComponent>
388
+
389
+ <DockedLayout {...props} />
390
+ </AnimationComponent>
391
+ <View
392
+ style={[
393
+ Styles.paddingHorizontal,
394
+ isTablet ? Styles.tabletLayout : undefined,
395
+ ]}
396
+ >
397
+ {!docked && (
398
+ <AnimationComponent
399
+ animationType={ComponentAnimationType.componentFade}
400
+ >
401
+ <AudioDescription
402
+ title={title}
403
+ summary={summary}
404
+ value={value}
405
+ layoutState={layoutState}
406
+ />
407
+ <Separator />
408
+ {needsToShowProgressBar && (
409
+ <View>
410
+ <ProgressBar
411
+ content={entry}
334
412
  value={value}
335
- title={sleepTitle}
413
+ visible={true}
414
+ layoutState={layoutState}
415
+ onPlayerSeek={onPlayerSeek}
416
+ playerState={playerState}
417
+ disableFullscreen={true}
418
+ sliderStyle={2}
419
+ startTimestamp={startTimestamp}
420
+ endTimestamp={endTimestamp}
421
+ isDateBasedLiveContent={isDateBasedLiveContent}
422
+ onSkipLive={onSkipLive}
336
423
  />
337
- )}
338
424
 
339
- <Separator />
340
- </View>
341
- )}
342
- <Controls
343
- value={value}
344
- visible={true}
345
- keepAlive={null}
346
- buttons={controlButtons}
347
- style={Styles.controls(isTablet, isLiveContent && showFullControls)}
348
- playerState={playerState}
349
- />
350
- </View>
425
+ {showCounter && timeLeft >= 0 && (
426
+ <CountDown
427
+ timeLeft={timeLeft}
428
+ value={value}
429
+ title={sleepTitle}
430
+ />
431
+ )}
432
+
433
+ <Separator />
434
+ </View>
435
+ )}
436
+ <Controls
437
+ value={value}
438
+ visible={true}
439
+ keepAlive={null}
440
+ buttons={controlButtons}
441
+ style={Styles.controls(
442
+ isTablet,
443
+ isLiveContent && showFullControls
444
+ )}
445
+ playerState={playerState}
446
+ />
447
+ </AnimationComponent>
448
+ )}
351
449
  </View>
352
450
  </View>
353
451
  );
@@ -15,8 +15,13 @@ import {
15
15
  Button,
16
16
  } from "@applicaster/quick-brick-mobile-transport-controls";
17
17
 
18
- import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
19
18
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
19
+ import {
20
+ useModalAnimationContext,
21
+ AnimationComponent,
22
+ ComponentAnimationType,
23
+ PlayerAnimationStateEnum,
24
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
20
25
 
21
26
  type Dimensions = {
22
27
  width: number;
@@ -104,14 +109,26 @@ const backgroundColorStyle = (value: GetValue): ViewStyle => ({
104
109
  backgroundColor: value("docked_player_background_color"),
105
110
  });
106
111
 
107
- const playerDimensionsStyle = ({
108
- height,
109
- }: PlayerDimensionsProp): ViewStyle => ({
110
- paddingLeft: height || 100,
112
+ const playerDimensionsStyle = (
113
+ minimisedHeight: number,
114
+ addPadding: boolean
115
+ ): ViewStyle => ({
116
+ paddingLeft: addPadding ? minimisedHeight || 100 : 0,
111
117
  });
112
118
 
113
- const backgroundImageStyle = (dimensions: Dimensions): ViewStyle => ({
114
- height: dimensions.height,
119
+ const animatedContainerStyle = (docked: boolean): ViewStyle => ({
120
+ top: docked ? 2 : null,
121
+ position: "absolute",
122
+ width: "100%",
123
+ zIndex: docked ? 1 : -1,
124
+ });
125
+
126
+ const backgroundImageStyle = (
127
+ dimensions: Dimensions,
128
+ minimisedHeight: number,
129
+ isActiveGesture: boolean
130
+ ): ViewStyle => ({
131
+ height: isActiveGesture ? minimisedHeight : dimensions.height,
115
132
  aspectRatio: 1,
116
133
  position: "absolute",
117
134
  });
@@ -193,7 +210,6 @@ export default function DockedLayout(props: LayoutProps) {
193
210
  dimensions,
194
211
  entry,
195
212
  onCloseVideoModal,
196
- maximiseVideoModal,
197
213
  onPlayerSeek,
198
214
  playerState,
199
215
  configuration,
@@ -207,9 +223,29 @@ export default function DockedLayout(props: LayoutProps) {
207
223
  const visible = true;
208
224
  const liveButton = Boolean(value("live_badge_enabled"));
209
225
 
226
+ const {
227
+ isActiveGesture,
228
+ setPlayerAnimationState,
229
+ startComponentsAnimation,
230
+ minimisedHeight,
231
+ } = useModalAnimationContext();
232
+
233
+ const maximazeVideoModal = React.useCallback(
234
+ () => setPlayerAnimationState(PlayerAnimationStateEnum.maximaze),
235
+ []
236
+ );
237
+
238
+ if (!layoutState.docked && !startComponentsAnimation) return null;
239
+
210
240
  return (
211
- <View style={styles.container}>
212
- {!playerState.isLive && (
241
+ <AnimationComponent
242
+ animationType={ComponentAnimationType.componentAppears}
243
+ style={[
244
+ styles.container,
245
+ animatedContainerStyle(isActiveGesture || layoutState.docked),
246
+ ]}
247
+ >
248
+ {!playerState.isLive && layoutState.docked && (
213
249
  <ProgressBar
214
250
  content={entry}
215
251
  value={value}
@@ -217,18 +253,27 @@ export default function DockedLayout(props: LayoutProps) {
217
253
  layoutState={layoutState}
218
254
  onPlayerSeek={onPlayerSeek}
219
255
  playerState={playerState}
256
+ docked
220
257
  />
221
258
  )}
222
259
 
223
260
  <View style={styles.content}>
224
- <PlayerImageBackground
225
- configuration={configuration}
226
- entry={entry}
227
- style={backgroundImageStyle(dimensions)}
261
+ <View
262
+ style={backgroundImageStyle(
263
+ dimensions,
264
+ minimisedHeight,
265
+ isActiveGesture
266
+ )}
228
267
  />
229
- <TouchableWithoutFeedback onPress={maximiseVideoModal}>
268
+ <TouchableWithoutFeedback onPress={maximazeVideoModal}>
230
269
  <View
231
- style={[styles.touchableArea, playerDimensionsStyle(dimensions)]}
270
+ style={[
271
+ styles.touchableArea,
272
+ playerDimensionsStyle(
273
+ minimisedHeight,
274
+ isActiveGesture || layoutState.docked
275
+ ),
276
+ ]}
232
277
  >
233
278
  <View
234
279
  style={[
@@ -272,6 +317,6 @@ export default function DockedLayout(props: LayoutProps) {
272
317
  />
273
318
  </View>
274
319
  </View>
275
- </View>
320
+ </AnimationComponent>
276
321
  );
277
322
  }
@@ -19,6 +19,8 @@ const playbackButtonMargins = (
19
19
  ): number =>
20
20
  isLiveShowFullControls ? (isTablet ? 65 : 10) : isTablet ? 95 : 40;
21
21
 
22
+ export const PADDING_HORIZONTAL = 15;
23
+
22
24
  export const playbackButton = ({
23
25
  playerState,
24
26
  liveButton,
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
2
  import DefaultLayout from "./DefaultLayout";
3
- import DockedLayout from "./DockedLayout";
4
3
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
5
4
 
6
5
  type Dimensions = {
@@ -40,6 +39,7 @@ type LayoutProps = {
40
39
  onSkipLive: () => void;
41
40
  maximiseVideoModal: () => void;
42
41
  dimensions: Dimensions;
42
+ defaultDimensions: Dimensions;
43
43
  playNextData: PlayNextData;
44
44
  title: string | number;
45
45
  summary: string | number;
@@ -48,11 +48,5 @@ type LayoutProps = {
48
48
  export default function Layout(
49
49
  props: LayoutProps
50
50
  ): React.ReactElement<LayoutProps> {
51
- const { layoutState } = props;
52
-
53
- if (layoutState.docked) {
54
- return <DockedLayout {...props} />;
55
- }
56
-
57
51
  return <DefaultLayout {...props} />;
58
52
  }
@@ -1,7 +1,6 @@
1
- // Disabling for now as lint can't recognise typescript definition here
2
1
  /* eslint-disable react/prop-types */
3
2
  import * as React from "react";
4
- import { StyleSheet, View } from "react-native";
3
+ import { Animated, StyleSheet, View } from "react-native";
5
4
  import LinearGradient from "../LinearGradient";
6
5
  import { getDominantColor, getPalettes } from "../../../utils/utils";
7
6
  import { useEffect } from "react";
@@ -11,7 +10,7 @@ type MagicBGProps = {
11
10
  image: string;
12
11
  fallbackUrl?: string;
13
12
  defaultColor: string;
14
- colors: string[]; // Updated to type string[] as it holds hex values
13
+ colors: string[];
15
14
  };
16
15
 
17
16
  const defaultColors = {
@@ -66,31 +65,46 @@ export const MagicBG: React.FC<MagicBGProps> = ({
66
65
  defaultColors.defaultGradientEnd,
67
66
  ]);
68
67
 
68
+ const [backgroundColorAnim] = React.useState(new Animated.Value(0)); // from 0 to 1
69
+ const [gradientOpacityAnim] = React.useState(new Animated.Value(0)); // from 0 to 1
70
+
69
71
  useEffect(() => {
72
+ const animateColors = () => {
73
+ Animated.timing(backgroundColorAnim, {
74
+ toValue: 1,
75
+ duration: 500,
76
+ useNativeDriver: false,
77
+ }).start();
78
+
79
+ Animated.timing(gradientOpacityAnim, {
80
+ toValue: 1,
81
+ duration: 500,
82
+ useNativeDriver: false,
83
+ }).start();
84
+ };
85
+
70
86
  const fetchColors = async () => {
71
- // Check if colors are provided and not empty
72
87
  if (colors && colors.length > 0) {
73
- // Use provided colors for gradient and dominant color
74
- setGradientColors(colors.slice(0, 2)); // First 2 colors for gradient
75
- setDominantColor(colors[0]); // First color as dominant
88
+ setGradientColors(colors.slice(0, 2));
89
+ setDominantColor(colors[0]);
90
+ animateColors();
76
91
  } else if (image) {
77
- // If no colors, try fetching from image
78
92
  try {
79
93
  const { gradientColors, dominantColor } =
80
94
  await fetchPaletteAndDominantColor(image);
81
95
 
82
96
  setGradientColors(gradientColors);
83
97
  setDominantColor(dominantColor);
98
+ animateColors();
84
99
  } catch (error) {
85
- // If image fetch fails, try fallback URL
86
100
  try {
87
101
  const { gradientColors, dominantColor } =
88
102
  await fetchPaletteAndDominantColor(fallbackUrl);
89
103
 
90
104
  setGradientColors(gradientColors);
91
105
  setDominantColor(dominantColor);
106
+ animateColors();
92
107
  } catch {
93
- // If everything fails, use default color
94
108
  setGradientColors([
95
109
  defaultColors.defaultGradientStart,
96
110
  defaultColors.defaultGradientEnd,
@@ -100,7 +114,6 @@ export const MagicBG: React.FC<MagicBGProps> = ({
100
114
  }
101
115
  }
102
116
  } else {
103
- // If no image or colors, use default color
104
117
  setGradientColors([
105
118
  defaultColors.defaultGradientStart,
106
119
  defaultColors.defaultGradientEnd,
@@ -111,13 +124,32 @@ export const MagicBG: React.FC<MagicBGProps> = ({
111
124
  };
112
125
 
113
126
  fetchColors();
114
- }, [image, fallbackUrl, colors, defaultColor]);
127
+ }, []);
128
+
129
+ const interpolatedBackgroundColor = backgroundColorAnim.interpolate({
130
+ inputRange: [0, 1],
131
+ outputRange: ["black", dominantColor],
132
+ });
115
133
 
116
134
  return (
117
- <View style={[styles.container, { backgroundColor: dominantColor }]}>
135
+ <Animated.View
136
+ style={[
137
+ styles.container,
138
+ { backgroundColor: interpolatedBackgroundColor },
139
+ ]}
140
+ >
118
141
  <View style={styles.fullScreenOverlay} />
119
- <LinearGradient colors={gradientColors} style={styles.gradientOverlay} />
142
+
143
+ <Animated.View
144
+ style={[styles.gradientOverlay, { opacity: gradientOpacityAnim }]}
145
+ >
146
+ <LinearGradient
147
+ colors={gradientColors}
148
+ style={StyleSheet.absoluteFill}
149
+ />
150
+ </Animated.View>
151
+
120
152
  {children}
121
- </View>
153
+ </Animated.View>
122
154
  );
123
155
  };
@@ -1,26 +1,5 @@
1
1
  import queryString from "query-string";
2
-
3
- export const toTypeString = (obj) => {
4
- switch (typeof obj) {
5
- case "object":
6
- return obj instanceof Date ? obj.toISOString() : JSON.stringify(obj); // object, null
7
- case "undefined":
8
- return "";
9
- default:
10
- // boolean, number, string
11
- return obj.toString();
12
- }
13
- };
14
-
15
- export const stringsOnlyObject = (obj) => {
16
- const strObj = {};
17
-
18
- Object.keys(obj).forEach((x) => {
19
- strObj[x] = toTypeString(obj[x]);
20
- });
21
-
22
- return strObj;
23
- };
2
+ import { dayjs } from "@applicaster/zapp-react-native-utils/dateUtils";
24
3
 
25
4
  export const getLiveUrl = (url: string) => {
26
5
  // Use the replace method with a regular expression to replace all instances of subClass with livePage
@@ -42,3 +21,11 @@ export const addQueryParam = (
42
21
 
43
22
  return `${urlWithoutQuery}?${updatedQueryString}`;
44
23
  };
24
+
25
+ export const isValidDate = (dateString: string | undefined): boolean => {
26
+ return (
27
+ typeof dateString === "string" &&
28
+ dateString.trim() !== "" &&
29
+ dayjs.utc(dateString).isValid()
30
+ );
31
+ };
@@ -4,7 +4,7 @@
4
4
  import React from "react";
5
5
  import * as R from "ramda";
6
6
 
7
- import { findNodeHandle, NativeModules, StyleSheet, View } from "react-native";
7
+ import { findNodeHandle, StyleSheet, View } from "react-native";
8
8
  import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
9
9
  import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
10
10
 
@@ -38,15 +38,11 @@ import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/c
38
38
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
39
39
  import { getTargetScreenDataFromEntry } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData";
40
40
  import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
41
-
42
- const RNPlayerManager = {
43
- QuickBrickPlayerPlugin: isApplePlatform()
44
- ? NativeModules.PlayerModule
45
- : NativeModules.QuickBrickDefaultPlayerModule,
46
- KalturaPlayerPlugin: isApplePlatform()
47
- ? NativeModules.KalturaPlayerModule
48
- : NativeModules.QBKalturaPlayerModule,
49
- };
41
+ import {
42
+ AnimationComponent,
43
+ ComponentAnimationType,
44
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
45
+ import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
50
46
 
51
47
  const styles = StyleSheet.create({
52
48
  container: { flex: 1 },
@@ -57,6 +53,7 @@ const styles = StyleSheet.create({
57
53
  position: "absolute",
58
54
  zIndex: 1,
59
55
  },
56
+ playerSize: { width: "100%", height: "100%" },
60
57
  });
61
58
 
62
59
  const logger = createLogger({
@@ -115,8 +112,6 @@ export default class VideoPlayer extends React.Component<
115
112
  }
116
113
 
117
114
  componentWillUnmount() {
118
- this.stopBackgroundPlayback();
119
-
120
115
  this.playbackStarted = false;
121
116
  this.handleAnalyticEvent(GENERAL_EVENT.session_end);
122
117
  this.onPlayerClosed();
@@ -161,6 +156,11 @@ export default class VideoPlayer extends React.Component<
161
156
  }
162
157
  }
163
158
 
159
+ // For close button
160
+ public closeNativePlayer = () => {
161
+ this.stopBackgroundPlayback();
162
+ };
163
+
164
164
  getPlayNextData = () => this.props.playNextData as PlayNextData;
165
165
 
166
166
  /**
@@ -339,7 +339,7 @@ export default class VideoPlayer extends React.Component<
339
339
  * Sets either subtitle language, or audio language if we have a selected a new locale.
340
340
  * If no locale is set it defaults to system language for audio, and leaves subtitles unaltered.
341
341
  */
342
- setLanguage = (selected = null) => {
342
+ selectTrack = (selected = null) => {
343
343
  const { trackType, id, language } = selected || {};
344
344
 
345
345
  const isOff = language === "Off";
@@ -780,7 +780,6 @@ export default class VideoPlayer extends React.Component<
780
780
 
781
781
  render() {
782
782
  const {
783
- castAction,
784
783
  docked,
785
784
  entry,
786
785
  fullscreen,
@@ -790,8 +789,13 @@ export default class VideoPlayer extends React.Component<
790
789
  isTabletPortrait,
791
790
  muted,
792
791
  style,
792
+ castAction,
793
+ controller,
794
+ startComponentsAnimation,
793
795
  } = this.props;
794
796
 
797
+ const supportNativeCast = controller.supportNativeCast();
798
+
795
799
  const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
796
800
  this.state;
797
801
 
@@ -856,7 +860,11 @@ export default class VideoPlayer extends React.Component<
856
860
  ? supportsNativeControls && !isInlineTV && !this.getPlayNextData()
857
861
  : supportsNativeControls && !this.getPlayNextData();
858
862
 
859
- const shouldUseNativeControls = controls && !docked;
863
+ const shouldUseNativeControls =
864
+ controls &&
865
+ !startComponentsAnimation &&
866
+ !docked &&
867
+ !castAction?.state?.connected;
860
868
 
861
869
  platformSpecificProps = {
862
870
  ...platformSpecificProps,
@@ -1027,6 +1035,7 @@ export default class VideoPlayer extends React.Component<
1027
1035
  extraInfoText={castAction?.state?.friendlyStatus}
1028
1036
  controlsAlwaysVisible={castAction?.state?.connected}
1029
1037
  playNextData={this.getPlayNextData()}
1038
+ nativeControls={!!controls}
1030
1039
  />
1031
1040
  ) : null;
1032
1041
  };
@@ -1061,29 +1070,14 @@ export default class VideoPlayer extends React.Component<
1061
1070
  const renderMobilePlayer = () => {
1062
1071
  return isFullScreenAudioPlayer && isAudioItem ? (
1063
1072
  <View style={containerStyle}>
1064
- {!castAction?.state?.connected && (
1073
+ {(!castAction?.state?.connected || supportNativeCast) && (
1065
1074
  <this.props.PlayerComponent
1066
1075
  ref={this._assignRoot}
1067
1076
  {...nativeEvents}
1068
1077
  {...nativeProps}
1069
1078
  />
1070
1079
  )}
1071
- <AudioPlayerWrapper {...sharedWrapperViewProps}>
1072
- <View>
1073
- {
1074
- <PlayNextOverlay
1075
- item={entry}
1076
- playerIsDocked={docked}
1077
- pluginConfiguration={this.getPluginConfiguration()}
1078
- playerId={this.props.playerId}
1079
- playNextData={this.getPlayNextData()}
1080
- setNextVideoPreloadThresholdPercentage={
1081
- this.props.setNextVideoPreloadThresholdPercentage
1082
- }
1083
- />
1084
- }
1085
- </View>
1086
- </AudioPlayerWrapper>
1080
+ <AudioPlayerWrapper {...sharedWrapperViewProps} />
1087
1081
  </View>
1088
1082
  ) : (
1089
1083
  <PlayerWrapper {...sharedWrapperViewProps}>
@@ -1098,11 +1092,24 @@ export default class VideoPlayer extends React.Component<
1098
1092
  <View style={styles.container}>
1099
1093
  <View style={[playerContainerStyles, playerDimensions]}>
1100
1094
  {isAudioItem && (
1101
- <AudioPlayer
1102
- style={playerDimensions}
1103
- audio_item={entry}
1104
- plugin_configuration={this.getPluginConfiguration()}
1105
- />
1095
+ <AnimationComponent
1096
+ animationType={ComponentAnimationType.player}
1097
+ additionalData={{
1098
+ ...playerDimensions,
1099
+ inlineAudioPlayer: true,
1100
+ }}
1101
+ style={getStyle(playerDimensions)}
1102
+ >
1103
+ <AudioPlayer
1104
+ style={
1105
+ isTV()
1106
+ ? getStyle(playerDimensions)
1107
+ : styles.playerSize
1108
+ }
1109
+ audio_item={entry}
1110
+ plugin_configuration={this.getPluginConfiguration()}
1111
+ />
1112
+ </AnimationComponent>
1106
1113
  )}
1107
1114
 
1108
1115
  {shouldRenderBackground && (
@@ -1114,18 +1121,28 @@ export default class VideoPlayer extends React.Component<
1114
1121
  />
1115
1122
  )}
1116
1123
 
1117
- {!castAction?.state?.connected && (
1118
- <this.props.PlayerComponent
1119
- ref={this._assignRoot}
1120
- {...nativeEvents}
1121
- {...nativeProps}
1122
- listener={this.props.listener}
1124
+ {(!castAction?.state?.connected || supportNativeCast) && (
1125
+ <AnimationComponent
1126
+ animationType={ComponentAnimationType.player}
1127
+ additionalData={playerDimensions}
1123
1128
  style={getStyle(playerDimensions)}
1124
- pluginConfiguration={this.getPluginConfiguration()}
1125
- controller={this.props.controller}
1126
1129
  >
1127
- {this.childrenWithPlayNext()}
1128
- </this.props.PlayerComponent>
1130
+ <this.props.PlayerComponent
1131
+ ref={this._assignRoot}
1132
+ {...nativeEvents}
1133
+ {...nativeProps}
1134
+ listener={this.props.listener}
1135
+ style={
1136
+ isTV()
1137
+ ? getStyle(playerDimensions)
1138
+ : styles.playerSize
1139
+ }
1140
+ pluginConfiguration={this.getPluginConfiguration()}
1141
+ controller={this.props.controller}
1142
+ >
1143
+ {this.childrenWithPlayNext()}
1144
+ </this.props.PlayerComponent>
1145
+ </AnimationComponent>
1129
1146
  )}
1130
1147
  </View>
1131
1148