@applicaster/zapp-react-native-ui-components 11.0.0-rc.99 → 12.0.0-alpha.5035843303
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/Components/BaseFocusable/index.ios.ts +1 -1
- package/Components/Cell/Cell.tsx +5 -13
- package/Components/Cell/{CellWithFocusable.js → CellWithFocusable.tsx} +32 -18
- package/Components/Cell/TvOSCellComponent.tsx +9 -12
- package/Components/FastImage/index.ios.js +3 -0
- package/Components/Focusable/FocusableTvOS.tsx +4 -1
- package/Components/FocusableCell/{index.js → index.tsx} +8 -5
- package/Components/FocusableGroup/FocusableTvOS.tsx +2 -2
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -8
- package/Components/HandlePlayable/HandlePlayable.tsx +2 -2
- package/Components/HookRenderer/HookRenderer.tsx +9 -6
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +1 -1
- package/Components/Layout/TV/index.tsx +9 -13
- package/Components/Layout/index.tsx +9 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +3 -1
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +4 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +22 -19
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +1 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +2 -0
- package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -3
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/CollapsibleTextContainer.tsx +9 -3
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/__snapshots__/index.test.tsx.snap +7 -1
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +12 -4
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +23 -9
- package/Components/MasterCell/hoc/withAsyncRender.tsx +8 -4
- package/Components/MasterCell/hooks/useAsyncRendering/MasterCellAsyncRenderManager.ts +12 -6
- package/Components/MasterCell/hooks/useAsyncRendering/__tests__/MasterCellAsyncRenderManager.test.ts +21 -0
- package/Components/MasterCell/hooks/useAsyncRendering/index.ts +15 -13
- package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
- package/Components/MasterCell/utils/index.ts +143 -19
- package/Components/PlayerContainer/PlayerContainer.tsx +10 -6
- package/Components/River/ComponentsMap/ComponentsMap.tsx +4 -1
- package/Components/River/River.tsx +3 -4
- package/Components/River/TV/River.tsx +9 -4
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
- package/Components/River/index.tsx +3 -1
- package/Components/Screen/__tests__/Screen.test.tsx +6 -3
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
- package/Components/Screen/index.tsx +12 -44
- package/Components/Screen/navigationHandler.ts +1 -1
- package/Components/Spinner/SpinnerComponent.tsx +6 -2
- package/Components/VideoLive/LiveImageManager.ts +62 -3
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +23 -1
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +74 -0
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +47 -0
- package/Components/VideoLive/__tests__/utils.test.ts +109 -0
- package/Components/VideoLive/utils.ts +40 -0
- package/Components/VideoModal/PlayerDetails.tsx +42 -6
- package/Components/VideoModal/PlayerWrapper.tsx +32 -29
- package/Components/VideoModal/VideoModal.tsx +20 -17
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +21 -9
- package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +14 -24
- package/Contexts/InteractionManagerContext/index.ts +0 -2
- package/Contexts/ScreenContext/index.tsx +118 -0
- package/package.json +5 -5
|
@@ -5,7 +5,6 @@ import { Text } from "react-native";
|
|
|
5
5
|
import * as R from "ramda";
|
|
6
6
|
|
|
7
7
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
8
|
-
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
9
8
|
import {
|
|
10
9
|
useFeedLoader,
|
|
11
10
|
useLayoutVersion,
|
|
@@ -17,6 +16,10 @@ import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
17
16
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
18
17
|
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
19
18
|
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
19
|
+
import {
|
|
20
|
+
useNavbarState,
|
|
21
|
+
useSetNavbarState,
|
|
22
|
+
} from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
20
23
|
|
|
21
24
|
type Props = {
|
|
22
25
|
screenId: string;
|
|
@@ -43,8 +46,10 @@ export const River = (props: Props) => {
|
|
|
43
46
|
extraOffset,
|
|
44
47
|
} = props;
|
|
45
48
|
|
|
46
|
-
const {
|
|
47
|
-
|
|
49
|
+
const { title: screenTitle, summary: screenSummary } = useNavbarState();
|
|
50
|
+
|
|
51
|
+
const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
|
|
52
|
+
useSetNavbarState();
|
|
48
53
|
|
|
49
54
|
const navigationPluginData = useNavigationPluginData();
|
|
50
55
|
|
|
@@ -92,7 +97,7 @@ export const River = (props: Props) => {
|
|
|
92
97
|
setScreenTitle(stringOrEmpty(screenData?.title));
|
|
93
98
|
setScreenSummary(stringOrEmpty(screenData?.summary));
|
|
94
99
|
}
|
|
95
|
-
}, [feedData, screenData]);
|
|
100
|
+
}, [feedData, screenData, screenTitle, screenSummary]);
|
|
96
101
|
|
|
97
102
|
React.useEffect(() => {
|
|
98
103
|
if (
|
|
@@ -60,6 +60,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
60
60
|
onScroll={[Function]}
|
|
61
61
|
onScrollBeginDrag={[Function]}
|
|
62
62
|
onScrollEndDrag={[Function]}
|
|
63
|
+
overScrollMode="auto"
|
|
63
64
|
refreshControl={null}
|
|
64
65
|
removeClippedSubviews={true}
|
|
65
66
|
renderItem={[Function]}
|
|
@@ -7,6 +7,7 @@ import { ContainerContext } from "../../Contexts";
|
|
|
7
7
|
import { RiverComponent } from "./River";
|
|
8
8
|
import { withDimensionsProvider } from "@applicaster/zapp-react-native-utils/dimensionsProvider";
|
|
9
9
|
import { WithRiverFeedLoader } from "../../Decorators/RiverFeedLoader";
|
|
10
|
+
import { withScreenContext } from "../../Contexts/ScreenContext";
|
|
10
11
|
|
|
11
12
|
const reduxConnector = connectToStore(R.pick(["rivers", "appData"]));
|
|
12
13
|
|
|
@@ -16,5 +17,6 @@ export const River = R.compose(
|
|
|
16
17
|
WithRiverFeedLoader,
|
|
17
18
|
withNavigator,
|
|
18
19
|
withDimensionsProvider,
|
|
19
|
-
ContainerContext.withConsumer
|
|
20
|
+
ContainerContext.withConsumer,
|
|
21
|
+
withScreenContext
|
|
20
22
|
)(RiverComponent);
|
|
@@ -53,6 +53,9 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
|
53
53
|
useCurrentScreenData: jest.fn(() => ({
|
|
54
54
|
id: "testId",
|
|
55
55
|
})),
|
|
56
|
+
useNavbarState: jest.fn(() => ({
|
|
57
|
+
visible: jest.fn(() => mock_navBarVisibleFlag),
|
|
58
|
+
})),
|
|
56
59
|
}));
|
|
57
60
|
|
|
58
61
|
const {
|
|
@@ -67,11 +70,11 @@ const navBarProps = { nav_items: [] };
|
|
|
67
70
|
const screenData = {};
|
|
68
71
|
|
|
69
72
|
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
canGoBack: () => false,
|
|
73
|
+
useRoute: jest.fn(() => ({
|
|
74
|
+
pathname: "/river/testId",
|
|
73
75
|
})),
|
|
74
76
|
useNavigation: jest.fn(() => ({
|
|
77
|
+
canGoBack: () => false,
|
|
75
78
|
currentRoute: "/river/testId",
|
|
76
79
|
data: { screen: { id: "testId" } },
|
|
77
80
|
})),
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
import React, { useMemo } from "react";
|
|
3
3
|
import * as R from "ramda";
|
|
4
|
-
import {
|
|
4
|
+
import { View } from "react-native";
|
|
5
5
|
import { RouteManager } from "../RouteManager";
|
|
6
6
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
7
|
-
import {
|
|
8
|
-
useNavBarStateForScreen,
|
|
9
|
-
useNavigation,
|
|
10
|
-
} from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
11
7
|
import { useScreenOrientationHandler } from "./orientationHandler";
|
|
12
|
-
import { isNavBarVisible } from "./navigationHandler";
|
|
13
8
|
import { useScreenConfiguration } from "../River/useScreenConfiguration";
|
|
14
9
|
import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
15
10
|
import type { ZappNavigationProps } from "@applicaster/zapp-react-native-flow-types/index";
|
|
16
11
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
12
|
+
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
17
13
|
|
|
18
14
|
type Props = {
|
|
19
15
|
pathname: string;
|
|
@@ -71,12 +67,8 @@ export function Screen(props: Props) {
|
|
|
71
67
|
|
|
72
68
|
// Gets the data for the current screen configuration
|
|
73
69
|
const currentScreenData = useCurrentScreenData();
|
|
74
|
-
|
|
75
70
|
const theme = useTheme();
|
|
76
|
-
|
|
77
|
-
const navigator = useNavigation();
|
|
78
|
-
const screenConfig = useScreenConfiguration(currentScreenData.id);
|
|
79
|
-
const { backgroundColor } = useMemo(() => screenConfig, [pathname]);
|
|
71
|
+
const { backgroundColor } = useScreenConfiguration(currentScreenData.id);
|
|
80
72
|
|
|
81
73
|
const style = React.useMemo(
|
|
82
74
|
() => ({
|
|
@@ -88,44 +80,20 @@ export function Screen(props: Props) {
|
|
|
88
80
|
[theme.app_background_color, backgroundColor]
|
|
89
81
|
);
|
|
90
82
|
|
|
91
|
-
const { isNavBarVisible: showNavBar, canGoBack } =
|
|
92
|
-
useNavBarStateForScreen(pathname);
|
|
93
|
-
|
|
94
|
-
const navBarVisible = useMemo(
|
|
95
|
-
() =>
|
|
96
|
-
isNavBarVisible(
|
|
97
|
-
pathname,
|
|
98
|
-
currentScreenData || screenData,
|
|
99
|
-
showNavBar,
|
|
100
|
-
canGoBack(),
|
|
101
|
-
navigator.videoModalState
|
|
102
|
-
),
|
|
103
|
-
[pathname, showNavBar, navigator.videoModalState]
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const navBarProps = useMemo(() => props.navBarProps, []);
|
|
107
|
-
|
|
108
83
|
const targetScreenData =
|
|
109
84
|
currentScreenData || screenData?.targetScreen || screenData;
|
|
110
85
|
|
|
111
86
|
useScreenOrientationHandler({ screenData: targetScreenData });
|
|
112
87
|
|
|
113
|
-
const shouldHideStatusBar = useMemo(
|
|
114
|
-
() =>
|
|
115
|
-
!navBarVisible &&
|
|
116
|
-
navigator.videoModalState?.mode === "FULLSCREEN" &&
|
|
117
|
-
navigator.videoModalState?.visible === true,
|
|
118
|
-
[navBarVisible, navigator.videoModalState]
|
|
119
|
-
);
|
|
120
|
-
|
|
121
88
|
return (
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
89
|
+
<ScreenContextProvider pathname={pathname}>
|
|
90
|
+
<View style={style}>
|
|
91
|
+
<NavBar key={pathname} {...props.navBarProps} />
|
|
92
|
+
|
|
93
|
+
<OfflineFallbackScreen screenData={screenData}>
|
|
94
|
+
<RouteManager pathname={pathname} screenData={screenData} />
|
|
95
|
+
</OfflineFallbackScreen>
|
|
96
|
+
</View>
|
|
97
|
+
</ScreenContextProvider>
|
|
130
98
|
);
|
|
131
99
|
}
|
|
@@ -8,11 +8,15 @@ type ActivityIndicatorThemeValues = {
|
|
|
8
8
|
loading_spinner_color: ActivityIndicatorProps["color"];
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type Props = ActivityIndicatorProps & { showAnyway?: boolean };
|
|
12
|
+
|
|
13
|
+
export function SpinnerComponent(props: Props) {
|
|
12
14
|
const { loading_spinner_size, loading_spinner_color } =
|
|
13
15
|
useTheme() as ActivityIndicatorThemeValues;
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
const { showAnyway = false } = props;
|
|
18
|
+
|
|
19
|
+
if (isAndroidTVPlatform() && !showAnyway) {
|
|
16
20
|
return null;
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -9,7 +9,12 @@ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
|
9
9
|
|
|
10
10
|
const { log_debug, log_info, log_error } = loggerLiveImageManager;
|
|
11
11
|
|
|
12
|
-
export type LiveImageManagerEvent =
|
|
12
|
+
export type LiveImageManagerEvent =
|
|
13
|
+
| "onEnd"
|
|
14
|
+
| "onStart"
|
|
15
|
+
| "onError"
|
|
16
|
+
| "onDestroy"
|
|
17
|
+
| "onPlayerClose";
|
|
13
18
|
|
|
14
19
|
export type LiveImageManagerListenerData = {
|
|
15
20
|
id: string;
|
|
@@ -100,6 +105,15 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
this.items = this.items.filter((i) => i !== item);
|
|
108
|
+
|
|
109
|
+
this.invokeListenersUpdate({
|
|
110
|
+
callbackName: "onDestroy",
|
|
111
|
+
event: {
|
|
112
|
+
item,
|
|
113
|
+
playerId: this.currentlyPlaying?.playerId,
|
|
114
|
+
primaryPlayerId: this.primaryPlayer?.playerId,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
103
117
|
};
|
|
104
118
|
|
|
105
119
|
public onViewportEnter = (item: LiveImage) => {
|
|
@@ -351,9 +365,11 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
351
365
|
}, primaryPlayerId: ${this.primaryPlayer?.playerId}`
|
|
352
366
|
);
|
|
353
367
|
|
|
368
|
+
const isCurrentItemEnded = this.currentlyPlaying === item;
|
|
369
|
+
|
|
354
370
|
// TODO: This branch was used when we had play button to toggle replay.
|
|
355
371
|
// Now we mute button instead but we keep it just in case.
|
|
356
|
-
if (IS_ALLOWED_REPLAY && !isTV() &&
|
|
372
|
+
if (IS_ALLOWED_REPLAY && !isTV() && isCurrentItemEnded) {
|
|
357
373
|
this.currentlyPlaying.player.seekTo(0);
|
|
358
374
|
this.currentlyPlaying.player.pause();
|
|
359
375
|
|
|
@@ -362,8 +378,29 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
362
378
|
item.setMode(LiveImageType.Image);
|
|
363
379
|
}
|
|
364
380
|
|
|
381
|
+
// Prevent receiving onEnd event from both `close` and `end` player events
|
|
382
|
+
isCurrentItemEnded &&
|
|
383
|
+
this.invokeListenersUpdate({
|
|
384
|
+
callbackName: "onEnd",
|
|
385
|
+
event: {
|
|
386
|
+
item,
|
|
387
|
+
playerId: this.currentlyPlaying?.playerId,
|
|
388
|
+
primaryPlayerId: this.primaryPlayer?.playerId,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
onLiveImagePlayerClose = (item: LiveImage) => {
|
|
394
|
+
log_debug(
|
|
395
|
+
`onLiveImagePlayerClose: live-image ${playerInfo(
|
|
396
|
+
item.player
|
|
397
|
+
)}, currentPlayingId: ${
|
|
398
|
+
this.currentlyPlaying?.playerId
|
|
399
|
+
}, primaryPlayerId: ${this.primaryPlayer?.playerId}`
|
|
400
|
+
);
|
|
401
|
+
|
|
365
402
|
this.invokeListenersUpdate({
|
|
366
|
-
callbackName: "
|
|
403
|
+
callbackName: "onPlayerClose",
|
|
367
404
|
event: {
|
|
368
405
|
item,
|
|
369
406
|
playerId: this.currentlyPlaying?.playerId,
|
|
@@ -475,6 +512,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
475
512
|
LiveImageManager.instance.onLiveImageError(this, error);
|
|
476
513
|
};
|
|
477
514
|
|
|
515
|
+
onVideoError = (error: Error) => {
|
|
516
|
+
log_error(
|
|
517
|
+
`onVideoError: live-image, fail to play, switching back to image. error: ${
|
|
518
|
+
error?.message
|
|
519
|
+
}, ${playerInfo(this.player)}`
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
LiveImageManager.instance.onLiveImageError(this, error);
|
|
523
|
+
};
|
|
524
|
+
|
|
478
525
|
onVideoEnd = (_event) => {
|
|
479
526
|
log_info(
|
|
480
527
|
`onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
|
|
@@ -484,4 +531,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
484
531
|
|
|
485
532
|
LiveImageManager.instance.onLiveImageEnded(this);
|
|
486
533
|
};
|
|
534
|
+
|
|
535
|
+
onPlayerClose = () => {
|
|
536
|
+
log_info(
|
|
537
|
+
`onPlayerClose: live-image, player closed, switching back to image, ${playerInfo(
|
|
538
|
+
this.player
|
|
539
|
+
)}`
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
// TODO: we maybe want to add onPlayerClose to separate completion behavior
|
|
543
|
+
LiveImageManager.instance.onLiveImagePlayerClose(this);
|
|
544
|
+
LiveImageManager.instance.onLiveImageEnded(this);
|
|
545
|
+
};
|
|
487
546
|
}
|
|
@@ -24,6 +24,7 @@ import { overlayFadeIn } from "./animationUtils";
|
|
|
24
24
|
import { loggerLiveImageComponent } from "./loggerHelper";
|
|
25
25
|
import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
|
|
26
26
|
import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
|
|
27
|
+
import { getAutoplaySettings } from "./utils";
|
|
27
28
|
|
|
28
29
|
const { log_error } = loggerLiveImageComponent;
|
|
29
30
|
|
|
@@ -114,6 +115,11 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
114
115
|
}
|
|
115
116
|
}, [playerId, item?.id]);
|
|
116
117
|
|
|
118
|
+
const { start, end } = React.useMemo(
|
|
119
|
+
() => getAutoplaySettings(item),
|
|
120
|
+
[item?.id]
|
|
121
|
+
);
|
|
122
|
+
|
|
117
123
|
const controller = liveImageItem.getPlayer();
|
|
118
124
|
const player = usePlayer(playerId);
|
|
119
125
|
|
|
@@ -121,6 +127,10 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
121
127
|
ref.current = component;
|
|
122
128
|
};
|
|
123
129
|
|
|
130
|
+
const onStop = React.useCallback(() => {
|
|
131
|
+
LiveImageManager.instance.pauseLiveImage(liveImageItem);
|
|
132
|
+
}, [liveImageItem]);
|
|
133
|
+
|
|
124
134
|
React.useEffect(() => {
|
|
125
135
|
// FIXME - find a more elegant way to disable live-image on cell for measurement
|
|
126
136
|
if (isMeasurement(item)) {
|
|
@@ -147,8 +157,20 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
147
157
|
id: "mute-action-button",
|
|
148
158
|
listener: {
|
|
149
159
|
onLoad: () => {
|
|
160
|
+
start && player.seekTo(start);
|
|
150
161
|
setIsReady(true);
|
|
151
162
|
},
|
|
163
|
+
onVideoProgress: ({ currentTime }) => {
|
|
164
|
+
if (start && currentTime < start) {
|
|
165
|
+
// fixes the case when the video is not seekable in onLoad callback
|
|
166
|
+
player.seekTo(start);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (end && currentTime > end) {
|
|
170
|
+
onStop();
|
|
171
|
+
setIsReady(false);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
152
174
|
onPlayerMute: () => {
|
|
153
175
|
setMuted(true);
|
|
154
176
|
},
|
|
@@ -160,7 +182,7 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
160
182
|
},
|
|
161
183
|
},
|
|
162
184
|
});
|
|
163
|
-
}, [player]);
|
|
185
|
+
}, [player, onStop]);
|
|
164
186
|
|
|
165
187
|
React.useEffect(() => {
|
|
166
188
|
const subscription = AppState.addEventListener(
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import * as PlayerFactory from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
|
|
5
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
6
|
+
import { PlayerLiveImage } from "../PlayerLiveImageComponent";
|
|
7
|
+
|
|
8
|
+
const seekTo = jest.fn();
|
|
9
|
+
|
|
10
|
+
jest.mock(
|
|
11
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/player",
|
|
12
|
+
() => ({
|
|
13
|
+
Player: {
|
|
14
|
+
seekTo,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe("PlayerLiveImageComponent", () => {
|
|
20
|
+
const defaultProps = {
|
|
21
|
+
item: { id: "1", title: "Test", type: { value: "" } },
|
|
22
|
+
style: {},
|
|
23
|
+
playerId: "player1",
|
|
24
|
+
imageKey: "image1",
|
|
25
|
+
audioMutedByDefault: false,
|
|
26
|
+
screenConfig: {},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const autoplay_settings = {
|
|
30
|
+
start_time: "00:10",
|
|
31
|
+
end_time: "00:35",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
beforeAll(() => {
|
|
35
|
+
jest.spyOn(PlayerFactory, "playerFactory").mockImplementation(() => ({
|
|
36
|
+
controller: new Player({}),
|
|
37
|
+
Component: () => null,
|
|
38
|
+
}));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should render correctly with default props", () => {
|
|
42
|
+
const component = render(<PlayerLiveImage {...defaultProps} />);
|
|
43
|
+
|
|
44
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should call seekTo() on video loaded with autoplay settings", () => {
|
|
48
|
+
const props = {
|
|
49
|
+
...defaultProps,
|
|
50
|
+
item: {
|
|
51
|
+
...defaultProps.item,
|
|
52
|
+
extensions: { autoplay_settings },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
render(<PlayerLiveImage {...props} />);
|
|
57
|
+
|
|
58
|
+
// TODO: implement this test
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should call pauseLiveImage() on video reached end timestamp", () => {
|
|
62
|
+
const props = {
|
|
63
|
+
...defaultProps,
|
|
64
|
+
item: {
|
|
65
|
+
...defaultProps.item,
|
|
66
|
+
extensions: { autoplay_settings },
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
render(<PlayerLiveImage {...props} />);
|
|
71
|
+
|
|
72
|
+
// TODO: implement this test
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`PlayerLiveImageComponent should render correctly with default props 1`] = `
|
|
4
|
+
<TrackedView
|
|
5
|
+
onPositionUpdated={[Function]}
|
|
6
|
+
testId="tracked-view-player1-Test"
|
|
7
|
+
>
|
|
8
|
+
<View
|
|
9
|
+
collapsable={false}
|
|
10
|
+
style={
|
|
11
|
+
Object {
|
|
12
|
+
"opacity": 1,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
>
|
|
16
|
+
<View
|
|
17
|
+
accessibilityIgnoresInvertColors={true}
|
|
18
|
+
style={Object {}}
|
|
19
|
+
>
|
|
20
|
+
<Image
|
|
21
|
+
resizeMode="cover"
|
|
22
|
+
source={
|
|
23
|
+
Object {
|
|
24
|
+
"uri": undefined,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
style={
|
|
28
|
+
Array [
|
|
29
|
+
Object {
|
|
30
|
+
"bottom": 0,
|
|
31
|
+
"left": 0,
|
|
32
|
+
"position": "absolute",
|
|
33
|
+
"right": 0,
|
|
34
|
+
"top": 0,
|
|
35
|
+
},
|
|
36
|
+
Object {
|
|
37
|
+
"height": undefined,
|
|
38
|
+
"width": undefined,
|
|
39
|
+
},
|
|
40
|
+
undefined,
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
/>
|
|
44
|
+
</View>
|
|
45
|
+
</View>
|
|
46
|
+
</TrackedView>
|
|
47
|
+
`;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { parseTime, getAutoplaySettings } from "../utils";
|
|
2
|
+
|
|
3
|
+
describe("parseTime", () => {
|
|
4
|
+
it("should return the parsed time in seconds", () => {
|
|
5
|
+
expect(parseTime("01:30")).toBe(90);
|
|
6
|
+
expect(parseTime("00:45")).toBe(45);
|
|
7
|
+
expect(parseTime("10:00")).toBe(600);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should throw error for invalid time formats", () => {
|
|
11
|
+
const error = "Invalid time format";
|
|
12
|
+
expect(() => parseTime("")).toThrow(error);
|
|
13
|
+
expect(() => parseTime("abc")).toThrow(error);
|
|
14
|
+
expect(() => parseTime("12")).toThrow(error);
|
|
15
|
+
expect(() => parseTime(":12")).toThrow(error);
|
|
16
|
+
expect(() => parseTime("00:60")).toThrow(error);
|
|
17
|
+
expect(() => parseTime("-10:00")).toThrow(error);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("getAutoplaySettings", () => {
|
|
22
|
+
it("should return the autoplay settings with parsed start and end times", () => {
|
|
23
|
+
const item = {
|
|
24
|
+
extensions: {
|
|
25
|
+
autoplay_settings: {
|
|
26
|
+
start_time: "01:30",
|
|
27
|
+
end_time: "02:00",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const { start, end } = getAutoplaySettings(item);
|
|
33
|
+
|
|
34
|
+
expect(start).toBe(90);
|
|
35
|
+
expect(end).toBe(120);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return null for missing start or end times", () => {
|
|
39
|
+
const item = {
|
|
40
|
+
extensions: {
|
|
41
|
+
autoplay_settings: {
|
|
42
|
+
start_time: "01:30",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const { start, end } = getAutoplaySettings(item);
|
|
48
|
+
|
|
49
|
+
expect(start).toBe(90);
|
|
50
|
+
expect(end).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should return null for invalid start or end times", () => {
|
|
54
|
+
const item = {
|
|
55
|
+
extensions: {
|
|
56
|
+
autoplay_settings: {
|
|
57
|
+
start_time: "abc",
|
|
58
|
+
end_time: "def",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const { start, end } = getAutoplaySettings(item);
|
|
64
|
+
|
|
65
|
+
expect(start).toBeNull();
|
|
66
|
+
expect(end).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should return null end time if start time greater than end time", () => {
|
|
70
|
+
const item = {
|
|
71
|
+
extensions: {
|
|
72
|
+
autoplay_settings: {
|
|
73
|
+
start_time: "02:00",
|
|
74
|
+
end_time: "01:30",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const { start, end } = getAutoplaySettings(item);
|
|
80
|
+
|
|
81
|
+
expect(start).toBe(120);
|
|
82
|
+
expect(end).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should return null end time if start time equal to end time", () => {
|
|
86
|
+
const item = {
|
|
87
|
+
extensions: {
|
|
88
|
+
autoplay_settings: {
|
|
89
|
+
start_time: "00:30",
|
|
90
|
+
end_time: "00:30",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const { start, end } = getAutoplaySettings(item);
|
|
96
|
+
|
|
97
|
+
expect(start).toBe(30);
|
|
98
|
+
expect(end).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return null for missing autoplay settings", () => {
|
|
102
|
+
const item = {};
|
|
103
|
+
|
|
104
|
+
const result = getAutoplaySettings(item);
|
|
105
|
+
|
|
106
|
+
expect(result.start).toBeNull();
|
|
107
|
+
expect(result.end).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseTimeToSeconds } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/utils";
|
|
2
|
+
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
3
|
+
import { loggerLiveImageComponent } from "./loggerHelper";
|
|
4
|
+
|
|
5
|
+
const { log_error } = loggerLiveImageComponent;
|
|
6
|
+
|
|
7
|
+
type AutoplaySettings = {
|
|
8
|
+
start: number | null;
|
|
9
|
+
end: number | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const parseTime = (time: string): number => {
|
|
13
|
+
const seconds = toNumberWithDefaultZero(parseTimeToSeconds(time));
|
|
14
|
+
|
|
15
|
+
return Math.max(seconds, 0);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getAutoplaySettings = (item: ZappEntry): AutoplaySettings => {
|
|
19
|
+
const autoplay_settings = item?.extensions?.autoplay_settings;
|
|
20
|
+
|
|
21
|
+
let start: number | null = null;
|
|
22
|
+
let end: number | null = null;
|
|
23
|
+
|
|
24
|
+
if (autoplay_settings) {
|
|
25
|
+
const { start_time, end_time } = autoplay_settings;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
start = start_time ? parseTime(start_time) : null;
|
|
29
|
+
end = end_time ? parseTime(end_time) : null;
|
|
30
|
+
|
|
31
|
+
if (start && end && start >= end) {
|
|
32
|
+
end = null;
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
log_error(`PlayerLiveImageComponent: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { start, end };
|
|
40
|
+
};
|