@applicaster/quick-brick-player 12.0.0-alpha.3788876021 → 12.0.0-alpha.3823240084

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.3788876021",
3
+ "version": "12.0.0-alpha.3823240084",
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-alpha.3788876021",
40
- "@applicaster/quick-brick-tv-transport-controls": "11.0.0-rc.133",
41
- "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.3788876021",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.3788876021",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.3788876021",
39
+ "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.106",
40
+ "@applicaster/quick-brick-tv-transport-controls": "12.0.0-rc.109",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.3823240084",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.3823240084",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.3823240084",
44
44
  "keymirror": "^0.1.1",
45
45
  "query-string": "7.1.3",
46
46
  "react-native-web-linear-gradient": "1.1.2",
@@ -10,66 +10,76 @@ type Asset = string | undefined;
10
10
  const isCurrentPlayer = (playerId) =>
11
11
  LiveImageManager.instance.getCurrentlyPlaying()?.playerId === playerId;
12
12
 
13
+ const liveImageButtonsListenerKey = "live-image-buttons";
14
+
13
15
  const usePlayPauseListeners = (playerId, player, asset, setImageUri) =>
14
16
  useEffect(() => {
15
17
  if (playerId && player) {
16
- if (player.playerState.isReadyToPlay) {
17
- if (player.playerState.isPaused) {
18
- setImageUri(asset.active);
19
- } else {
20
- setImageUri(asset.inactive);
21
- }
18
+ setImageUri(null);
19
+
20
+ return;
21
+ }
22
+
23
+ if (player.playerState.isReadyToPlay) {
24
+ if (player.playerState.isPaused) {
25
+ setImageUri(asset.active);
26
+ } else {
27
+ setImageUri(asset.inactive);
22
28
  }
29
+ }
23
30
 
24
- return player.addListener({
25
- id: "live-image-buttons",
26
- listener: {
27
- onPlayerResume: () => {
28
- setImageUri(asset.inactive);
29
- },
30
- onPlayerPause: () => {
31
- setImageUri(asset.active);
32
- },
33
- onPlayerClose: () => {
34
- setImageUri(null);
35
- },
36
- onLoad() {
37
- setImageUri(asset.inactive);
38
- },
31
+ return player.addListener({
32
+ id: liveImageButtonsListenerKey,
33
+ listener: {
34
+ onPlayerResume: () => {
35
+ setImageUri(asset.inactive);
39
36
  },
40
- });
41
- }
37
+ onPlayerPause: () => {
38
+ setImageUri(asset.active);
39
+ },
40
+ onPlayerClose: () => {
41
+ setImageUri(null);
42
+ },
43
+ onLoad() {
44
+ setImageUri(asset.inactive);
45
+ },
46
+ },
47
+ });
42
48
  }, [player, playerId, asset]);
43
49
 
44
50
  const useMuteUnmuteListeners = (playerId, player, asset, setImageUri) =>
45
51
  useEffect(() => {
46
- if (playerId && player) {
47
- // active action = muted, inactive = unmuted
48
- return player.addListener({
49
- id: "live-image-buttons",
50
- listener: {
51
- onPlayerMute: () => {
52
- if (isCurrentPlayer(playerId)) {
53
- setImageUri(asset.active);
54
- }
55
- },
56
- onPlayerUnmute: () => {
57
- if (isCurrentPlayer(playerId)) {
58
- setImageUri(asset.inactive);
59
- }
60
- },
61
- onLoad() {
62
- setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
63
- },
64
- onPlayerClose: () => {
65
- setImageUri(null);
66
- },
67
- onVideoEnd: () => {
68
- setImageUri(null);
69
- },
70
- },
71
- });
52
+ if (!player || !playerId) {
53
+ setImageUri(null);
54
+
55
+ return;
72
56
  }
57
+
58
+ // active action = muted, inactive = unmuted
59
+ return player.addListener({
60
+ id: liveImageButtonsListenerKey,
61
+ listener: {
62
+ onPlayerMute: () => {
63
+ if (isCurrentPlayer(playerId)) {
64
+ setImageUri(asset.active);
65
+ }
66
+ },
67
+ onPlayerUnmute: () => {
68
+ if (isCurrentPlayer(playerId)) {
69
+ setImageUri(asset.inactive);
70
+ }
71
+ },
72
+ onLoad() {
73
+ setImageUri(player?.getIsMuted() ? asset.active : asset.inactive);
74
+ },
75
+ onPlayerClose: () => {
76
+ setImageUri(null);
77
+ },
78
+ onVideoEnd: () => {
79
+ setImageUri(null);
80
+ },
81
+ },
82
+ });
73
83
  }, [player, playerId, asset]);
74
84
 
75
85
  const useListeners = MUTED_USAGE
@@ -21,7 +21,7 @@ const playActionCallback = (player) => {
21
21
  }
22
22
  };
23
23
 
24
- const invokeAction = () => {
24
+ const invokeAction = (_item: ZappEntry, _object) => {
25
25
  const actionCallback = MUTED_USAGE ? muteActionCallback : playActionCallback;
26
26
  // We only allow one player to be present at a time
27
27
  const player = LiveImageManager.instance.getCurrentlyPlaying()?.player;
@@ -29,6 +29,54 @@ const invokeAction = () => {
29
29
  actionCallback(player);
30
30
  };
31
31
 
32
+ // Not exported because currently the is no way
33
+ // to subscribe to the action availability change.
34
+ // We currently return a null state to disable the action.
35
+ // eslint-disable-next-line unused-imports/no-unused-vars
36
+ const isActionAvailable = (item: ZappEntry) => {
37
+ return (
38
+ LiveImageManager.instance.getCurrentlyPlaying()?.getPlayer().entry.id ===
39
+ item.id
40
+ );
41
+ };
42
+
43
+ const addListener = (
44
+ entryId: string,
45
+ listener: CellActionStateListenerFunction
46
+ ) => {
47
+ const callback = (event: Record<string, any>) => {
48
+ if (event.entry?.id !== entryId) {
49
+ return;
50
+ }
51
+
52
+ const player = LiveImageManager.instance.getCurrentlyPlaying()?.getPlayer();
53
+
54
+ if (!player || player.entry?.id !== entryId) {
55
+ listener(null);
56
+
57
+ return;
58
+ }
59
+
60
+ const actionState = MUTED_USAGE ? player.getIsMuted() : player.isPaused();
61
+
62
+ listener({
63
+ state: actionState ? 1 : 0,
64
+ label: "",
65
+ asset: AssetComponent,
66
+ });
67
+ };
68
+
69
+ return LiveImageManager.instance.addListener({
70
+ id: `cell-player-${entryId}`,
71
+ listener: {
72
+ onEnd: callback,
73
+ onStart: callback,
74
+ onError: callback,
75
+ onDestroy: callback,
76
+ },
77
+ });
78
+ };
79
+
32
80
  const emptyState = [];
33
81
 
34
82
  const initialEntryState = () => ({
@@ -46,6 +94,7 @@ export function ContextProvider(Component: ReactComponent<any>) {
46
94
  state: emptyState,
47
95
  invokeAction,
48
96
  initialEntryState,
97
+ addListener,
49
98
  }),
50
99
  [invokeAction, initialEntryState]
51
100
  );
@@ -32,6 +32,7 @@ import {
32
32
  AnimatedScrollModal,
33
33
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
34
34
  import { PADDING_HORIZONTAL } from "./Layout/defaultLayoutsConts";
35
+ import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
35
36
 
36
37
  const { log_debug } = createLogger({
37
38
  subsystem: "UIComponent",
@@ -294,7 +295,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
294
295
  playerState.isPaused ? player?.play() : player.pause();
295
296
  }, [playerState, player]);
296
297
 
297
- const seekDuration = Number(value("seek_duration"));
298
+ const seekDuration = toNumberWithDefault(10, value("seek_duration"));
298
299
 
299
300
  const onRewindButtonPress = React.useCallback(() => {
300
301
  player?.rewind(seekDuration);
@@ -387,6 +388,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
387
388
  <AnimationComponent
388
389
  animationType={ComponentAnimationType.moveUpComponent}
389
390
  additionalData={{ saveArea: true }}
391
+ style={Styles.playerContainer}
390
392
  >
391
393
  <AnimatedScrollModal>
392
394
  <View style={Styles.playerContainer} onLayout={onLayout}>
@@ -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,
@@ -33,6 +34,7 @@ import {
33
34
  ComponentAnimationType,
34
35
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
35
36
  import DockedLayout from "./DockedLayout";
37
+ import { isValidDate } from "../../Utils";
36
38
 
37
39
  type Dimensions = {
38
40
  width: number;
@@ -79,7 +81,7 @@ type LayoutProps = {
79
81
 
80
82
  const Styles = {
81
83
  container: { flex: 1 },
82
- tabletContainer: (isPortraitMode) => ({
84
+ tabletContainer: (isPortraitMode: boolean) => ({
83
85
  flex: 1,
84
86
  alignItems: "center",
85
87
  justifyContent: isPortraitMode ? "center" : "flex-start",
@@ -109,10 +111,10 @@ const Styles = {
109
111
  };
110
112
  },
111
113
  backgroundImageContainerImageStyle: (
112
- isTablet,
113
- docked,
114
- startComponentsAnimation,
115
- isActiveGesture
114
+ isTablet: boolean,
115
+ docked: boolean,
116
+ startComponentsAnimation: boolean,
117
+ isActiveGesture: boolean
116
118
  ) => ({
117
119
  borderRadius:
118
120
  (startComponentsAnimation && isActiveGesture) || docked
@@ -190,8 +192,31 @@ export default function DefaultLayout(props: LayoutProps) {
190
192
 
191
193
  let showFullControls = !controller?.isAd();
192
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
+
193
217
  if (isLiveContent) {
194
218
  const seekEnabled = Boolean(value("liveSeekingEnabled"));
219
+
195
220
  showFullControls = !!(controller?.hasSeekableDuration?.() && seekEnabled);
196
221
  }
197
222
 
@@ -221,12 +246,14 @@ export default function DefaultLayout(props: LayoutProps) {
221
246
 
222
247
  const rewind = rewindButton({
223
248
  isTablet,
224
- onPress: controller?.hasContentDuration?.() ? onRewindButtonPress : null,
249
+ onPress: controller?.hasSeekableDuration?.() ? onRewindButtonPress : null,
225
250
  });
226
251
 
227
252
  const forward = forwardButton({
228
253
  isTablet,
229
- onPress: controller?.hasContentDuration?.() ? onForwardButtonPress : null,
254
+ onPress: controller?.hasSeekableDuration?.()
255
+ ? onForwardButtonPress
256
+ : null,
230
257
  });
231
258
 
232
259
  const speed = speedButton({
@@ -255,7 +282,6 @@ export default function DefaultLayout(props: LayoutProps) {
255
282
  return [skipToStart, rewind, play, forward, skipToLive];
256
283
  }
257
284
 
258
- // Temporary empty button until we add sleep button. It will ensure the buttons will not break in their position
259
285
  return [speed, rewind, play, forward, sleep];
260
286
  }, [
261
287
  playerState,
@@ -276,15 +302,12 @@ export default function DefaultLayout(props: LayoutProps) {
276
302
 
277
303
  useEffect(() => {
278
304
  if (showCounter && sleepTimerDate !== 0) {
279
- // Setting the initial timeLeft based on sleepTimerDate
280
305
  setTimeLeft(sleepTimerDate - Date.now());
281
306
 
282
- // Setting up an interval to update the remaining time every second
283
307
  const intervalId = setInterval(() => {
284
308
  setTimeLeft(() => {
285
309
  const newTimeLeft = sleepTimerDate - Date.now();
286
310
 
287
- // If the new remaining time is less than or equal to 0, clear the interval and trigger the cancelSleepTimer function
288
311
  if (newTimeLeft <= 0) {
289
312
  clearInterval(intervalId);
290
313
 
@@ -292,36 +315,24 @@ export default function DefaultLayout(props: LayoutProps) {
292
315
 
293
316
  return 0;
294
317
  } else {
295
- // Otherwise, update the state with the new remaining time
296
318
  return newTimeLeft;
297
319
  }
298
320
  });
299
321
  }, 1000);
300
322
 
301
- // Clearing the interval when the component unmounts or dependencies changea
302
323
  return () => clearInterval(intervalId);
303
324
  } else {
304
- // Ensure the timeLeft state is set correctly if showCounter is false
305
325
  setTimeLeft(0);
306
326
  }
307
327
  }, [sleepTimerDate, showCounter, cancelSleepTimer]);
308
328
 
309
329
  const Separator = () => <View style={Styles.separator} />;
310
330
 
311
- const liveSeekingEnabled = value("liveSeekingEnabled");
312
-
313
- const startTime = entry.extensions.start_time; // October 28, 2024, at 14:27 UTC
314
- const endTime = entry.extensions.end_time; // October 28, 2024, at 16:27 UTC
315
-
316
- const needsToShowProgressBar =
317
- (isLiveContent && (liveSeekingEnabled || (startTime && endTime))) ||
318
- !isLiveContent;
319
-
320
331
  const defaultImageDimensions = React.useMemo(
321
332
  () =>
322
333
  defaultDimensions &&
323
334
  Styles.backgroundImageContainer(defaultDimensions, isTablet),
324
- [defaultDimensions]
335
+ [defaultDimensions, isTablet]
325
336
  );
326
337
 
327
338
  const { isActiveGesture, startComponentsAnimation } =
@@ -345,7 +356,7 @@ export default function DefaultLayout(props: LayoutProps) {
345
356
  </AnimationComponent>
346
357
  <AnimationComponent
347
358
  animationType={ComponentAnimationType.moveUpComponent}
348
- style={isTablet ? Styles.playerImageWrapper : null}
359
+ style={isTablet ? Styles.playerImageWrapper : undefined}
349
360
  >
350
361
  <AnimationComponent
351
362
  animationType={ComponentAnimationType.moveComponentHorizontal}
@@ -360,7 +371,7 @@ export default function DefaultLayout(props: LayoutProps) {
360
371
  }
361
372
  : null,
362
373
  }}
363
- style={isTablet ? Styles.alignItemsCenter : null}
374
+ style={isTablet ? Styles.alignItemsCenter : undefined}
364
375
  >
365
376
  <PlayerImageBackground
366
377
  entry={entry}
@@ -380,7 +391,7 @@ export default function DefaultLayout(props: LayoutProps) {
380
391
  <View
381
392
  style={[
382
393
  Styles.paddingHorizontal,
383
- isTablet ? Styles.tabletLayout : null,
394
+ isTablet ? Styles.tabletLayout : undefined,
384
395
  ]}
385
396
  >
386
397
  {!docked && (
@@ -405,9 +416,10 @@ export default function DefaultLayout(props: LayoutProps) {
405
416
  playerState={playerState}
406
417
  disableFullscreen={true}
407
418
  sliderStyle={2}
408
- startTime={startTime}
409
- endTime={endTime}
410
- isLive={isLiveContent}
419
+ startTimestamp={startTimestamp}
420
+ endTimestamp={endTimestamp}
421
+ isDateBasedLiveContent={isDateBasedLiveContent}
422
+ onSkipLive={onSkipLive}
411
423
  />
412
424
 
413
425
  {showCounter && timeLeft >= 0 && (
@@ -117,7 +117,6 @@ const playerDimensionsStyle = (
117
117
  });
118
118
 
119
119
  const animatedContainerStyle = (docked: boolean): ViewStyle => ({
120
- top: docked ? 2 : null,
121
120
  position: "absolute",
122
121
  width: "100%",
123
122
  zIndex: docked ? 1 : -1,
@@ -245,17 +244,19 @@ export default function DockedLayout(props: LayoutProps) {
245
244
  animatedContainerStyle(isActiveGesture || layoutState.docked),
246
245
  ]}
247
246
  >
248
- {!playerState.isLive && layoutState.docked && (
249
- <ProgressBar
250
- content={entry}
251
- value={value}
252
- visible={visible}
253
- layoutState={layoutState}
254
- onPlayerSeek={onPlayerSeek}
255
- playerState={playerState}
256
- docked
257
- />
258
- )}
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
+ )}
259
260
 
260
261
  <View style={styles.content}>
261
262
  <View
@@ -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,
@@ -113,11 +126,30 @@ export const MagicBG: React.FC<MagicBGProps> = ({
113
126
  fetchColors();
114
127
  }, []);
115
128
 
129
+ const interpolatedBackgroundColor = backgroundColorAnim.interpolate({
130
+ inputRange: [0, 1],
131
+ outputRange: ["black", dominantColor],
132
+ });
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
 
@@ -21,14 +21,8 @@ import { PlayerWrapper } from "@applicaster/zapp-react-native-ui-components/Comp
21
21
 
22
22
  import { PlayNextOverlay } from "./PlayNextOverlay";
23
23
  import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
24
- import { AnalyticsTracker } from "@applicaster/zapp-react-native-utils/analyticsUtils/analyticsTracker";
25
24
 
26
- import {
27
- AD_EVENT,
28
- GENERAL_EVENT,
29
- PLAYBACK_EVENT,
30
- QOS_EVENT,
31
- } from "@applicaster/zapp-react-native-utils/analyticsUtils/events";
25
+ import { GENERAL_EVENT } from "@applicaster/zapp-react-native-utils/analyticsUtils/events";
32
26
  import { Categories, createLogger, Subsystem } from "../logger";
33
27
  import { parseLanguageTracks } from "@applicaster/zapp-react-native-utils/playerUtils/configurationUtils";
34
28
  import { DEFAULT_COLORS, SEEK_TIME } from "../utils/const";
@@ -42,15 +36,10 @@ import {
42
36
  AnimationComponent,
43
37
  ComponentAnimationType,
44
38
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
39
+ import { PlayerAnalyticsManager } from "@applicaster/zapp-react-native-utils/analyticsUtils/PlayerAnalyticsManager";
40
+ import { PlayerAnalyticsTrackerI } from "@applicaster/zapp-react-native-utils/analyticsUtils/playerAnalyticsTracker";
45
41
 
46
- const RNPlayerManager = {
47
- QuickBrickPlayerPlugin: isApplePlatform()
48
- ? NativeModules.PlayerModule
49
- : NativeModules.QuickBrickDefaultPlayerModule,
50
- KalturaPlayerPlugin: isApplePlatform()
51
- ? NativeModules.KalturaPlayerModule
52
- : NativeModules.QBKalturaPlayerModule,
53
- };
42
+ import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
54
43
 
55
44
  const styles = StyleSheet.create({
56
45
  container: { flex: 1 },
@@ -75,7 +64,6 @@ export default class VideoPlayer extends React.Component<
75
64
  > {
76
65
  public static propTypes = {};
77
66
  _root: any;
78
- analyticsTracker: AnalyticsTracker;
79
67
  playerPluginId: string;
80
68
  takeParentStyle: Record<any, any>;
81
69
  playbackStarted = false;
@@ -106,6 +94,17 @@ export default class VideoPlayer extends React.Component<
106
94
  duration: 0,
107
95
  };
108
96
 
97
+ private _tracker: PlayerAnalyticsTrackerI;
98
+ get analyticsTracker() {
99
+ if (!this._tracker) {
100
+ this._tracker = PlayerAnalyticsManager.getInstance().getTracker(
101
+ this.props.playerId
102
+ );
103
+ }
104
+
105
+ return this._tracker;
106
+ }
107
+
109
108
  constructor(props) {
110
109
  super(props);
111
110
  this.onVideoLoadCalled = false; // android
@@ -114,16 +113,13 @@ export default class VideoPlayer extends React.Component<
114
113
  ? { width: "100%", flex: "1 1 auto", objectFit: "contain" }
115
114
  : { flexBasis: "auto", flexGrow: 1, flexShrink: 1 };
116
115
 
117
- const entry = this.props?.entry;
118
- this.analyticsTracker = new AnalyticsTracker(entry);
119
116
  this.playerPluginId = this.props?.playerPluginIdentifier;
120
117
  }
121
118
 
122
119
  componentWillUnmount() {
123
- this.stopBackgroundPlayback();
124
-
125
120
  this.playbackStarted = false;
126
- this.handleAnalyticEvent(GENERAL_EVENT.session_end);
121
+
122
+ this.analyticsTracker.handleAnalyticEvent(GENERAL_EVENT.session_end);
127
123
  this.onPlayerClosed();
128
124
 
129
125
  if (isAndroidPlatform()) {
@@ -166,6 +162,11 @@ export default class VideoPlayer extends React.Component<
166
162
  }
167
163
  }
168
164
 
165
+ // For close button
166
+ public closeNativePlayer = () => {
167
+ this.stopBackgroundPlayback();
168
+ };
169
+
169
170
  getPlayNextData = () => this.props.playNextData as PlayNextData;
170
171
 
171
172
  /**
@@ -179,27 +180,6 @@ export default class VideoPlayer extends React.Component<
179
180
  return this.props.pluginConfiguration;
180
181
  };
181
182
 
182
- /**
183
- * Handles analytic events, generates a new analytics tracker and transmits events
184
- * Passes entry data, and playerState to the tracker for additional context
185
- * The end result is an event formatted and passed to AnalyticsBridge.postEvent
186
- * When we get the synthetic event we need to pull the data in the nativeEvent object
187
- * nativeEvent contains the return value from native
188
- */
189
- handleAnalyticEvent = (event, data = {}) => {
190
- const entry = this.props?.entry;
191
- const playerState = this.getState();
192
-
193
- if (this.analyticsTracker?.handleAnalyticEvent) {
194
- this.analyticsTracker?.handleAnalyticEvent?.(
195
- event,
196
- { ...data, player_type: this.props.playerPluginIdentifier },
197
- entry,
198
- playerState
199
- );
200
- }
201
- };
202
-
203
183
  /**
204
184
  * This method is for the events that have no implementation in react but are being invoked by native
205
185
  * When we get the synthetic event we need to pull the data in the nativeEvent object
@@ -340,11 +320,7 @@ export default class VideoPlayer extends React.Component<
340
320
  }
341
321
  };
342
322
 
343
- /**
344
- * Sets either subtitle language, or audio language if we have a selected a new locale.
345
- * If no locale is set it defaults to system language for audio, and leaves subtitles unaltered.
346
- */
347
- setLanguage = (selected = null) => {
323
+ selectTrack = (selected) => {
348
324
  const { trackType, id, language } = selected || {};
349
325
 
350
326
  const isOff = language === "Off";
@@ -403,28 +379,16 @@ export default class VideoPlayer extends React.Component<
403
379
  };
404
380
 
405
381
  onPlayerPresented = () => {
406
- this.handleAnalyticEvent(GENERAL_EVENT.player_presented);
382
+ this.analyticsTracker?.handleAnalyticEvent(GENERAL_EVENT.player_presented);
407
383
  };
408
384
 
409
385
  onPlayerClosed = () => {
410
- this.handleAnalyticEvent(GENERAL_EVENT.player_closed);
411
- };
412
-
413
- onPlayerSessionEnd = () => {
414
- // TODO: Implement method on native android, ios exists
415
- this.handleAnalyticEvent(GENERAL_EVENT.session_end);
416
- };
417
-
418
- onPlayerSessionStart = () => {
419
- // TODO: Implement method on native android, ios exists
420
-
421
- this.handleAnalyticEvent(GENERAL_EVENT.session_start);
386
+ this.analyticsTracker?.handleAnalyticEvent(GENERAL_EVENT.player_closed);
422
387
  };
423
388
 
424
389
  onVideoEnd = () => {
425
390
  this.setState({ displayOverlay: !this.state.displayOverlay }, () => {
426
391
  if (this.state?.displayOverlay) {
427
- this.handleAnalyticEvent(PLAYBACK_EVENT.complete);
428
392
  this.props?.listener?.onVideoEnd({});
429
393
  }
430
394
  });
@@ -434,15 +398,12 @@ export default class VideoPlayer extends React.Component<
434
398
  onVideoError = ({ nativeEvent }) => {
435
399
  const { message } = nativeEvent;
436
400
 
437
- this.handleAnalyticEvent(PLAYBACK_EVENT.error, nativeEvent);
438
-
439
401
  return this.props?.listener?.onError(new Error(message));
440
402
  };
441
403
 
442
404
  onPlayerPause = ({ nativeEvent = null }) => {
443
405
  this.setState({ paused: true });
444
406
  this.props?.listener?.onPlayerPause(nativeEvent);
445
- this.handleAnalyticEvent(PLAYBACK_EVENT.pause, nativeEvent);
446
407
  };
447
408
 
448
409
  onPlayerResume = ({ nativeEvent = null }) => {
@@ -453,98 +414,61 @@ export default class VideoPlayer extends React.Component<
453
414
 
454
415
  if (isNotAnAd && !resume) {
455
416
  this.setState({ resume: true });
456
- this.handleAnalyticEvent(PLAYBACK_EVENT.play, nativeEvent);
457
417
  }
458
418
  };
459
419
 
460
420
  onPlayerSeekComplete = ({ nativeEvent }) => {
461
421
  this.props?.listener?.onPlayerSeekComplete(nativeEvent);
462
- this.handleAnalyticEvent(PLAYBACK_EVENT.seek_complete, nativeEvent);
463
422
  };
464
423
 
465
424
  onPlayerSeekStart = ({ nativeEvent }) => {
466
425
  this.props?.listener?.onPlayerSeekStart(nativeEvent);
467
- this.handleAnalyticEvent(PLAYBACK_EVENT.seek_start, nativeEvent);
468
426
  };
469
427
 
470
428
  onBufferStart = ({ nativeEvent }) => {
471
429
  this.props?.listener?.onBufferStart(nativeEvent);
472
- this.handleAnalyticEvent(PLAYBACK_EVENT.buffer_start, nativeEvent);
473
430
  };
474
431
 
475
432
  onBufferComplete = ({ nativeEvent }) => {
476
433
  this.props?.listener?.onBufferComplete(nativeEvent);
477
- this.handleAnalyticEvent(PLAYBACK_EVENT.buffer_complete, nativeEvent);
478
- };
479
-
480
- onPlayerNext = ({ nativeEvent }) => {
481
- // TODO: Implement method on native
482
-
483
- this.handleAnalyticEvent(PLAYBACK_EVENT.next, nativeEvent);
484
- };
485
-
486
- onPlayerPrevious = ({ nativeEvent }) => {
487
- // TODO: Implement method on native
488
-
489
- this.handleAnalyticEvent(PLAYBACK_EVENT.previous, nativeEvent);
490
- };
491
-
492
- onPlayerStop = ({ nativeEvent }) => {
493
- // TODO: Implement method on native
494
-
495
- this.handleAnalyticEvent(PLAYBACK_EVENT.stop, nativeEvent);
496
- };
497
-
498
- onVideoBandwidthUpdate = ({ nativeEvent }) => {
499
- // TODO: Implement method on native
500
-
501
- this.handleAnalyticEvent(QOS_EVENT.bitrate_changed, nativeEvent);
502
434
  };
503
435
 
504
436
  onAdBegin = ({ nativeEvent }) => {
505
437
  this.setState({ isAd: true });
506
- this.handleAnalyticEvent(AD_EVENT.ad_start, nativeEvent);
507
438
  this.props.listener?.onAdBegin?.(nativeEvent);
508
439
  };
509
440
 
510
441
  onAdBreakBegin = ({ nativeEvent }) => {
511
442
  this.setState({ isAd: true });
512
- this.handleAnalyticEvent(AD_EVENT.ad_break_start, nativeEvent);
513
443
  this.props.listener?.onAdBreakBegin?.(nativeEvent);
514
444
  };
515
445
 
516
446
  onAdBreakEnd = ({ nativeEvent }) => {
517
447
  this.setState({ isAd: false });
518
- this.handleAnalyticEvent(AD_EVENT.ad_break_completed, nativeEvent);
519
448
  this.props.listener?.onAdBreakEnd?.(nativeEvent);
520
449
  };
521
450
 
522
451
  onAdEnd = ({ nativeEvent }) => {
523
452
  this.setState({ isAd: false });
524
- this.handleAnalyticEvent(AD_EVENT.ad_complete, nativeEvent);
525
453
  this.props.listener?.onAdEnd?.(nativeEvent);
526
454
  };
527
455
 
528
456
  onAdError = ({ nativeEvent }) => {
529
457
  this.setState({ isAd: false });
530
- this.handleAnalyticEvent(AD_EVENT.ad_error, nativeEvent);
531
458
  this.props.listener?.onAdError?.(nativeEvent);
532
459
  };
533
460
 
534
461
  onAdRequest = ({ nativeEvent }) => {
535
- this.handleAnalyticEvent(AD_EVENT.ad_request, nativeEvent);
536
462
  this.props.listener?.onAdRequest?.(nativeEvent);
537
463
  };
538
464
 
539
465
  onAdClicked = ({ nativeEvent }) => {
540
- this.handleAnalyticEvent(AD_EVENT.ad_clicked, nativeEvent);
541
466
  // Broadcast click event
542
467
  this.props.playerEvent("ad-clicked", nativeEvent);
543
468
  this.props.listener?.onAdClicked?.(nativeEvent);
544
469
  };
545
470
 
546
471
  onAdTapped = ({ nativeEvent }) => {
547
- this.handleAnalyticEvent(AD_EVENT.ad_tapped, nativeEvent);
548
472
  // Broadcast click event
549
473
  this.props.playerEvent("ad-tapped", nativeEvent);
550
474
  this.props.listener?.onAdTapped?.(nativeEvent);
@@ -657,10 +581,6 @@ export default class VideoPlayer extends React.Component<
657
581
  }
658
582
 
659
583
  if (!this.state.isAd && !this.onVideoLoadCalled) {
660
- this.handleAnalyticEvent(PLAYBACK_EVENT.load_video, {
661
- player_type: this.props.playerPluginIdentifier,
662
- });
663
-
664
584
  // TODO: Hack, needs to remove
665
585
  this.onVideoLoadCalled = true;
666
586
  }
@@ -785,7 +705,6 @@ export default class VideoPlayer extends React.Component<
785
705
 
786
706
  render() {
787
707
  const {
788
- castAction,
789
708
  docked,
790
709
  entry,
791
710
  fullscreen,
@@ -795,8 +714,13 @@ export default class VideoPlayer extends React.Component<
795
714
  isTabletPortrait,
796
715
  muted,
797
716
  style,
717
+ castAction,
718
+ controller,
719
+ startComponentsAnimation,
798
720
  } = this.props;
799
721
 
722
+ const supportNativeCast = controller.supportNativeCast();
723
+
800
724
  const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
801
725
  this.state;
802
726
 
@@ -858,10 +782,14 @@ export default class VideoPlayer extends React.Component<
858
782
  !!this.props.controller?.supportsNativeControls?.();
859
783
 
860
784
  const controls = isAppleTV()
861
- ? supportsNativeControls && !isInlineTV && !this.getPlayNextData()
785
+ ? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
862
786
  : supportsNativeControls && !this.getPlayNextData();
863
787
 
864
- const shouldUseNativeControls = controls && !docked;
788
+ const shouldUseNativeControls =
789
+ controls &&
790
+ !startComponentsAnimation &&
791
+ !docked &&
792
+ !castAction?.state?.connected;
865
793
 
866
794
  platformSpecificProps = {
867
795
  ...platformSpecificProps,
@@ -920,8 +848,6 @@ export default class VideoPlayer extends React.Component<
920
848
  onPlayerResume: this.onPlayerResume, // play
921
849
  onPlayerSeekComplete: this.onPlayerSeekComplete, // seek_complete
922
850
  onPlayerSeekStart: this.onPlayerSeekStart, // seek_start
923
- onPlayerSessionEnd: this.onPlayerSessionEnd, // session_end
924
- onPlayerSessionStart: this.onPlayerSessionStart, // session_start
925
851
  };
926
852
 
927
853
  const lifecycleEvents = {
@@ -954,7 +880,6 @@ export default class VideoPlayer extends React.Component<
954
880
  onTimedMetadata: this.unHandledEvent("onTimedMetadata"),
955
881
  onTracksChanged: this.onTracksChanged,
956
882
  eventSubtitlesChange: this.unHandledEvent("eventSubtitlesChange"),
957
- onVideoBandwidthUpdate: this.onVideoBandwidthUpdate,
958
883
  };
959
884
 
960
885
  const playerSystemEvents = {
@@ -974,9 +899,6 @@ export default class VideoPlayer extends React.Component<
974
899
  const unsupportedEvents = {
975
900
  onBufferComplete: this.onBufferComplete, // buffer_complete
976
901
  onBufferStart: this.onBufferStart, // buffer_start
977
- onPlayerNext: this.onPlayerNext, // next
978
- onPlayerPrevious: this.onPlayerPrevious, // previous
979
- onPlayerStop: this.onPlayerStop, // stop
980
902
  };
981
903
 
982
904
  const nativeEvents = {
@@ -990,7 +912,13 @@ export default class VideoPlayer extends React.Component<
990
912
  ...androidLifecyclePiPEvents,
991
913
  };
992
914
 
993
- const needToShowPlayNextOverlay = this.getPlayNextData() && !docked;
915
+ const needToShowPlayNextOverlay =
916
+ isTV() && this.getPlayNextData() && !docked;
917
+
918
+ const needToShowDefaultControls = !(
919
+ (controls && !castAction?.state?.connected) ||
920
+ this.getPlayNextData()
921
+ );
994
922
 
995
923
  const playerContainerStyles = isApplePlatform()
996
924
  ? styles.playerContainerBlack
@@ -1032,6 +960,7 @@ export default class VideoPlayer extends React.Component<
1032
960
  extraInfoText={castAction?.state?.friendlyStatus}
1033
961
  controlsAlwaysVisible={castAction?.state?.connected}
1034
962
  playNextData={this.getPlayNextData()}
963
+ needToShowDefaultControls={needToShowDefaultControls}
1035
964
  />
1036
965
  ) : null;
1037
966
  };
@@ -1066,16 +995,14 @@ export default class VideoPlayer extends React.Component<
1066
995
  const renderMobilePlayer = () => {
1067
996
  return isFullScreenAudioPlayer && isAudioItem ? (
1068
997
  <View style={containerStyle}>
1069
- {!castAction?.state?.connected && (
998
+ {(!castAction?.state?.connected || supportNativeCast) && (
1070
999
  <this.props.PlayerComponent
1071
1000
  ref={this._assignRoot}
1072
1001
  {...nativeEvents}
1073
1002
  {...nativeProps}
1074
1003
  />
1075
1004
  )}
1076
- <AudioPlayerWrapper {...sharedWrapperViewProps}>
1077
- <></>
1078
- </AudioPlayerWrapper>
1005
+ <AudioPlayerWrapper {...sharedWrapperViewProps} />
1079
1006
  </View>
1080
1007
  ) : (
1081
1008
  <PlayerWrapper {...sharedWrapperViewProps}>
@@ -1119,7 +1046,7 @@ export default class VideoPlayer extends React.Component<
1119
1046
  />
1120
1047
  )}
1121
1048
 
1122
- {!castAction?.state?.connected && (
1049
+ {(!castAction?.state?.connected || supportNativeCast) && (
1123
1050
  <AnimationComponent
1124
1051
  animationType={ComponentAnimationType.player}
1125
1052
  additionalData={playerDimensions}