@zezosoft/zezo-ott-react-native-video-player 1.0.4 → 1.0.6
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/lib/module/AdsPlayer/store/adsPlayerStore.js +3 -3
- package/lib/module/AdsPlayer/store/adsPlayerStore.js.map +1 -1
- package/lib/module/VideoPlayer/store/videoPlayerStore.js +3 -3
- package/lib/module/VideoPlayer/store/videoPlayerStore.js.map +1 -1
- package/lib/typescript/src/AdsPlayer/store/adsPlayerStore.d.ts +2 -3
- package/lib/typescript/src/AdsPlayer/store/adsPlayerStore.d.ts.map +1 -1
- package/lib/typescript/src/VideoPlayer/store/videoPlayerStore.d.ts +2 -3
- package/lib/typescript/src/VideoPlayer/store/videoPlayerStore.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/AdsPlayer/AdsPlayer.tsx +0 -311
- package/src/AdsPlayer/MediaControls/AdBottomControls.tsx +0 -191
- package/src/AdsPlayer/MediaControls/AdMediaControls.tsx +0 -104
- package/src/AdsPlayer/MediaControls/AdMediaControlsProvider.tsx +0 -62
- package/src/AdsPlayer/MediaControls/AdMiddleControls.tsx +0 -63
- package/src/AdsPlayer/MediaControls/AdTopControls.tsx +0 -191
- package/src/AdsPlayer/MediaControls/index.ts +0 -5
- package/src/AdsPlayer/components/RotatingLoader.tsx +0 -79
- package/src/AdsPlayer/index.ts +0 -4
- package/src/AdsPlayer/store/adsPlayer.type.ts +0 -29
- package/src/AdsPlayer/store/adsPlayerStore.ts +0 -59
- package/src/AdsPlayer/store/index.ts +0 -2
- package/src/AdsPlayer/utils/adStateReset.ts +0 -29
- package/src/AdsPlayer/utils/controls.ts +0 -69
- package/src/AdsPlayer/utils/useAdControlsAutoHide.ts +0 -32
- package/src/AdsPlayer/utils/useAdInitialization.ts +0 -86
- package/src/AdsPlayer/utils/useAdTracking.ts +0 -89
- package/src/AdsPlayer/utils/useAdsManager.ts +0 -215
- package/src/VideoPlayer/MediaControls/BottomControls.tsx +0 -210
- package/src/VideoPlayer/MediaControls/MediaControls.tsx +0 -30
- package/src/VideoPlayer/MediaControls/MediaControlsProvider.tsx +0 -104
- package/src/VideoPlayer/MediaControls/MiddleControls.tsx +0 -259
- package/src/VideoPlayer/MediaControls/TopControls.tsx +0 -100
- package/src/VideoPlayer/Settings/AudioAndSubtitles.tsx +0 -295
- package/src/VideoPlayer/Settings/Episodes.tsx +0 -297
- package/src/VideoPlayer/Settings/SettingModal.tsx +0 -127
- package/src/VideoPlayer/Settings/SpeedControls.tsx +0 -130
- package/src/VideoPlayer/Settings/VideoPlayerSettings.tsx +0 -141
- package/src/VideoPlayer/VideoPlayerCore.tsx +0 -356
- package/src/VideoPlayer/components/ProgressBar.tsx +0 -211
- package/src/VideoPlayer/components/SkipAndNextControls.tsx +0 -192
- package/src/VideoPlayer/components/SubtitleView.tsx +0 -53
- package/src/VideoPlayer/components/Toast.tsx +0 -61
- package/src/VideoPlayer/context/VideoPlayerConfig.tsx +0 -65
- package/src/VideoPlayer/context/index.ts +0 -5
- package/src/VideoPlayer/index.ts +0 -4
- package/src/VideoPlayer/store/index.ts +0 -2
- package/src/VideoPlayer/store/videoPlayer.type.ts +0 -214
- package/src/VideoPlayer/store/videoPlayerStore.ts +0 -97
- package/src/VideoPlayer/styles/globalStyles.ts +0 -73
- package/src/VideoPlayer/utils/display/Display.ts +0 -10
- package/src/VideoPlayer/utils/display/index.ts +0 -1
- package/src/VideoPlayer/utils/format/index.ts +0 -1
- package/src/VideoPlayer/utils/format/timeFormatter.ts +0 -44
- package/src/VideoPlayer/utils/hooks/index.ts +0 -5
- package/src/VideoPlayer/utils/hooks/useAdEventHandler.ts +0 -95
- package/src/VideoPlayer/utils/hooks/useOrientationLock.ts +0 -29
- package/src/VideoPlayer/utils/hooks/usePauseVideoOnAd.ts +0 -46
- package/src/VideoPlayer/utils/hooks/useVideoPlayerBack.ts +0 -66
- package/src/VideoPlayer/utils/hooks/useVideoResolutions.ts +0 -125
- package/src/VideoPlayer/utils/index.ts +0 -6
- package/src/VideoPlayer/utils/platform/PlatformSelector.ts +0 -13
- package/src/VideoPlayer/utils/platform/index.ts +0 -2
- package/src/VideoPlayer/utils/platform/lockOrientation.ts +0 -40
- package/src/VideoPlayer/utils/player/index.ts +0 -2
- package/src/VideoPlayer/utils/player/playerEvents.ts +0 -97
- package/src/VideoPlayer/utils/player/useWatchReporter.ts +0 -105
- package/src/VideoPlayer/utils/video/index.ts +0 -5
- package/src/VideoPlayer/utils/video/videoControl.ts +0 -185
- package/src/VideoPlayer/utils/video/videoRef.ts +0 -21
- package/src/VideoPlayer/utils/video/videoResume.ts +0 -23
- package/src/VideoPlayer/utils/video/videoSource.ts +0 -23
- package/src/VideoPlayer.tsx +0 -181
- package/src/index.tsx +0 -3
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Linking } from 'react-native';
|
|
2
|
-
import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
|
|
3
|
-
import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
|
|
4
|
-
import { useAdsPlayerStore } from '../store/adsPlayerStore';
|
|
5
|
-
import { createRef } from 'react';
|
|
6
|
-
import type { AdsPlayerRef } from '../AdsPlayer';
|
|
7
|
-
export const adVideoRef = createRef<AdsPlayerRef>();
|
|
8
|
-
|
|
9
|
-
export const getDomainFromUrl = (url: string | undefined): string => {
|
|
10
|
-
if (!url) return '';
|
|
11
|
-
const domain = url.replace(/^https?:\/\//, '').split('/')[0];
|
|
12
|
-
return domain || '';
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const handleAdClickThrough = async (
|
|
16
|
-
ad: VideoAd,
|
|
17
|
-
onAdTracking?: ({
|
|
18
|
-
ad,
|
|
19
|
-
trackingUrl,
|
|
20
|
-
event,
|
|
21
|
-
}: {
|
|
22
|
-
ad: VideoAd;
|
|
23
|
-
trackingUrl: string;
|
|
24
|
-
event: string;
|
|
25
|
-
}) => void
|
|
26
|
-
): Promise<void> => {
|
|
27
|
-
if (!ad.clickThroughUrl) return;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const canOpen = await Linking.canOpenURL(ad.clickThroughUrl);
|
|
31
|
-
if (canOpen) {
|
|
32
|
-
await Linking.openURL(ad.clickThroughUrl);
|
|
33
|
-
if (ad.tracking?.click && onAdTracking) {
|
|
34
|
-
onAdTracking({
|
|
35
|
-
ad,
|
|
36
|
-
trackingUrl: ad.tracking.click,
|
|
37
|
-
event: 'click',
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error('Error opening ad URL:', error);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const hideAdControls = (): void => {
|
|
47
|
-
const { setControlsVisible } = useVideoPlayerStore.getState();
|
|
48
|
-
const { isAdPaused } = useAdsPlayerStore.getState();
|
|
49
|
-
if (isAdPaused) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
setControlsVisible(false);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const showAdControls = (): void => {
|
|
56
|
-
const { setControlsVisible } = useVideoPlayerStore.getState();
|
|
57
|
-
setControlsVisible(true);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const toggleAdControls = (): void => {
|
|
61
|
-
const { controlsVisible, setControlsVisible } =
|
|
62
|
-
useVideoPlayerStore.getState();
|
|
63
|
-
const { isAdPaused } = useAdsPlayerStore.getState();
|
|
64
|
-
if (isAdPaused) {
|
|
65
|
-
setControlsVisible(true);
|
|
66
|
-
} else {
|
|
67
|
-
setControlsVisible(!controlsVisible);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { Animated } from 'react-native';
|
|
3
|
-
import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
|
|
4
|
-
import { useAdsPlayerStore } from '../store/adsPlayerStore';
|
|
5
|
-
import { hideAdControls } from './controls';
|
|
6
|
-
|
|
7
|
-
export const useAdControlsAutoHide = (): Animated.Value => {
|
|
8
|
-
const { controlsVisible, controlsTimer } = useVideoPlayerStore();
|
|
9
|
-
const { isAdPaused } = useAdsPlayerStore();
|
|
10
|
-
const timeoutId = useRef<NodeJS.Timeout | null>(null);
|
|
11
|
-
const fadeAnim = useRef(new Animated.Value(0)).current;
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
Animated.timing(fadeAnim, {
|
|
15
|
-
toValue: controlsVisible ? 1 : 0,
|
|
16
|
-
duration: 200,
|
|
17
|
-
useNativeDriver: true,
|
|
18
|
-
}).start();
|
|
19
|
-
|
|
20
|
-
if (controlsVisible && !isAdPaused) {
|
|
21
|
-
timeoutId.current = setTimeout(() => {
|
|
22
|
-
hideAdControls();
|
|
23
|
-
}, controlsTimer * 1000);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return () => {
|
|
27
|
-
if (timeoutId.current) clearTimeout(timeoutId.current);
|
|
28
|
-
};
|
|
29
|
-
}, [controlsVisible, controlsTimer, fadeAnim, isAdPaused]);
|
|
30
|
-
|
|
31
|
-
return fadeAnim;
|
|
32
|
-
};
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import { useAdsPlayerStore } from '../store/adsPlayerStore';
|
|
3
|
-
import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
|
|
4
|
-
import { handlePause } from '../../VideoPlayer/utils';
|
|
5
|
-
import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
|
|
6
|
-
|
|
7
|
-
interface UseAdInitializationParams {
|
|
8
|
-
onAdTracking?: ({
|
|
9
|
-
ad,
|
|
10
|
-
trackingUrl,
|
|
11
|
-
event,
|
|
12
|
-
}: {
|
|
13
|
-
ad: VideoAd;
|
|
14
|
-
trackingUrl: string;
|
|
15
|
-
event: string;
|
|
16
|
-
}) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Reusable hook for initializing and starting ads
|
|
21
|
-
* Consolidates duplicate ad initialization logic from useAdsManager
|
|
22
|
-
*/
|
|
23
|
-
export const useAdInitialization = ({
|
|
24
|
-
onAdTracking,
|
|
25
|
-
}: UseAdInitializationParams = {}) => {
|
|
26
|
-
const { setCurrentAd, setIsAdPlaying, setIsAdPaused, setResumeTime } =
|
|
27
|
-
useAdsPlayerStore();
|
|
28
|
-
const { setIsPaused } = useVideoPlayerStore();
|
|
29
|
-
|
|
30
|
-
const startAd = useCallback(
|
|
31
|
-
(
|
|
32
|
-
ad: VideoAd,
|
|
33
|
-
options: {
|
|
34
|
-
resumeTime?: number;
|
|
35
|
-
trackImpression?: boolean;
|
|
36
|
-
} = {}
|
|
37
|
-
) => {
|
|
38
|
-
try {
|
|
39
|
-
// Always pause main video first before starting ad
|
|
40
|
-
// Use handlePause to properly pause video and save watch time
|
|
41
|
-
handlePause();
|
|
42
|
-
setIsPaused(true);
|
|
43
|
-
|
|
44
|
-
// Set ad state - ensure ad is ready to play
|
|
45
|
-
setCurrentAd(ad);
|
|
46
|
-
setIsAdPlaying(true);
|
|
47
|
-
setIsAdPaused(false);
|
|
48
|
-
|
|
49
|
-
if (options.resumeTime !== undefined) {
|
|
50
|
-
setResumeTime(options.resumeTime);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Track impression is handled in AdsPlayer component to avoid duplication
|
|
54
|
-
// Only track here if explicitly requested
|
|
55
|
-
if (
|
|
56
|
-
options.trackImpression === true &&
|
|
57
|
-
ad.tracking?.impression &&
|
|
58
|
-
onAdTracking
|
|
59
|
-
) {
|
|
60
|
-
onAdTracking({
|
|
61
|
-
ad,
|
|
62
|
-
trackingUrl: ad.tracking.impression,
|
|
63
|
-
event: 'impression',
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.error('Error starting ad:', error);
|
|
68
|
-
// Reset state on error
|
|
69
|
-
setCurrentAd(null);
|
|
70
|
-
setIsAdPlaying(false);
|
|
71
|
-
setIsAdPaused(false);
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
[
|
|
76
|
-
setCurrentAd,
|
|
77
|
-
setIsAdPlaying,
|
|
78
|
-
setIsAdPaused,
|
|
79
|
-
setIsPaused,
|
|
80
|
-
setResumeTime,
|
|
81
|
-
onAdTracking,
|
|
82
|
-
]
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return { startAd };
|
|
86
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
|
|
3
|
-
|
|
4
|
-
interface UseAdTrackingParams {
|
|
5
|
-
onAdTracking?: ({
|
|
6
|
-
ad,
|
|
7
|
-
trackingUrl,
|
|
8
|
-
event,
|
|
9
|
-
}: {
|
|
10
|
-
ad: VideoAd;
|
|
11
|
-
trackingUrl: string;
|
|
12
|
-
event: string;
|
|
13
|
-
}) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Reusable hook for ad tracking
|
|
18
|
-
* Consolidates ad tracking logic across components
|
|
19
|
-
*/
|
|
20
|
-
export const useAdTracking = ({ onAdTracking }: UseAdTrackingParams = {}) => {
|
|
21
|
-
const trackAdEvent = useCallback(
|
|
22
|
-
(ad: VideoAd, event: string, trackingUrl?: string) => {
|
|
23
|
-
if (!onAdTracking) return;
|
|
24
|
-
|
|
25
|
-
const url =
|
|
26
|
-
trackingUrl || ad.tracking?.[event as keyof typeof ad.tracking];
|
|
27
|
-
if (url) {
|
|
28
|
-
onAdTracking({
|
|
29
|
-
ad,
|
|
30
|
-
trackingUrl: url,
|
|
31
|
-
event,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
[onAdTracking]
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const trackAdImpression = useCallback(
|
|
39
|
-
(ad: VideoAd) => {
|
|
40
|
-
trackAdEvent(ad, 'impression', ad.tracking?.impression);
|
|
41
|
-
},
|
|
42
|
-
[trackAdEvent]
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const trackAdStart = useCallback(
|
|
46
|
-
(ad: VideoAd) => {
|
|
47
|
-
trackAdEvent(ad, 'start', ad.tracking?.start);
|
|
48
|
-
},
|
|
49
|
-
[trackAdEvent]
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const trackAdComplete = useCallback(
|
|
53
|
-
(ad: VideoAd) => {
|
|
54
|
-
trackAdEvent(ad, 'complete', ad.tracking?.complete);
|
|
55
|
-
},
|
|
56
|
-
[trackAdEvent]
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const trackAdSkip = useCallback(
|
|
60
|
-
(ad: VideoAd) => {
|
|
61
|
-
trackAdEvent(ad, 'skip', ad.tracking?.skip);
|
|
62
|
-
},
|
|
63
|
-
[trackAdEvent]
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const trackAdQuartile = useCallback(
|
|
67
|
-
(ad: VideoAd, quartile: 'firstQuartile' | 'midpoint' | 'thirdQuartile') => {
|
|
68
|
-
trackAdEvent(ad, quartile, ad.tracking?.[quartile]);
|
|
69
|
-
},
|
|
70
|
-
[trackAdEvent]
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const trackAdClick = useCallback(
|
|
74
|
-
(ad: VideoAd) => {
|
|
75
|
-
trackAdEvent(ad, 'click', ad.tracking?.click);
|
|
76
|
-
},
|
|
77
|
-
[trackAdEvent]
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
trackAdEvent,
|
|
82
|
-
trackAdImpression,
|
|
83
|
-
trackAdStart,
|
|
84
|
-
trackAdComplete,
|
|
85
|
-
trackAdSkip,
|
|
86
|
-
trackAdQuartile,
|
|
87
|
-
trackAdClick,
|
|
88
|
-
};
|
|
89
|
-
};
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { useVideoPlayerStore } from '../../VideoPlayer/store/videoPlayerStore';
|
|
3
|
-
import { useAdsPlayerStore } from '../store/adsPlayerStore';
|
|
4
|
-
import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
|
|
5
|
-
import { useAdInitialization } from './useAdInitialization';
|
|
6
|
-
|
|
7
|
-
export const useAdsManager = (
|
|
8
|
-
ads: VideoAd[] = [],
|
|
9
|
-
onAdTracking?: ({
|
|
10
|
-
ad,
|
|
11
|
-
trackingUrl,
|
|
12
|
-
event,
|
|
13
|
-
}: {
|
|
14
|
-
ad: VideoAd;
|
|
15
|
-
trackingUrl: string;
|
|
16
|
-
event: string;
|
|
17
|
-
}) => void
|
|
18
|
-
) => {
|
|
19
|
-
const { currentTime, activeTrack } = useVideoPlayerStore();
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
currentAd,
|
|
23
|
-
isAdPlaying,
|
|
24
|
-
pendingMidRollAds,
|
|
25
|
-
addPendingMidRollAd,
|
|
26
|
-
clearPendingMidRollAds,
|
|
27
|
-
} = useAdsPlayerStore();
|
|
28
|
-
|
|
29
|
-
const { startAd } = useAdInitialization({ onAdTracking });
|
|
30
|
-
|
|
31
|
-
const adsInitializedRef = useRef(false);
|
|
32
|
-
const previousAdsRef = useRef<string>('');
|
|
33
|
-
const playedMidRollAdsRef = useRef<Set<string>>(new Set());
|
|
34
|
-
const playedPreRollAdsRef = useRef<Set<string>>(new Set());
|
|
35
|
-
const playedPostRollAdsRef = useRef<Set<string>>(new Set());
|
|
36
|
-
const previousTimeRef = useRef<number>(0);
|
|
37
|
-
const isResettingRef = useRef(false);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
// Prevent infinite loops
|
|
41
|
-
if (isResettingRef.current) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const adsKey = JSON.stringify(ads?.map((ad) => ad.id) || []);
|
|
46
|
-
const hadAds = previousAdsRef.current !== '';
|
|
47
|
-
const hasAds = ads && ads.length > 0;
|
|
48
|
-
|
|
49
|
-
if (hasAds && adsKey !== previousAdsRef.current) {
|
|
50
|
-
previousAdsRef.current = adsKey;
|
|
51
|
-
|
|
52
|
-
// Don't initialize ads if it's a trailer
|
|
53
|
-
if (activeTrack?.isTrailer) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const preRollAds = ads.filter((ad) => ad.position === 'pre');
|
|
58
|
-
const midRollAds = ads.filter((ad) => ad.position === 'mid');
|
|
59
|
-
|
|
60
|
-
if (preRollAds.length > 0 && preRollAds[0] && !currentAd) {
|
|
61
|
-
try {
|
|
62
|
-
playedPreRollAdsRef.current.add(preRollAds[0].id);
|
|
63
|
-
startAd(preRollAds[0], { trackImpression: false }); // Impression tracked in AdsPlayer
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Error starting pre-roll ad:', error);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!adsInitializedRef.current) {
|
|
70
|
-
midRollAds.forEach((ad) => {
|
|
71
|
-
if (ad.time !== undefined) {
|
|
72
|
-
addPendingMidRollAd(ad);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
adsInitializedRef.current = true;
|
|
76
|
-
}
|
|
77
|
-
} else if (!hasAds && hadAds) {
|
|
78
|
-
// Only reset when ads change from non-empty to empty
|
|
79
|
-
isResettingRef.current = true;
|
|
80
|
-
|
|
81
|
-
// Clear all ad state when ads array becomes empty
|
|
82
|
-
adsInitializedRef.current = false;
|
|
83
|
-
previousAdsRef.current = '';
|
|
84
|
-
clearPendingMidRollAds();
|
|
85
|
-
|
|
86
|
-
// Reset played ads tracking
|
|
87
|
-
playedMidRollAdsRef.current.clear();
|
|
88
|
-
playedPreRollAdsRef.current.clear();
|
|
89
|
-
playedPostRollAdsRef.current.clear();
|
|
90
|
-
|
|
91
|
-
// If an ad is currently playing, stop it
|
|
92
|
-
const {
|
|
93
|
-
isAdPlaying: currentIsAdPlaying,
|
|
94
|
-
currentAd: currentAdState,
|
|
95
|
-
resetAdsStore,
|
|
96
|
-
} = useAdsPlayerStore.getState();
|
|
97
|
-
if (currentIsAdPlaying || currentAdState) {
|
|
98
|
-
resetAdsStore();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Reset the flag after a short delay to allow state to settle
|
|
102
|
-
setTimeout(() => {
|
|
103
|
-
isResettingRef.current = false;
|
|
104
|
-
}, 0);
|
|
105
|
-
} else if (!hasAds) {
|
|
106
|
-
// If ads are empty and were already empty, just update the ref
|
|
107
|
-
previousAdsRef.current = '';
|
|
108
|
-
}
|
|
109
|
-
}, [
|
|
110
|
-
ads,
|
|
111
|
-
currentAd,
|
|
112
|
-
addPendingMidRollAd,
|
|
113
|
-
clearPendingMidRollAds,
|
|
114
|
-
startAd,
|
|
115
|
-
activeTrack?.isTrailer,
|
|
116
|
-
]);
|
|
117
|
-
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
// Don't trigger mid-roll ads if it's a trailer
|
|
120
|
-
if (activeTrack?.isTrailer) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
!isAdPlaying &&
|
|
126
|
-
pendingMidRollAds.length > 0 &&
|
|
127
|
-
currentTime > 0 &&
|
|
128
|
-
!currentAd
|
|
129
|
-
) {
|
|
130
|
-
const previousTime = previousTimeRef.current;
|
|
131
|
-
const timeDifference = Math.abs(currentTime - previousTime);
|
|
132
|
-
const isSeeking = timeDifference > 2; // If time jumps more than 2 seconds, it's a seek
|
|
133
|
-
|
|
134
|
-
// Check for ads that should be triggered
|
|
135
|
-
let nextMidRollAd = pendingMidRollAds.find(
|
|
136
|
-
(ad) =>
|
|
137
|
-
ad.time &&
|
|
138
|
-
!playedMidRollAdsRef.current.has(ad.id) &&
|
|
139
|
-
currentTime >= ad.time - 1 &&
|
|
140
|
-
currentTime <= ad.time + 1
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// If seeking, check if we skipped past any ads
|
|
144
|
-
if (!nextMidRollAd && isSeeking && previousTime < currentTime) {
|
|
145
|
-
const skippedAd = pendingMidRollAds.find(
|
|
146
|
-
(ad) =>
|
|
147
|
-
ad.time &&
|
|
148
|
-
!playedMidRollAdsRef.current.has(ad.id) &&
|
|
149
|
-
ad.time > previousTime &&
|
|
150
|
-
ad.time <= currentTime
|
|
151
|
-
);
|
|
152
|
-
if (skippedAd) {
|
|
153
|
-
nextMidRollAd = skippedAd;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (nextMidRollAd) {
|
|
158
|
-
try {
|
|
159
|
-
playedMidRollAdsRef.current.add(nextMidRollAd.id);
|
|
160
|
-
const timeBeforeAd = isSeeking
|
|
161
|
-
? nextMidRollAd.time || currentTime
|
|
162
|
-
: currentTime;
|
|
163
|
-
startAd(nextMidRollAd, {
|
|
164
|
-
resumeTime: timeBeforeAd,
|
|
165
|
-
trackImpression: false, // Impression tracked in AdsPlayer
|
|
166
|
-
});
|
|
167
|
-
const remainingAds = pendingMidRollAds.filter(
|
|
168
|
-
(ad) => ad.id !== nextMidRollAd.id
|
|
169
|
-
);
|
|
170
|
-
clearPendingMidRollAds();
|
|
171
|
-
remainingAds.forEach((ad) => addPendingMidRollAd(ad));
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error('Error starting mid-roll ad:', error);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Update previous time
|
|
178
|
-
previousTimeRef.current = currentTime;
|
|
179
|
-
} else if (currentTime > 0) {
|
|
180
|
-
// Update previous time even if conditions aren't met
|
|
181
|
-
previousTimeRef.current = currentTime;
|
|
182
|
-
}
|
|
183
|
-
}, [
|
|
184
|
-
currentTime,
|
|
185
|
-
isAdPlaying,
|
|
186
|
-
pendingMidRollAds,
|
|
187
|
-
currentAd,
|
|
188
|
-
ads,
|
|
189
|
-
addPendingMidRollAd,
|
|
190
|
-
clearPendingMidRollAds,
|
|
191
|
-
startAd,
|
|
192
|
-
activeTrack?.isTrailer,
|
|
193
|
-
]);
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
checkPostRollAds: () => {
|
|
197
|
-
try {
|
|
198
|
-
// Don't show post-roll ads if it's a trailer
|
|
199
|
-
if (activeTrack?.isTrailer) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
const postRollAds = ads.filter((ad) => ad.position === 'post');
|
|
203
|
-
if (postRollAds.length > 0 && postRollAds[0]) {
|
|
204
|
-
playedPostRollAdsRef.current.add(postRollAds[0].id);
|
|
205
|
-
startAd(postRollAds[0], { trackImpression: false }); // Impression tracked in AdsPlayer
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
return false;
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error('Error checking post-roll ads:', error);
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
};
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
StyleSheet,
|
|
4
|
-
Text,
|
|
5
|
-
TouchableOpacity,
|
|
6
|
-
View,
|
|
7
|
-
type GestureResponderEvent,
|
|
8
|
-
type TextStyle,
|
|
9
|
-
type ViewStyle,
|
|
10
|
-
} from 'react-native';
|
|
11
|
-
import { scale, verticalScale, moderateScale } from 'react-native-size-matters';
|
|
12
|
-
import { RFValue } from 'react-native-responsive-fontsize';
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
Settings as SettingsIcon,
|
|
16
|
-
SlidersHorizontal as SpeedIcon,
|
|
17
|
-
Subtitles as SubtitlesIcon,
|
|
18
|
-
ListChecks as EpisodesIcon,
|
|
19
|
-
} from 'lucide-react-native';
|
|
20
|
-
|
|
21
|
-
import { useVideoPlayerStore } from '../store/videoPlayerStore';
|
|
22
|
-
import { useAdsPlayerStore } from '../../AdsPlayer/store/adsPlayerStore';
|
|
23
|
-
import { videoRef } from '../utils';
|
|
24
|
-
import type { SettingsAction } from '../store/videoPlayer.type';
|
|
25
|
-
import { formatTime } from '../utils';
|
|
26
|
-
import ProgressBar from '../components/ProgressBar';
|
|
27
|
-
import { useVideoPlayerConfig } from '../context';
|
|
28
|
-
|
|
29
|
-
type ControlButtonProps = {
|
|
30
|
-
onPress: (event: GestureResponderEvent) => void;
|
|
31
|
-
icon: React.ReactNode;
|
|
32
|
-
label: string;
|
|
33
|
-
accessibilityLabel?: string;
|
|
34
|
-
buttonStyle?: ViewStyle;
|
|
35
|
-
labelStyle?: TextStyle;
|
|
36
|
-
colors: {
|
|
37
|
-
text: string;
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const ControlButton: React.FC<ControlButtonProps> = ({
|
|
42
|
-
onPress,
|
|
43
|
-
icon,
|
|
44
|
-
label,
|
|
45
|
-
accessibilityLabel,
|
|
46
|
-
buttonStyle,
|
|
47
|
-
labelStyle,
|
|
48
|
-
colors,
|
|
49
|
-
}) => (
|
|
50
|
-
<TouchableOpacity
|
|
51
|
-
onPress={onPress}
|
|
52
|
-
style={[styles.button, buttonStyle]}
|
|
53
|
-
activeOpacity={0.8}
|
|
54
|
-
accessibilityLabel={accessibilityLabel ?? label}
|
|
55
|
-
>
|
|
56
|
-
{icon}
|
|
57
|
-
<Text style={[styles.buttonText, labelStyle, { color: colors.text }]}>
|
|
58
|
-
{label}
|
|
59
|
-
</Text>
|
|
60
|
-
</TouchableOpacity>
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const BottomControls = () => {
|
|
64
|
-
const {
|
|
65
|
-
duration,
|
|
66
|
-
currentTime,
|
|
67
|
-
activeTrack,
|
|
68
|
-
playableDuration,
|
|
69
|
-
setSettingsModal,
|
|
70
|
-
setIsPaused,
|
|
71
|
-
setControlsVisible,
|
|
72
|
-
playBackRateLabel,
|
|
73
|
-
} = useVideoPlayerStore();
|
|
74
|
-
|
|
75
|
-
const { pendingMidRollAds } = useAdsPlayerStore();
|
|
76
|
-
const { colors } = useVideoPlayerConfig();
|
|
77
|
-
const getRemainingTime = useCallback(
|
|
78
|
-
(totalDuration: number, currentPos: number) => {
|
|
79
|
-
const remainingTime = totalDuration - currentPos;
|
|
80
|
-
return formatTime(Math.max(remainingTime, 0));
|
|
81
|
-
},
|
|
82
|
-
[]
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const onSeek = (value: number) => {
|
|
86
|
-
videoRef.current?.seek(value);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const onOpenSettings = (type: SettingsAction) => {
|
|
90
|
-
setSettingsModal({ action: type, isVisible: true });
|
|
91
|
-
setIsPaused(true);
|
|
92
|
-
setControlsVisible(false);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// Memoize ad markers to avoid recalculation on every render
|
|
96
|
-
const adMarkers = useMemo(
|
|
97
|
-
() =>
|
|
98
|
-
pendingMidRollAds
|
|
99
|
-
.filter((ad) => ad.time !== undefined && ad.time > 0)
|
|
100
|
-
.map((ad) => ad.time as number)
|
|
101
|
-
.sort((a, b) => a - b),
|
|
102
|
-
[pendingMidRollAds]
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// Memoize time text style
|
|
106
|
-
const timeTextStyle = useMemo(
|
|
107
|
-
() => [styles.timeText, { color: colors.text }],
|
|
108
|
-
[colors.text]
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<View style={styles.root}>
|
|
113
|
-
{/* Progress Bar Section */}
|
|
114
|
-
<View style={styles.sliderContainer}>
|
|
115
|
-
<View style={styles.sliderWrapper}>
|
|
116
|
-
<ProgressBar
|
|
117
|
-
duration={duration}
|
|
118
|
-
currentTime={currentTime}
|
|
119
|
-
bufferedTime={playableDuration}
|
|
120
|
-
onSeek={onSeek}
|
|
121
|
-
adMarkers={adMarkers}
|
|
122
|
-
/>
|
|
123
|
-
</View>
|
|
124
|
-
<Text style={timeTextStyle} numberOfLines={1}>
|
|
125
|
-
{getRemainingTime(duration, currentTime)}
|
|
126
|
-
</Text>
|
|
127
|
-
</View>
|
|
128
|
-
|
|
129
|
-
{/* Action Buttons */}
|
|
130
|
-
<View style={styles.buttonsContainer}>
|
|
131
|
-
<ControlButton
|
|
132
|
-
onPress={() => onOpenSettings('speed')}
|
|
133
|
-
icon={<SpeedIcon size={moderateScale(18)} color={colors.text} />}
|
|
134
|
-
label={
|
|
135
|
-
playBackRateLabel && playBackRateLabel !== 'Normal'
|
|
136
|
-
? `Speed (${playBackRateLabel})`
|
|
137
|
-
: 'Speed'
|
|
138
|
-
}
|
|
139
|
-
colors={colors}
|
|
140
|
-
/>
|
|
141
|
-
<ControlButton
|
|
142
|
-
onPress={() => onOpenSettings('audioOrSubtitle')}
|
|
143
|
-
icon={<SubtitlesIcon size={moderateScale(19)} color={colors.text} />}
|
|
144
|
-
label="Audio & Subtitles"
|
|
145
|
-
colors={colors}
|
|
146
|
-
/>
|
|
147
|
-
<ControlButton
|
|
148
|
-
onPress={() => onOpenSettings('settings')}
|
|
149
|
-
icon={<SettingsIcon size={moderateScale(18)} color={colors.text} />}
|
|
150
|
-
label="Settings"
|
|
151
|
-
colors={colors}
|
|
152
|
-
/>
|
|
153
|
-
{!activeTrack?.isTrailer && activeTrack?.type === 'series' && (
|
|
154
|
-
<ControlButton
|
|
155
|
-
onPress={() => onOpenSettings('episodes')}
|
|
156
|
-
icon={<EpisodesIcon size={moderateScale(17)} color={colors.text} />}
|
|
157
|
-
label="Episodes"
|
|
158
|
-
colors={colors}
|
|
159
|
-
/>
|
|
160
|
-
)}
|
|
161
|
-
</View>
|
|
162
|
-
</View>
|
|
163
|
-
);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export default BottomControls;
|
|
167
|
-
|
|
168
|
-
const styles = StyleSheet.create({
|
|
169
|
-
root: {
|
|
170
|
-
paddingTop: verticalScale(8),
|
|
171
|
-
paddingBottom: verticalScale(4),
|
|
172
|
-
},
|
|
173
|
-
sliderContainer: {
|
|
174
|
-
flexDirection: 'row',
|
|
175
|
-
alignItems: 'center',
|
|
176
|
-
paddingHorizontal: scale(12),
|
|
177
|
-
marginBottom: verticalScale(4),
|
|
178
|
-
},
|
|
179
|
-
sliderWrapper: {
|
|
180
|
-
flex: 1,
|
|
181
|
-
minWidth: 0,
|
|
182
|
-
},
|
|
183
|
-
timeText: {
|
|
184
|
-
minWidth: scale(52),
|
|
185
|
-
maxWidth: scale(56),
|
|
186
|
-
textAlign: 'right',
|
|
187
|
-
marginLeft: scale(6),
|
|
188
|
-
fontSize: RFValue(12),
|
|
189
|
-
fontWeight: '500',
|
|
190
|
-
},
|
|
191
|
-
buttonsContainer: {
|
|
192
|
-
flexDirection: 'row',
|
|
193
|
-
flexWrap: 'wrap',
|
|
194
|
-
justifyContent: 'center',
|
|
195
|
-
alignContent: 'center',
|
|
196
|
-
paddingHorizontal: scale(10),
|
|
197
|
-
gap: scale(4),
|
|
198
|
-
},
|
|
199
|
-
button: {
|
|
200
|
-
flexDirection: 'row',
|
|
201
|
-
alignItems: 'center',
|
|
202
|
-
paddingHorizontal: scale(12),
|
|
203
|
-
paddingVertical: verticalScale(6),
|
|
204
|
-
borderRadius: moderateScale(8),
|
|
205
|
-
},
|
|
206
|
-
buttonText: {
|
|
207
|
-
fontSize: RFValue(13),
|
|
208
|
-
marginLeft: scale(6),
|
|
209
|
-
},
|
|
210
|
-
});
|