@applicaster/zapp-react-native-ui-components 13.0.0-alpha.3592913150 → 13.0.0-alpha.3640541630

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 (56) hide show
  1. package/Components/BaseFocusable/index.ios.ts +1 -1
  2. package/Components/BaseFocusable/index.tsx +1 -1
  3. package/Components/Cell/Cell.tsx +7 -4
  4. package/Components/Cell/TvOSCellComponent.tsx +22 -20
  5. package/Components/Cell/index.js +1 -3
  6. package/Components/Focusable/FocusableTvOS.tsx +1 -5
  7. package/Components/Focusable/Touchable.tsx +19 -20
  8. package/Components/FocusableList/index.tsx +9 -25
  9. package/Components/FocusableScrollView/index.tsx +12 -39
  10. package/Components/Layout/TV/NavBarContainer.tsx +7 -8
  11. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +66 -0
  12. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -1
  13. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -11
  14. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/__tests__/index.test.ts +93 -0
  15. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +1 -1
  16. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  17. package/Components/MasterCell/utils/index.ts +1 -1
  18. package/Components/ModalComponent/Header/index.tsx +3 -3
  19. package/Components/River/ComponentsMap/ComponentsMap.tsx +39 -65
  20. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +78 -51
  21. package/Components/River/RiverFooter.tsx +39 -9
  22. package/Components/River/RiverItem.tsx +37 -2
  23. package/Components/River/TV/index.tsx +1 -6
  24. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +148 -31
  25. package/Components/River/__tests__/componentsMap.test.js +17 -5
  26. package/Components/Screen/hooks.ts +56 -0
  27. package/Components/Screen/index.tsx +13 -39
  28. package/Components/Tabs/Tab.tsx +6 -6
  29. package/Components/TextInputTv/index.tsx +2 -2
  30. package/Components/Transitioner/AnimationManager.js +8 -8
  31. package/Components/Transitioner/Scene.tsx +52 -23
  32. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +59 -43
  33. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -2
  34. package/Components/Transitioner/index.js +8 -4
  35. package/Components/VideoLive/LiveImageManager.ts +27 -1
  36. package/Components/VideoLive/PlayerLiveImageComponent.tsx +29 -21
  37. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +51 -1
  38. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
  39. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +7 -6
  40. package/Components/VideoModal/ModalAnimation/utils.ts +2 -2
  41. package/Components/VideoModal/OpaqueLayer.tsx +33 -0
  42. package/Components/VideoModal/PlayerWrapper.tsx +16 -35
  43. package/Components/VideoModal/VideoModal.tsx +14 -23
  44. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -1
  45. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +0 -90
  46. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +89 -0
  47. package/Components/VideoModal/hooks/index.ts +7 -0
  48. package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +49 -0
  49. package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +91 -0
  50. package/Components/VideoModal/hooks/utils/index.ts +33 -0
  51. package/Components/VideoModal/utils.ts +1 -1
  52. package/Contexts/ScreenContext/index.tsx +3 -2
  53. package/Decorators/ZappPipesDataConnector/__tests__/Hero.js +1 -1
  54. package/Decorators/ZappPipesDataConnector/index.tsx +31 -1
  55. package/package.json +5 -5
  56. package/Contexts/ComponentsMapOffsetContext/index.tsx +0 -46
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
- import TestRenderer from "react-test-renderer";
3
- import { cleanup } from "@testing-library/react-native";
2
+ import { cleanup, render } from "@testing-library/react-native";
4
3
  import { Provider } from "react-redux";
5
4
  import configureStore from "redux-mock-store";
6
5
 
@@ -81,6 +80,13 @@ const mockScreenData = {
81
80
  id: "A1234",
82
81
  };
83
82
 
83
+ jest.mock("@applicaster/zapp-react-native-redux/AppStore", () => ({
84
+ appStore: {
85
+ get: jest.fn((prop) => mockStore[prop]),
86
+ getState: jest.fn(),
87
+ },
88
+ }));
89
+
84
90
  jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
85
91
  useIsRTL: jest.fn(() => mock_rtl_flag),
86
92
  }));
@@ -153,7 +159,13 @@ const plugins = [];
153
159
  const navigation = {};
154
160
 
155
161
  const props = { components, cellStyles, riverComponents, navigation };
