@applicaster/zapp-react-native-ui-components 12.0.0-rc.10 → 12.0.0-rc.101

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.
Files changed (150) hide show
  1. package/Components/AudioPlayer/AudioPlayer.tsx +9 -1
  2. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +5 -0
  3. package/Components/AudioPlayer/helpers.tsx +1 -0
  4. package/Components/BaseFocusable/index.tsx +3 -3
  5. package/Components/Cell/Cell.tsx +32 -11
  6. package/Components/Cell/CellWithFocusable.tsx +40 -19
  7. package/Components/Cell/TvOSCellComponent.tsx +5 -9
  8. package/Components/ErrorScreen/index.tsx +1 -1
  9. package/Components/Focusable/Focusable.tsx +6 -1
  10. package/Components/FocusableCell/index.tsx +7 -7
  11. package/Components/FocusableGroup/index.tsx +11 -2
  12. package/Components/FocusableList/FocusableChild.ts +24 -14
  13. package/Components/FocusableList/FocusableItem.tsx +1 -1
  14. package/Components/FocusableList/FocusableListItemWrapper.tsx +76 -0
  15. package/Components/FocusableList/hooks/useCellState.android.ts +3 -1
  16. package/Components/FocusableList/index.tsx +26 -25
  17. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +11 -8
  18. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  19. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  20. package/Components/HandlePlayable/HandlePlayable.tsx +4 -1
  21. package/Components/HookRenderer/index.tsx +1 -6
  22. package/Components/Image/Image.android.tsx +3 -26
  23. package/Components/Image/Image.tsx +18 -2
  24. package/Components/Image/utils/index.ts +39 -0
  25. package/Components/Layout/TV/ScreenContainer.tsx +79 -5
  26. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  27. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
  28. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
  29. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
  30. package/Components/Layout/TV/__tests__/index.test.tsx +20 -108
  31. package/Components/Layout/TV/index.tsx +15 -84
  32. package/Components/Layout/TV/index.web.tsx +33 -0
  33. package/Components/Layout/index.tsx +15 -11
  34. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +4 -1
  35. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +88 -113
  36. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +21 -8
  37. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  38. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
  39. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  40. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  41. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  42. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  43. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +37 -2
  44. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  45. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  46. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  47. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  48. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  49. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  50. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
  51. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +6 -10
  52. package/Components/MasterCell/dataAdapter.ts +21 -7
  53. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  54. package/Components/MasterCell/index.tsx +3 -1
  55. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  56. package/Components/MasterCell/utils/index.ts +33 -35
  57. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  58. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  59. package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
  60. package/Components/ModalComponent/Button/Item.tsx +82 -0
  61. package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
  62. package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
  63. package/Components/ModalComponent/Button/assets.ts +4 -0
  64. package/Components/ModalComponent/Button/index.tsx +111 -0
  65. package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
  66. package/Components/ModalComponent/Header/Title.tsx +70 -0
  67. package/Components/ModalComponent/Header/assets.ts +4 -0
  68. package/Components/ModalComponent/Header/index.tsx +71 -0
  69. package/Components/ModalComponent/Header/utils.ts +79 -0
  70. package/Components/ModalComponent/index.tsx +38 -0
  71. package/Components/ModalComponent/utils.ts +126 -0
  72. package/Components/Navigator/StackNavigator.tsx +57 -0
  73. package/Components/Navigator/index.ts +1 -0
  74. package/Components/PlayerContainer/PlayerContainer.tsx +66 -31
  75. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  76. package/Components/PlayerImageBackground/index.tsx +34 -8
  77. package/Components/River/ComponentsMap/ComponentsMap.tsx +97 -2
  78. package/Components/River/RefreshControl.tsx +55 -1
  79. package/Components/River/River.tsx +8 -5
  80. package/Components/River/TV/River.tsx +2 -25
  81. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -1
  82. package/Components/River/__tests__/componentsMap.test.js +7 -0
  83. package/Components/River/useScreenConfiguration.ts +2 -2
  84. package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +1 -0
  85. package/Components/RouteManager/__tests__/routeManager.test.js +9 -2
  86. package/Components/RouteManager/index.tsx +4 -2
  87. package/Components/Screen/TV/hooks/index.ts +3 -0
  88. package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
  89. package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
  90. package/Components/Screen/TV/index.web.tsx +165 -0
  91. package/Components/Screen/__tests__/Screen.test.tsx +91 -74
  92. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +24 -6
  93. package/Components/Screen/__tests__/navigationHandler.test.ts +1 -178
  94. package/Components/Screen/index.tsx +110 -54
  95. package/Components/Screen/navigationHandler.ts +5 -82
  96. package/Components/ScreenResolver/index.tsx +10 -13
  97. package/Components/Tabs/TV/styles.ts +1 -1
  98. package/Components/TrackedView/index.tsx +6 -3
  99. package/Components/Transitioner/Scene.tsx +32 -26
  100. package/Components/Transitioner/Transitioner.tsx +21 -28
  101. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
  102. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  103. package/Components/VideoLive/LiveImageManager.ts +70 -14
  104. package/Components/VideoLive/PlayerLiveImageComponent.tsx +132 -95
  105. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  106. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +21 -29
  107. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +66 -0
  108. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +355 -0
  109. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +161 -0
  110. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +456 -0
  111. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +182 -0
  112. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  113. package/Components/VideoModal/ModalAnimation/utils.ts +302 -0
  114. package/Components/VideoModal/PlayerDetails.tsx +6 -3
  115. package/Components/VideoModal/PlayerWrapper.tsx +81 -49
  116. package/Components/VideoModal/VideoModal.tsx +23 -40
  117. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +0 -2
  118. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +12 -1
  119. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +266 -166
  120. package/Components/VideoModal/hooks/useModalSize.ts +10 -12
  121. package/Components/VideoModal/utils.ts +8 -5
  122. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +26 -21
  123. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  124. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +4 -5
  125. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  126. package/Components/Viewport/utils.js +1 -1
  127. package/Components/ZappUIComponent/index.tsx +22 -12
  128. package/Components/index.js +2 -0
  129. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  130. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  131. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  132. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  133. package/Contexts/FocusableGroupContext/index.ts +7 -0
  134. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  135. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  136. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  137. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  138. package/Contexts/ScreenContext/index.tsx +95 -29
  139. package/Contexts/ScreenDataContext/index.tsx +20 -0
  140. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  141. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  142. package/Decorators/RiverResolver/index.tsx +0 -4
  143. package/Decorators/index.tsx +0 -4
  144. package/events/index.ts +1 -0
  145. package/package.json +5 -6
  146. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  147. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  148. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  149. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
  150. /package/Contexts/{index.js → index.ts} +0 -0