156
- const store = mockStore({ components, cellStyles, plugins });
162
+
163
+ const store = mockStore({
164
+ components,
165
+ cellStyles,
166
+ plugins,
167
+ getState: jest.fn(),
168
+ });
157
169
 
158
170
  jest.useFakeTimers();
159
171
 
@@ -170,12 +182,12 @@ describe("componentsMap", () => {
170
182
  .spyOn(themeUtils, "useTheme")
171
183
  .mockImplementation(() => () => theme);
172
184
 
173
- const wrapper = TestRenderer.create(
185
+ const { toJSON } = render(
174
186
  <Provider store={store}>
175
187
  <ComponentsMap {...props} />
176
188
  </Provider>
177
189
  );
178
190
 
179
- expect(wrapper.toJSON()).toMatchSnapshot();
191
+ expect(toJSON()).toMatchSnapshot();
180
192
  });
181
193
  });
@@ -0,0 +1,56 @@
1
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
+ import {
3
+ useGetScreenOrientation,
4
+ isOrientationCompatible,
5
+ } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
6
+ import {
7
+ useCurrentScreenData,
8
+ useDimensions,
9
+ useRoute,
10
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
11
+ import { useMemo, useEffect, useState } from "react";
12
+
13
+ export const useWaitForValidOrientation = () => {
14
+ const {
15
+ width: screenWidth,
16
+ height,
17
+ deviceInfo,
18
+ } = useDimensions("screen", {
19
+ fullDimensions: true,
20
+ updateForInactiveScreens: false,
21
+ });
22
+
23
+ const currentScreenData = useCurrentScreenData();
24
+
25
+ const { screenData } = useRoute();
26
+
27
+ const [readyState, setReadyState] = useState(false);
28
+
29
+ const isTablet = deviceInfo?.isTablet;
30
+
31
+ const { appData } = usePickFromState(["appData"]);
32
+ const isTabletPortrait = appData?.isTabletPortrait;
33
+
34
+ const layoutData = useMemo(
35
+ () => ({ isTablet, isTabletPortrait, width: screenWidth, height }),
36
+ [isTablet, isTabletPortrait, screenWidth, height]
37
+ );
38
+
39
+ const targetScreenData =
40
+ currentScreenData || (screenData as any)?.targetScreen || screenData;
41
+
42
+ const orientation = useGetScreenOrientation(targetScreenData);
43
+
44
+ const isReadyForDisplay = isOrientationCompatible({
45
+ orientation,
46
+ layoutData,
47
+ });
48
+
49
+ useEffect(() => {
50
+ if (isReadyForDisplay && !readyState) {
51
+ setReadyState(true);
52
+ }
53
+ }, [readyState, orientation, layoutData]);
54
+
55
+ return readyState;
56
+ };
@@ -2,10 +2,6 @@
2
2
  import React from "react";
3
3
  import { View } from "react-native";
4
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
- import {
6
- isOrientationCompatible,
7
- useGetScreenOrientation,
8
- } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
9
5
 
10
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
11
7
  import { getComponentModule } from "@applicaster/zapp-react-native-utils/pluginUtils";
@@ -15,7 +11,6 @@ import {
15
11
  getScreenId,
16
12
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
17
13
  import {
18
- useDimensions,
19
14
  useRoute,
20
15
  useCurrentScreenData,
21
16
  useNavbarState,
@@ -27,6 +22,7 @@ import { getNavigationPluginModule } from "@applicaster/zapp-react-native-app/Ap
27
22
  import { RouteManager } from "../RouteManager";
28
23
  import { useScreenConfiguration } from "../River/useScreenConfiguration";
29
24
  import { isValidColor } from "./utils";
25
+ import { useWaitForValidOrientation } from "./hooks";
30
26
 
31
27
  const screenStyles = {
32
28
  flex: 1,
@@ -50,14 +46,6 @@ export function Screen(_props: Props) {
50
46
  const currentScreenData = useCurrentScreenData();
51
47
  const { backgroundColor } = useScreenConfiguration(currentScreenData.id);
52
48
 
53
- const {
54
- width: screenWidth,
55
- height,
56
- deviceInfo,
57
- } = useDimensions("screen", {
58
- fullDimensions: true,
59
- });
60
-
61
49
  const { screenData, pathname } = useRoute();
62
50
 
63
51
  const currentRiver = useScreenData(
@@ -66,13 +54,6 @@ export function Screen(_props: Props) {
66
54
 
67
55
  const { title } = useNavbarState();
68
56
 
69
- const isTablet = deviceInfo?.isTablet;
70
-
71
- const { appData } = usePickFromState(["appData"]);
72
- const isTabletPortrait = appData?.isTabletPortrait;
73
-
74
- const layoutData = { isTablet, isTabletPortrait, width: screenWidth, height };
75
-
76
57
  const hasMenu = shouldNavBarDisplayMenu(currentRiver, plugins);
77
58
 
78
59
  const navBarProps = React.useMemo<MobileNavBarPluginProps | null>(
@@ -108,30 +89,23 @@ export function Screen(_props: Props) {
108
89
  [theme.app_background_color, backgroundColor]
109
90
  );
110
91
 
111
- const targetScreenData =
112
- currentScreenData || (screenData as any)?.targetScreen || screenData;
113
-
114
- const orientation = useGetScreenOrientation(targetScreenData);
92
+ // Set ready state when screen is rotated to desired orientation
93
+ const isReady = useWaitForValidOrientation();
115
94
 
116
95
  // We prevent rendering of the screen until UI is actually rotated to screen desired orientation.
117
96
  // This saves unnecessary re-renders and user will not see distorted aspect screen.
118
- if (
119
- !isOrientationCompatible({
120
- orientation,
121
- layoutData,
122
- })
123
- ) {
124
- return <View style={style} />;
125
- }
126
-
127
97
  return (
128
98
  <View style={style}>
129
- {navBarProps && <NavBar {...navBarProps} hasMenu={hasMenu} />}
130
-
131
- <OfflineFallbackScreen>
132
- {/* @TODO RouteManager doesn't use props, can they be removed ? */}
133
- <RouteManager pathname={pathname} screenData={screenData} />
134
- </OfflineFallbackScreen>
99
+ {isReady ? (
100
+ <>
101
+ {navBarProps && <NavBar {...navBarProps} hasMenu={hasMenu} />}
102
+
103
+ <OfflineFallbackScreen>
104
+ {/* @TODO RouteManager doesn't use props, can they be removed ? */}
105
+ <RouteManager pathname={pathname} screenData={screenData} />
106
+ </OfflineFallbackScreen>
107
+ </>
108
+ ) : null}
135
109
  </View>
136
110
  );
137
111
  }
@@ -69,8 +69,8 @@ const getStyles = (configuration, selected, focused) => {
69
69
  ? tab_cell_background_color_selected_active
70
70
  : tab_cell_background_color_selected_default
71
71
  : focused
72
- ? tab_cell_background_color_active
73
- : tab_cell_background_color_default,
72
+ ? tab_cell_background_color_active
73
+ : tab_cell_background_color_default,
74
74
  justifyContent: "center",
75
75
  alignItems: "center",
76
76
  position: "relative",
@@ -85,8 +85,8 @@ const getStyles = (configuration, selected, focused) => {
85
85
  ? tab_cell_border_color_active_focused
86
86
  : tab_cell_border_color_active
87
87
  : focused
88
- ? tab_cell_border_color_default_focused
89
- : tab_cell_border_color_default,
88
+ ? tab_cell_border_color_default_focused
89
+ : tab_cell_border_color_default,
90
90
  },
91
91
  label: {
92
92
  fontFamily: text_label_font_family,
@@ -98,8 +98,8 @@ const getStyles = (configuration, selected, focused) => {
98
98
  ? text_label_selected_active_font_color
99
99
  : text_label_selected_default_font_color
100
100
  : focused
101
- ? text_label_active_font_color
102
- : text_label_default_font_color,
101
+ ? text_label_active_font_color
102
+ : text_label_default_font_color,
103
103
  },
104
104
  underline: {
105
105
  position: "absolute",
@@ -94,8 +94,8 @@ function TextInputTV(props: Props, ref) {
94
94
  styles.textAlign === "center"
95
95
  ? styles.textAlign
96
96
  : styles.textAlign === "left"
97
- ? "right"
98
- : "left";
97
+ ? "right"
98
+ : "left";
99
99
 
100
100
  return { style: { ...styles, textAlign } };
101
101
  }
@@ -3,19 +3,19 @@ import { Animated } from "react-native";
3
3
  import { NAV_ACTION_PUSH, NAV_ACTION_BACK } from "./Transitioner";
4
4
 
5
5
  type TransitionConfig = {
6
- duration: number,
7
- easing: any,
6
+ duration: number;
7
+ easing: any;
8
8
  from: {
9
- style: any,
10
- },
9
+ style: any;
10
+ };
11
11
  to: {
12
- style: any,
13
- },
12
+ style: any;
13
+ };
14
14
  };
15
15
 
16
16
  type Props = {
17
- transitionConfig: TransitionConfig,
18
- contentStyle: { [string]: any },
17
+ transitionConfig: TransitionConfig;
18
+ contentStyle: { [string]: any };
19
19
  };
20
20
 
21
21
  /**
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { equals } from "ramda";
2
3
  import { Animated, ViewProps, ViewStyle } from "react-native";
3
4
  import { useSafeAreaFrame } from "react-native-safe-area-context";
4
5
 
@@ -8,6 +9,11 @@ import { PathnameContext } from "../../Contexts/PathnameContext";
8
9
  import { ScreenDataContext } from "../../Contexts/ScreenDataContext";
9
10
  import { ScreenContextProvider } from "../../Contexts/ScreenContext";
10
11
 
12
+ const fullWidthDimensions = {
13
+ height: "100%",
14
+ width: "100%",
15
+ };
16
+
11
17
  type Props = {
12
18
  children: any;
13
19
  style: any;
@@ -20,6 +26,47 @@ type Props = {
20
26
  screenData: NavigationScreenData;
21
27
  };
22
28
 
29
+ export function CurrentScreenContextProvider({
30
+ children,
31
+ ...props
32
+ }: {
33
+ children: any;
34
+ pathname: string;
35
+ isActive: boolean;
36
+ screenData: NavigationScreenData;
37
+ }) {
38
+ const { pathname, isActive = false, screenData } = props;
39
+
40
+ const [initialScreenData, setInitialScreenData] = React.useState(screenData);
41
+
42
+ React.useLayoutEffect(() => {
43
+ const screenDataChanged = !equals(initialScreenData, screenData);
44
+
45
+ if (isActive && screenDataChanged) {
46
+ setInitialScreenData(screenData);
47
+ }
48
+ }, [initialScreenData, screenData, isActive]);
49
+
50
+ return (
51
+ <ScreenDataContext.Provider value={initialScreenData}>
52
+ <PathnameContext.Provider value={pathname}>
53
+ <ScreenContextProvider pathname={pathname}>
54
+ {children}
55
+ </ScreenContextProvider>
56
+ </PathnameContext.Provider>
57
+ </ScreenDataContext.Provider>
58
+ );
59
+ }
60
+
61
+ const withCurrentScreenContext = (Component) =>
62
+ function SceneComponent(props: Props) {
63
+ return (
64
+ <CurrentScreenContextProvider {...props}>
65
+ <Component {...props} />
66
+ </CurrentScreenContextProvider>
67
+ );
68
+ };
69
+
23
70
  /**
24
71
  * Animated View container for any purpose.
25
72
  * Renders given children with given style object.
@@ -32,53 +79,35 @@ type Props = {
32
79
  * @param contentStyle
33
80
  * @param screenUniqueId
34
81
  */
35
- export function Scene({
82
+ function SceneComponent({
36
83
  children,
37
84
  style,
38
85
  pointerEvents = "auto",
39
86
  overlayStyle,
40
87
  animating,
41
88
  contentStyle,
42
- pathname,
43
89
  isActive = false,
44
90
  screenData,
45
91
  }: Props) {
46
- const [initialScreenData, setInitialScreenData] = React.useState(screenData);
47
-
48
92
  useScreenOrientationHandler({
49
93
  screenData,
50
94
  isActive,
51
95
  });
52
96
 
53
- React.useLayoutEffect(() => {
54
- if (isActive) {
55
- setInitialScreenData(screenData);
56
- }
57
- }, [screenData, isActive]);
58
-
59
97
  const frame = useSafeAreaFrame();
60
98
  const isAnimating = animating && overlayStyle;
61
99
 
62
- const dimensions = isAnimating
63
- ? {
64
- width: "100%",
65
- height: "100%",
66
- }
67
- : frame;
100
+ const dimensions = isAnimating ? fullWidthDimensions : frame;
68
101
 
69
102
  return (
70
103
  <Animated.View
71
104
  pointerEvents={pointerEvents}
72
105
  style={[style, contentStyle, dimensions]}
73
106
  >
74
- <ScreenDataContext.Provider value={initialScreenData}>
75
- <PathnameContext.Provider value={pathname}>
76
- <ScreenContextProvider pathname={pathname}>
77
- {children}
78
- </ScreenContextProvider>
79
- </PathnameContext.Provider>
80
- </ScreenDataContext.Provider>
107
+ {children}
81
108
  {isAnimating && <Animated.View style={overlayStyle} />}
82
109
  </Animated.View>
83
110
  );
84
111
  }
112
+
113
+ export const Scene = withCurrentScreenContext(SceneComponent);
@@ -1,63 +1,79 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<Scene /> renders correctly 1`] = `
4
- <ForwardRef(AnimatedComponentWrapper)
5
- pointerEvents="auto"
4
+ <CurrentScreenContextProvider
5
+ contentStyle={
6
+ {
7
+ "paddingBottom": 49,
8
+ }
9
+ }
10
+ overlayStyle={
11
+ {
12
+ "opacity": 1,
13
+ }
14
+ }
6
15
  style={
7
- [
8
- {
9
- "flex": 1,
10
- },
16
+ {
17
+ "flex": 1,
18
+ }
19
+ }
20
+ >
21
+ <SceneComponent
22
+ contentStyle={
11
23
  {
12
24
  "paddingBottom": 49,
13
- },
25
+ }
26
+ }
27
+ overlayStyle={
14
28
  {
15
- "fontScale": 2,
16
- "height": 1334,
17
- "scale": 2,
18
- "statusBarHeight": null,
19
- "width": 750,
20
- },
21
- ]
22
- }
23
- >
24
- <ContextProvider>
25
- <ContextProvider>
26
- <ScreenContextProvider />
27
- </ContextProvider>
28
- </ContextProvider>
29
- </ForwardRef(AnimatedComponentWrapper)>
29
+ "opacity": 1,
30
+ }
31
+ }
32
+ style={
33
+ {
34
+ "flex": 1,
35
+ }
36
+ }
37
+ />
38
+ </CurrentScreenContextProvider>
30
39
  `;
31
40
 
32
41
  exports[`<Scene /> renders the overlay while animating 1`] = `
33
- <ForwardRef(AnimatedComponentWrapper)
34
- pointerEvents="auto"
42
+ <CurrentScreenContextProvider
43
+ animating={true}
44
+ contentStyle={
45
+ {
46
+ "paddingBottom": 49,
47
+ }
48
+ }
49
+ overlayStyle={
50
+ {
51
+ "opacity": 1,
52
+ }
53
+ }
35
54
  style={
36
- [
37
- {
38
- "flex": 1,
39
- },
55
+ {
56
+ "flex": 1,
57
+ }
58
+ }
59
+ >
60
+ <SceneComponent
61
+ animating={true}
62
+ contentStyle={
40
63
  {
41
64
  "paddingBottom": 49,
42
- },
65
+ }
66
+ }
67
+ overlayStyle={
43
68
  {
44
- "height": "100%",
45
- "width": "100%",
46
- },
47
- ]
48
- }
49
- >
50
- <ContextProvider>
51
- <ContextProvider>
52
- <ScreenContextProvider />
53
- </ContextProvider>
54
- </ContextProvider>
55
- <ForwardRef(AnimatedComponentWrapper)
69
+ "opacity": 1,
70
+ }
71
+ }
56
72
  style={
57
73
  {
58
- "opacity": 1,
74
+ "flex": 1,
59
75
  }
60
76
  }
61
77
  />
62
- </ForwardRef(AnimatedComponentWrapper)>
78
+ </CurrentScreenContextProvider>
63
79
  `;
@@ -9,7 +9,7 @@ exports[`<TransitionerComponent /> renders correctly 1`] = `
9
9
  }
10
10
  }
11
11
  >
12
- <Scene
12
+ <SceneComponent
13
13
  isActive={true}
14
14
  screenUniqueId="mocked-uuid"
15
15
  style={{}}
@@ -23,6 +23,6 @@ exports[`<TransitionerComponent /> renders correctly 1`] = `
23
23
  children
24
24
  </Text>
25
25
  </View>
26
- </Scene>
26
+ </SceneComponent>
27
27
  </View>
28
28
  `;
@@ -1,5 +1,4 @@
1
1
  import * as React from "react";
2
- import { useWindowDimensions } from "react-native";
3
2
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
4
3
  import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
5
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
@@ -7,10 +6,15 @@ import { TransitionerComponent } from "./Transitioner";
7
6
 
8
7
  export const Transitioner = (props) => {
9
8
  const navigator = useNavigation();
10
- const { height, width } = useWindowDimensions();
11
9
 
12
- const dimensions = useDimensions("window", { deviceInfo: true });
13
- const isTablet = dimensions?.deviceInfo?.isTablet;
10
+ const {
11
+ width,
12
+ height,
13
+ deviceInfo: { isTablet },
14
+ } = useDimensions("window", {
15
+ updateForInactiveScreens: false,
16
+ deviceInfo: true,
17
+ });
14
18
 
15
19
  const { appData } = usePickFromState(["appData"]);
16
20
  const isTabletPortrait = appData?.isTabletPortrait;
@@ -8,6 +8,8 @@ import { loggerLiveImageManager } from "./loggerHelper";
8
8
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
9
9
  import { Component } from "react";
10
10
 
11
+ const TIMEOUT_FOR_DELAY_CHECK_PLAYER_POSITION = 500; // ms
12
+
11
13
  const { log_debug, log_info, log_error } = loggerLiveImageManager;
12
14
 
13
15
  export type LiveImageManagerEvent =
@@ -62,6 +64,9 @@ export class LiveImageManager implements PlayerLifecycleListener {
62
64
  protected currentlyPlaying: LiveImage | null = null;
63
65
  protected primaryPlayer: Player | null = null;
64
66
 
67
+ private checkPlayerPositionTimeout: ReturnType<typeof setTimeout> | null =
68
+ null;
69
+
65
70
  protected listeners: Record<
66
71
  string,
67
72
  Record<LiveImageManagerEvent, (event?: Record<string, any>) => void>
@@ -118,6 +123,11 @@ export class LiveImageManager implements PlayerLifecycleListener {
118
123
  });
119
124
  };
120
125
 
126
+ private cancelCheckPlayerPositionTimeout = () => {
127
+ clearTimeout(this.checkPlayerPositionTimeout);
128
+ this.checkPlayerPositionTimeout = null;
129
+ };
130
+
121
131
  public onViewportEnter = (item: LiveImage) => {
122
132
  log_debug(
123
133
  `onViewportEnter: live-image ${playerInfo(
@@ -125,7 +135,21 @@ export class LiveImageManager implements PlayerLifecycleListener {
125
135
  )}, primary ${playerInfo(this.primaryPlayer)}`
126
136
  );
127
137
 
128
- this.checkPlayerPosition(item);
138
+ if (!isTV()) {
139
+ // mobile only
140
+ // we have to delay running checkPlayerPosition, because sometimes on fast scrolling we get wrong order onEnter, then onLeave.
141
+ // which could cause select wrong item to play
142
+
143
+ this.cancelCheckPlayerPositionTimeout();
144
+
145
+ this.checkPlayerPositionTimeout = setTimeout(() => {
146
+ this.cancelCheckPlayerPositionTimeout();
147
+
148
+ this.checkPlayerPosition(item);
149
+ }, TIMEOUT_FOR_DELAY_CHECK_PLAYER_POSITION);
150
+ } else {
151
+ this.checkPlayerPosition(item);
152
+ }
129
153
  };
130
154
 
131
155
  public onViewportLeave = (item: LiveImage) => {
@@ -246,6 +270,8 @@ export class LiveImageManager implements PlayerLifecycleListener {
246
270
  };
247
271
 
248
272
  public checkPlayerPosition = (item: LiveImage) => {
273
+ this.cancelCheckPlayerPositionTimeout();
274
+
249
275
  log_debug(
250
276
  `checkPlayerPosition: live-image playerId: ${playerInfo(
251
277
  item.player