@@ -4,10 +4,7 @@ import * as R from "ramda";
4
4
 
5
5
  import { AnimationManager } from "./AnimationManager";
6
6
  import { Scene } from "./Scene";
7
- import {
8
- getScreenOrientation,
9
- isOrientationCompatible,
10
- } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
7
+
11
8
  import {
12
9
  getSceneByRoute,
13
10
  shouldSkipAnimationForPreviousWebViewScene,
@@ -277,33 +274,25 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
277
274
  // so only a single scene should be rendered,
278
275
  // instead of dangerous nulls or duplicates.
279
276
  renderSingleScene({ scenes, to }) {
280
- const { contentStyle, layoutData } = this.props;
281
- const screenData = scenes[to.index]?.children?.props?.screenData;
282
-
283
- const orientation = getScreenOrientation({
284
- screenData,
285
- layoutData,
286
- });
277
+ const { contentStyle } = this.props;
278
+ const screenData = this.props.children.props.screenData;
279
+ const pathname = scenes[to.index].key;
287
280
 
288
- // We prevent rendering of the screen until UI is actually rotated to screen desired orientation.
289
- // This saves unnecessary re-renders and user will not see distorted aspect screen.
290
281
  return (
291
282
  <View style={styles.container}>
292
- {scenes[to.index] &&
293
- isOrientationCompatible({
294
- orientation,
295
- layoutData,
296
- }) && (
297
- <Scene
298
- {...{ style: to.style }}
299
- pathname={scenes[to.index].key}
300
- contentStyle={contentStyle}
301
- key={scenes[to.index].key}
302
- screenUniqueId={scenes[to.index].screenUniqueId}
303
- >
304
- {scenes[to.index].children}
305
- </Scene>
306
- )}
283
+ {scenes[to.index] && (
284
+ <Scene
285
+ {...{ style: to.style }}
286
+ pathname={pathname}
287
+ isActive={pathname === this.props.navigator.currentRoute}
288
+ contentStyle={contentStyle}
289
+ key={scenes[to.index].key}
290
+ screenUniqueId={scenes[to.index].screenUniqueId}
291
+ screenData={screenData}
292
+ >
293
+ {scenes[to.index].children}
294
+ </Scene>
295
+ )}
307
296
  </View>
308
297
  );
309
298
  }
@@ -319,12 +308,14 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
319
308
  <Scene
320
309
  key={scenes[from.index].key}
321
310
  pathname={scenes[from.index].key}
311
+ isActive={scenes[from.index].key === this.props.navigator.currentRoute}
322
312
  style={from.style}
323
313
  pointerEvents="none"
324
314
  overlayStyle={from.overlayStyle}
325
315
  animating={this.state.animating}
326
316
  contentStyle={contentStyle}
327
317
  screenUniqueId={scenes[from.index].screenUniqueId}
318
+ screenData={scenes[from.index].children.props.screenData}
328
319
  >
329
320
  {scenes[from.index].children}
330
321
  </Scene>
@@ -333,12 +324,14 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
333
324
  const toScene = scenes[to.index] && (
334
325
  <Scene
335
326
  pathname={scenes[to.index].key}
327
+ isActive={scenes[to.index].key === this.props.navigator.currentRoute}
336
328
  key={scenes[to.index].key}
337
329
  style={to.style}
338
330
  overlayStyle={to.overlayStyle}
339
331
  animating={this.state.animating}
340
332
  contentStyle={contentStyle}
341
333
  screenUniqueId={scenes[to.index].screenUniqueId}
334
+ screenData={this.props.children.props.screenData}
342
335
  >
343
336
  {scenes[to.index].children}
344
337
  </Scene>
@@ -21,7 +21,11 @@ exports[`<Scene /> renders correctly 1`] = `
21
21
  ]
22
22
  }
23
23
  >
24
- <ContextProvider />
24
+ <ContextProvider>
25
+ <ContextProvider>
26
+ <ScreenContextProvider />
27
+ </ContextProvider>
28
+ </ContextProvider>
25
29
  </ForwardRef(AnimatedComponentWrapper)>
26
30
  `;
27
31
 
@@ -37,13 +41,17 @@ exports[`<Scene /> renders the overlay while animating 1`] = `
37
41
  "paddingBottom": 49,
38
42
  },
39
43
  Object {
40
- "height": 1334,
41
- "width": 750,
44
+ "height": "100%",
45
+ "width": "100%",
42
46
  },
43
47
  ]
44
48
  }
45
49
  >
46
- <ContextProvider />
50
+ <ContextProvider>
51
+ <ContextProvider>
52
+ <ScreenContextProvider />
53
+ </ContextProvider>
54
+ </ContextProvider>
47
55
  <ForwardRef(AnimatedComponentWrapper)
48
56
  style={
49
57
  Object {
@@ -10,6 +10,7 @@ exports[`<TransitionerComponent /> renders correctly 1`] = `
10
10
  }
11
11
  >
12
12
  <Scene
13
+ isActive={true}
13
14
  screenUniqueId="mocked-uuid"
14
15
  >
15
16
  <View
@@ -6,10 +6,15 @@ import {
6
6
  import { setUserCellPlayerMutedPreference } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/userCellPlayerMutedPreference";
7
7
  import { loggerLiveImageManager } from "./loggerHelper";
8
8
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
9
+ import { Component } from "react";
9
10
 
10
11
  const { log_debug, log_info, log_error } = loggerLiveImageManager;
11
12
 
12
- export type LiveImageManagerEvent = "onEnd" | "onStart" | "onError";
13
+ export type LiveImageManagerEvent =
14
+ | "onEnd"
15
+ | "onStart"
16
+ | "onError"
17
+ | "onDestroy";
13
18
 
14
19
  export type LiveImageManagerListenerData = {
15
20
  id: string;
@@ -36,7 +41,8 @@ type Position = {
36
41
  type LiveImageProps = {
37
42
  player: Player;
38
43
  playerId: string;
39
- setMode: (type: LiveImageType) => void;
44
+ setMode?: (type: LiveImageType) => void;
45
+ component: Component;
40
46
  // TODO: ...primary, powerCell, tvGallery, etc.
41
47
  // type: string;
42
48
  };
@@ -100,6 +106,16 @@ export class LiveImageManager implements PlayerLifecycleListener {
100
106
  }
101
107
 
102
108
  this.items = this.items.filter((i) => i !== item);
109
+
110
+ this.invokeListenersUpdate({
111
+ callbackName: "onDestroy",
112
+ event: {
113
+ item,
114
+ playerId: this.currentlyPlaying?.playerId,
115
+ primaryPlayerId: this.primaryPlayer?.playerId,
116
+ entry: item.getPlayer().getEntry(),
117
+ },
118
+ });
103
119
  };
104
120
 
105
121
  public onViewportEnter = (item: LiveImage) => {
@@ -164,7 +180,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
164
180
 
165
181
  // Fake close event, because we unmount native view
166
182
  item.player?.onPlayerClose();
167
- item.setMode(LiveImageType.Image);
183
+ item.setMode?.(LiveImageType.Image);
168
184
 
169
185
  if (item === this.currentlyPlaying) {
170
186
  this.currentlyPlaying = null;
@@ -191,8 +207,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
191
207
  }
192
208
 
193
209
  this.currentlyPlaying = item;
194
-
195
- item.setMode(LiveImageType.Video);
210
+ item.setMode?.(LiveImageType.Video);
196
211
 
197
212
  if (item.player.playerState.isReadyToPlay) {
198
213
  item.player.play();
@@ -209,6 +224,11 @@ export class LiveImageManager implements PlayerLifecycleListener {
209
224
  this.pauseItem(item);
210
225
  };
211
226
 
227
+ public onLiveImageCompleted = (_item: LiveImage) => {
228
+ // TODO: Notify listeners that player has died
229
+ // Do not try look for new playable, otherwise you will restart video finish to play
230
+ };
231
+
212
232
  public muteAll = () => {
213
233
  log_debug("muteAll");
214
234
 
@@ -235,6 +255,14 @@ export class LiveImageManager implements PlayerLifecycleListener {
235
255
  const playerItem = this.findNextPlayableItem();
236
256
 
237
257
  if (playerItem) {
258
+ if (!playerItem.isFullyVisible) {
259
+ log_error(
260
+ `checkPlayerPosition: trying to start playback currently invisible item: ${playerInfo(
261
+ playerItem.player
262
+ )}`
263
+ );
264
+ }
265
+
238
266
  // Will also check if it's item that already playing
239
267
  this.playLiveImage(playerItem);
240
268
  }
@@ -242,7 +270,6 @@ export class LiveImageManager implements PlayerLifecycleListener {
242
270
 
243
271
  onPrimaryPlayerClosed = () => {
244
272
  const playerItem = this.findNextPlayableItem();
245
-
246
273
  log_info("onPrimaryPlayerClosed: starting to play visible live-image");
247
274
 
248
275
  this.currentlyPlaying = null;
@@ -338,6 +365,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
338
365
  item,
339
366
  playerId: this.currentlyPlaying?.playerId,
340
367
  primaryPlayerId: this.primaryPlayer?.playerId,
368
+ entry: item.getPlayer().getEntry(),
341
369
  },
342
370
  });
343
371
  };
@@ -356,12 +384,12 @@ export class LiveImageManager implements PlayerLifecycleListener {
356
384
  // TODO: This branch was used when we had play button to toggle replay.
357
385
  // Now we mute button instead but we keep it just in case.
358
386
  if (IS_ALLOWED_REPLAY && !isTV() && isCurrentItemEnded) {
359
- this.currentlyPlaying.player.seekTo(0);
360
- this.currentlyPlaying.player.pause();
387
+ this.currentlyPlaying?.player.seekTo(0);
388
+ this.currentlyPlaying?.player.pause();
361
389
 
362
390
  // TODO: ...Maybe player some other item
363
391
  } else {
364
- item.setMode(LiveImageType.Image);
392
+ item.setMode?.(LiveImageType.Image);
365
393
  }
366
394
 
367
395
  // Prevent receiving onEnd event from both `close` and `end` player events
@@ -372,15 +400,31 @@ export class LiveImageManager implements PlayerLifecycleListener {
372
400
  item,
373
401
  playerId: this.currentlyPlaying?.playerId,
374
402
  primaryPlayerId: this.primaryPlayer?.playerId,
403
+ entry: item.getPlayer().getEntry(),
375
404
  },
376
405
  });
377
406
  };
378
407
 
379
408
  onLiveImageError = (item: LiveImage, error: Error) => {
380
- item.setMode(LiveImageType.Image);
409
+ item.setMode?.(LiveImageType.Image);
381
410
 
382
- if (this.currentlyPlaying === item) {
411
+ const currentItem = this.currentlyPlaying;
412
+
413
+ log_debug(
414
+ `onLiveImageError: error: ${error.message}, live-image ${playerInfo(
415
+ item.player
416
+ )}, currentPlayingId: ${
417
+ this.currentlyPlaying?.playerId
418
+ }, primaryPlayerId: ${this.primaryPlayer?.playerId}`
419
+ );
420
+
421
+ if (currentItem === item) {
383
422
  this.currentlyPlaying = null;
423
+
424
+ log_debug(
425
+ `onLiveImageError: currentitem: ${currentItem.playerId} was removed`
426
+ );
427
+
384
428
  // TODO: ...Maybe player some other item
385
429
  }
386
430
 
@@ -389,8 +433,9 @@ export class LiveImageManager implements PlayerLifecycleListener {
389
433
  event: {
390
434
  item,
391
435
  error,
392
- playerId: this.currentlyPlaying?.playerId,
393
- primaryPlayerId: this.primaryPlayer?.playerId,
436
+ playerId: currentItem?.playerId,
437
+ primaryPlayerId: currentItem?.playerId,
438
+ entry: item.getPlayer().getEntry(),
394
439
  },
395
440
  });
396
441
  };
@@ -446,12 +491,13 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
446
491
  };
447
492
 
448
493
  readonly playerId: string;
494
+ readonly component: Component;
449
495
 
450
496
  constructor(props: LiveImageProps) {
451
497
  this.player = props.player;
452
498
  this.setMode = props.setMode;
453
499
  this.playerId = this.player.playerId;
454
-
500
+ this.component = props.component;
455
501
  this.player.addListener({ id: "live-image", listener: this });
456
502
  }
457
503
 
@@ -479,6 +525,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
479
525
  LiveImageManager.instance.onLiveImageError(this, error);
480
526
  };
481
527
 
528
+ onVideoError = (error: Error) => {
529
+ log_error(
530
+ `onVideoError: live-image, fail to play, switching back to image. error: ${
531
+ error?.message
532
+ }, ${playerInfo(this.player)}`
533
+ );
534
+
535
+ LiveImageManager.instance.onLiveImageError(this, error);
536
+ };
537
+
482
538
  onVideoEnd = (_event) => {
483
539
  log_info(
484
540
  `onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
@@ -1,22 +1,20 @@
1
1
  // /// <reference types="@applicaster/applicaster-types" />
2
2
  //
3
3
  import * as React from "react";
4
- import * as R from "ramda";
5
4
 
6
- import { AppState, AppStateStatus } from "react-native";
5
+ import { AppState, AppStateStatus, View } from "react-native";
7
6
  import { TrackedView } from "../TrackedView";
8
7
  import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
9
8
 
10
9
  import { playerFactory } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
11
10
 
12
- import { BufferAnimation } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/BufferAnimation";
13
11
  import {
14
12
  isApplePlatform,
15
13
  isTV,
16
14
  } from "@applicaster/zapp-react-native-utils/reactUtils";
17
15
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
18
16
  import { getUserCellPlayerMutedPreference } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/userCellPlayerMutedPreference";
19
- import { PlayerImageBackground } from "../PlayerImageBackground";
17
+ import Image from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/Image";
20
18
  import { LiveImage, LiveImageManager, LiveImageType } from "./LiveImageManager";
21
19
 
22
20
  import { AnimatedInOut } from "@applicaster/zapp-react-native-ui-components/Components/AnimatedInOut";
@@ -25,12 +23,14 @@ import { loggerLiveImageComponent } from "./loggerHelper";
25
23
  import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
26
24
  import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
27
25
  import { getAutoplaySettings } from "./utils";
26
+ import { isString } from "@applicaster/zapp-react-native-utils/stringUtils";
27
+ import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
28
+ import { useEffect } from "react";
28
29
 
29
- const { log_error } = loggerLiveImageComponent;
30
+ const { log_error, log_debug } = loggerLiveImageComponent;
30
31
 
31
- const isMeasurement = (item) => {
32
- return item.id.startsWith("pre-measurement-");
33
- };
32
+ const isMeasurement = (item: ZappEntry) =>
33
+ isString(item.id) && item.id.startsWith("pre-measurement-");
34
34
 
35
35
  type Props = {
36
36
  item: ZappEntry;
@@ -40,12 +40,13 @@ type Props = {
40
40
  imageKey?: string;
41
41
  screenConfig: Record<string, any>;
42
42
  audioMutedByDefault?: boolean;
43
+ uri: string; // cover image url
43
44
  };
44
45
 
45
46
  const PlayerLiveImageComponent = (props: Props) => {
46
47
  const dimensions = useDimensions("screen", { fullDimensions: true });
47
48
 
48
- const { item, style, playerId, imageKey } = props;
49
+ const { item, style, playerId } = props;
49
50
 
50
51
  const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
51
52
  const [mode, setMode] = React.useState(LiveImageType.Image);
@@ -55,20 +56,18 @@ const PlayerLiveImageComponent = (props: Props) => {
55
56
  isTV() ? false : getUserCellPlayerMutedPreference(props.audioMutedByDefault)
56
57
  );
57
58
 
58
- const [PlayerViewComponent, setPlayerViewComponent] = React.useState(null);
59
- const Player: any = PlayerViewComponent || BufferAnimation;
60
-
61
59
  const ref = React.useRef(null);
62
-
63
- const liveImageItem: LiveImage = React.useMemo(() => {
64
- /**
65
- * Debounced setMode function.
66
- *
67
- * Sets to video with a 1sec delay. Set to image happens immediately and cancels the timeout.
68
- *
69
- * @param type video or image
70
- */
71
- const setModeDebounced = (type: LiveImageType) => {
60
+ const trackViewRef = React.useRef(null);
61
+
62
+ /**
63
+ * Debounced setMode function.
64
+ *
65
+ * Sets to video with a 1sec delay. Set to image happens immediately and cancels the timeout.
66
+ *
67
+ * @param type video or image
68
+ */
69
+ const setModeDebounced = React.useCallback(
70
+ (type: LiveImageType) => {
72
71
  if (type === LiveImageType.Video) {
73
72
  if (type === mode) {
74
73
  return;
@@ -80,45 +79,69 @@ const PlayerLiveImageComponent = (props: Props) => {
80
79
  } else {
81
80
  if (timeoutRef.current) {
82
81
  clearTimeout(timeoutRef.current);
82
+ timeoutRef.current = null;
83
83
  }
84
84
 
85
85
  setMode(type);
86
86
  }
87
- };
87
+ },
88
+ [mode]
89
+ );
90
+
91
+ const entryToViewIdMapping = React.useRef({});
92
+
93
+ useEffect(() => {
94
+ const nativeTag = trackViewRef.current?._nativeTag?.toString();
95
+
96
+ if (!nativeTag) {
97
+ return;
98
+ }
99
+
100
+ const prevValue = entryToViewIdMapping.current[nativeTag];
88
101
 
102
+ if (prevValue && prevValue.itemId !== item.id) {
103
+ if (mode === LiveImageType.Video) {
104
+ log_debug(
105
+ "Entry to view association changed, stopping current live image"
106
+ );
107
+
108
+ setMode(LiveImageType.Image);
109
+ }
110
+ }
111
+
112
+ entryToViewIdMapping.current[nativeTag] = { itemId: item.id };
113
+ }, [trackViewRef.current, item.id, mode]);
114
+
115
+ const { screenConfig, playerPluginId } = props;
116
+
117
+ const liveImageItem: LiveImage = React.useMemo(() => {
89
118
  const playerFactoryItem = playerFactory({
90
119
  player: ref,
91
- listener: props as Record<string, any>,
92
120
  playerId,
93
121
  autoplay: false,
94
122
  entry: item,
95
- muted,
96
- playerPluginId: props?.playerPluginId,
97
- screenConfig: props?.screenConfig,
123
+ muted, // Initial muted state, not needed in dependencies
124
+ playerPluginId: playerPluginId,
125
+ screenConfig: screenConfig,
98
126
  playerRole: PlayerRole.Cell,
99
127
  });
100
128
 
101
- const Component = playerFactoryItem?.Component;
102
-
103
- if (!R.isNil(Component) && Component !== PlayerViewComponent) {
104
- setPlayerViewComponent(Component);
105
- }
106
-
107
129
  if (playerFactoryItem) {
108
130
  return new LiveImage({
109
131
  playerId,
110
132
  player: playerFactoryItem.controller,
111
- setMode: setModeDebounced,
133
+ component: playerFactoryItem?.Component,
112
134
  });
113
135
  } else {
114
136
  throw new Error("Player factory item is null");
115
137
  }
116
- }, [playerId, item?.id]);
138
+ }, [playerId, item, playerPluginId, screenConfig]);
117
139
 
118
- const { start, end } = React.useMemo(
119
- () => getAutoplaySettings(item),
120
- [item?.id]
121
- );
140
+ React.useEffect(() => {
141
+ liveImageItem.setMode = setModeDebounced;
142
+ }, [setModeDebounced, liveImageItem]);
143
+
144
+ const { start, end } = React.useMemo(() => getAutoplaySettings(item), [item]);
122
145
 
123
146
  const controller = liveImageItem.getPlayer();
124
147
  const player = usePlayer(playerId);
@@ -127,6 +150,10 @@ const PlayerLiveImageComponent = (props: Props) => {
127
150
  ref.current = component;
128
151
  };
129
152
 
153
+ const onLiveImageCompleted = React.useCallback(() => {
154
+ LiveImageManager.instance.onLiveImageCompleted(liveImageItem);
155
+ }, [liveImageItem]);
156
+
130
157
  const onStop = React.useCallback(() => {
131
158
  LiveImageManager.instance.pauseLiveImage(liveImageItem);
132
159
  }, [liveImageItem]);
@@ -140,13 +167,14 @@ const PlayerLiveImageComponent = (props: Props) => {
140
167
  const unregister = LiveImageManager.instance.register(liveImageItem);
141
168
 
142
169
  return () => {
143
- unregister();
144
-
145
170
  if (timeoutRef.current) {
146
171
  clearTimeout(timeoutRef.current);
172
+ timeoutRef.current = null;
147
173
  }
174
+
175
+ unregister();
148
176
  };
149
- }, [liveImageItem, item]);
177
+ }, [item, liveImageItem]);
150
178
 
151
179
  React.useEffect(() => {
152
180
  if (!player) {
@@ -178,11 +206,12 @@ const PlayerLiveImageComponent = (props: Props) => {
178
206
  setMuted(false);
179
207
  },
180
208
  onPlayerClose: () => {
209
+ onLiveImageCompleted();
181
210
  setIsReady(false);
182
211
  },
183
212
  },
184
213
  });
185
- }, [player, onStop]);
214
+ }, [player, onStop, onLiveImageCompleted, start, end]);
186
215
 
187
216
  React.useEffect(() => {
188
217
  const subscription = AppState.addEventListener(
@@ -233,40 +262,48 @@ const PlayerLiveImageComponent = (props: Props) => {
233
262
  };
234
263
  }
235
264
  }
236
- }, [liveImageItem]);
237
-
238
- const onPositionUpdated = (data) => {
239
- const normalizedRect = (rect) => {
240
- if (!rect) {
241
- return null;
242
- }
265
+ }, [liveImageItem, playerId, controller]);
266
+
267
+ const onPositionUpdated = React.useCallback(
268
+ (data) => {
269
+ const normalizedRect = (rect: {
270
+ left: number;
271
+ right: number;
272
+ top: number;
273
+ bottom: number;
274
+ }) => {
275
+ if (!rect) {
276
+ return null;
277
+ }
243
278
 
244
- const { left, right, top, bottom } = rect;
279
+ const { left, right, top, bottom } = rect;
245
280
 
246
- return {
247
- left: left / dimensions.width,
248
- right: right / dimensions.width,
249
- top: top / dimensions.height,
250
- bottom: bottom / dimensions.height,
251
- centerX: (left + right) / 2 / dimensions.width,
252
- centerY: (top + bottom) / 2 / dimensions.height,
281
+ return {
282
+ left: left / dimensions.width,
283
+ right: right / dimensions.width,
284
+ top: top / dimensions.height,
285
+ bottom: bottom / dimensions.height,
286
+ centerX: (left + right) / 2 / dimensions.width,
287
+ centerY: (top + bottom) / 2 / dimensions.height,
288
+ };
253
289
  };
254
- };
255
290
 
256
- liveImageItem.position = normalizedRect(data.rect);
291
+ liveImageItem.position = normalizedRect(data.rect);
257
292
 
258
- const newIsFullyVisible = !!data.rect;
293
+ const newIsFullyVisible = !!data.rect;
259
294
 
260
- if (liveImageItem.isFullyVisible !== newIsFullyVisible) {
261
- liveImageItem.isFullyVisible = newIsFullyVisible;
295
+ if (liveImageItem.isFullyVisible !== newIsFullyVisible) {
296
+ liveImageItem.isFullyVisible = newIsFullyVisible;
262
297
 
263
- liveImageItem.isFullyVisible
264
- ? LiveImageManager.instance.onViewportEnter(liveImageItem)
265
- : LiveImageManager.instance.onViewportLeave(liveImageItem);
266
- } else {
267
- LiveImageManager.instance.checkPlayerPosition(liveImageItem);
268
- }
269
- };
298
+ liveImageItem.isFullyVisible
299
+ ? LiveImageManager.instance.onViewportEnter(liveImageItem)
300
+ : LiveImageManager.instance.onViewportLeave(liveImageItem);
301
+ } else {
302
+ LiveImageManager.instance.checkPlayerPosition(liveImageItem);
303
+ }
304
+ },
305
+ [liveImageItem, dimensions]
306
+ );
270
307
 
271
308
  const platformSpecificProps = isApplePlatform()
272
309
  ? { hideNativeControls: true, controls: false }
@@ -276,38 +313,38 @@ const PlayerLiveImageComponent = (props: Props) => {
276
313
  const isVideoMode = mode === LiveImageType.Video;
277
314
  const isBackgroundImageVisible = isImageMode || !isReadyToPlay;
278
315
  const videoStyles = { ...style, position: "absolute", zIndex: -100 };
316
+ const Player: any = liveImageItem.component || BufferAnimation;
279
317
 
318
+ // TODO: nice to have - resolve imageKey when imageUrl is not provided
280
319
  return (
281
320
  <TrackedView
282
321
  testId={`tracked-view-${playerId}-${item.title}`}
283
322
  onPositionUpdated={onPositionUpdated}
284
323
  >
285
- {isVideoMode && (
286
- <Player
287
- autoplay={false}
288
- ref={_assignRoot}
289
- entry={item} // Must be passed first in list
290
- style={videoStyles}
291
- playerId={playerId}
292
- muted={muted}
293
- listener={controller?.getListener()}
294
- resizeMode={"cover"}
295
- {...platformSpecificProps}
296
- />
297
- )}
298
- {
299
- <AnimatedInOut
300
- visible={isBackgroundImageVisible}
301
- animationConfig={overlayFadeIn}
302
- staticStyles={style}
303
- >
304
- <PlayerImageBackground
305
- entry={item}
306
- style={style}
307
- imageKey={imageKey}
324
+ <View ref={trackViewRef}>
325
+ {isVideoMode && (
326
+ <Player
327
+ autoplay={false}
328
+ ref={_assignRoot}
329
+ entry={item} // Must be passed first in list
330
+ style={videoStyles}
331
+ playerId={playerId}
332
+ muted={muted}
333
+ listener={controller?.getListener()}
334
+ resizeMode={"cover"}
335
+ {...platformSpecificProps}
308
336
  />
309
- </AnimatedInOut>
310
- }
337
+ )}
338
+ {
339
+ <AnimatedInOut
340
+ visible={isBackgroundImageVisible}
341
+ animationConfig={overlayFadeIn}
342
+ staticStyles={style}
343
+ >
344
+ <Image {...props} resizeMode="cover" style={style} applyMaxSize />
345
+ </AnimatedInOut>
346
+ }
347
+ </View>
311
348
  </TrackedView>
312
349
  );
313
350
  };