@zezosoft/zezo-ott-react-native-video-player 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -2
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zezosoft/zezo-ott-react-native-video-player",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Production-ready React Native OTT video player library for Android & iOS. Features: playlists, seasons, auto-next playback, subtitles (SRT/VTT), custom theming, analytics tracking, fullscreen mode, gesture controls, ads player (pre-roll/mid-roll/post-roll), TypeScript support, and centralized state management. Perfect for streaming apps, video platforms, and OTT services.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"ios",
|
|
20
20
|
"lib",
|
|
21
21
|
"react-native.config.js",
|
|
22
|
-
"src",
|
|
23
22
|
"!android/build",
|
|
24
23
|
"!android/gradle",
|
|
25
24
|
"!android/gradlew",
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useEffect,
|
|
3
|
-
useRef,
|
|
4
|
-
useImperativeHandle,
|
|
5
|
-
forwardRef,
|
|
6
|
-
useCallback,
|
|
7
|
-
useMemo,
|
|
8
|
-
} from 'react';
|
|
9
|
-
import { View } from 'react-native';
|
|
10
|
-
import globalStyles from '../VideoPlayer/styles/globalStyles';
|
|
11
|
-
import Video, {
|
|
12
|
-
type OnBufferData,
|
|
13
|
-
type OnLoadStartData,
|
|
14
|
-
type OnProgressData,
|
|
15
|
-
type VideoRef,
|
|
16
|
-
} from 'react-native-video';
|
|
17
|
-
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
18
|
-
import { useAdsPlayerStore } from './store/adsPlayerStore';
|
|
19
|
-
import type { VideoAd } from '../VideoPlayer/store/videoPlayer.type';
|
|
20
|
-
import { AdMediaControlsProvider } from './MediaControls';
|
|
21
|
-
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
22
|
-
import { runOnJS } from 'react-native-reanimated';
|
|
23
|
-
import { toggleAdControls, showAdControls } from './utils/controls';
|
|
24
|
-
import { resetAdState } from './utils/adStateReset';
|
|
25
|
-
import { useAdTracking } from './utils/useAdTracking';
|
|
26
|
-
|
|
27
|
-
export interface AdsPlayerProps {
|
|
28
|
-
onAdEnd: (ad: VideoAd) => void;
|
|
29
|
-
onAdSkip?: (ad: VideoAd) => void;
|
|
30
|
-
onAdError?: (error: Error, ad: VideoAd) => void;
|
|
31
|
-
onAdTracking?: ({
|
|
32
|
-
ad,
|
|
33
|
-
trackingUrl,
|
|
34
|
-
event,
|
|
35
|
-
}: {
|
|
36
|
-
ad: VideoAd;
|
|
37
|
-
trackingUrl: string;
|
|
38
|
-
event: string;
|
|
39
|
-
}) => void;
|
|
40
|
-
onClose?: () => void;
|
|
41
|
-
insets: EdgeInsets;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface AdsPlayerRef {
|
|
45
|
-
seek: (time: number) => void;
|
|
46
|
-
getCurrentTime: () => number;
|
|
47
|
-
getDuration: () => number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const AdsPlayer = forwardRef<AdsPlayerRef, AdsPlayerProps>(
|
|
51
|
-
({ onAdEnd, onAdSkip, onAdError, onAdTracking, onClose, insets }, ref) => {
|
|
52
|
-
const {
|
|
53
|
-
currentAd,
|
|
54
|
-
adDuration,
|
|
55
|
-
adCurrentTime,
|
|
56
|
-
isAdPaused,
|
|
57
|
-
isAdMuted,
|
|
58
|
-
isAdPlaying,
|
|
59
|
-
setIsAdPaused,
|
|
60
|
-
setAdCurrentTime,
|
|
61
|
-
setAdDuration,
|
|
62
|
-
setShowSkipButton,
|
|
63
|
-
setIsAdBuffering,
|
|
64
|
-
} = useAdsPlayerStore();
|
|
65
|
-
const adVideoRef = useRef<VideoRef>(null);
|
|
66
|
-
const trackedQuartilesRef = useRef({
|
|
67
|
-
firstQuartile: false,
|
|
68
|
-
midpoint: false,
|
|
69
|
-
thirdQuartile: false,
|
|
70
|
-
});
|
|
71
|
-
const trackedImpressionRef = useRef<string | null>(null);
|
|
72
|
-
const {
|
|
73
|
-
trackAdImpression,
|
|
74
|
-
trackAdStart,
|
|
75
|
-
trackAdComplete,
|
|
76
|
-
trackAdQuartile,
|
|
77
|
-
} = useAdTracking({ onAdTracking });
|
|
78
|
-
|
|
79
|
-
const trigger = useCallback(() => {
|
|
80
|
-
toggleAdControls();
|
|
81
|
-
}, []);
|
|
82
|
-
|
|
83
|
-
const tapGesture = useMemo(
|
|
84
|
-
() =>
|
|
85
|
-
Gesture.Tap()
|
|
86
|
-
.onEnd(() => {
|
|
87
|
-
runOnJS(trigger)();
|
|
88
|
-
})
|
|
89
|
-
.maxDuration(250),
|
|
90
|
-
[trigger]
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
useImperativeHandle(ref, () => ({
|
|
94
|
-
seek: (time: number) => {
|
|
95
|
-
adVideoRef.current?.seek(time);
|
|
96
|
-
},
|
|
97
|
-
getCurrentTime: () => adCurrentTime,
|
|
98
|
-
getDuration: () => adDuration,
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
// Track impression only once per ad
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (
|
|
104
|
-
currentAd &&
|
|
105
|
-
isAdPlaying &&
|
|
106
|
-
trackedImpressionRef.current !== currentAd.id
|
|
107
|
-
) {
|
|
108
|
-
try {
|
|
109
|
-
trackedImpressionRef.current = currentAd.id;
|
|
110
|
-
trackAdImpression(currentAd);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error('Error in ad impression tracking:', error);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}, [currentAd, isAdPlaying, trackAdImpression]);
|
|
116
|
-
|
|
117
|
-
// Separate effect for ensuring ad is not paused
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (currentAd && isAdPlaying) {
|
|
120
|
-
setIsAdPaused(false);
|
|
121
|
-
showAdControls();
|
|
122
|
-
}
|
|
123
|
-
}, [currentAd, isAdPlaying, setIsAdPaused]);
|
|
124
|
-
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
if (currentAd?.skippable) {
|
|
127
|
-
if (currentAd.skipAfter > 0) {
|
|
128
|
-
const timer = setTimeout(() => {
|
|
129
|
-
setShowSkipButton(true);
|
|
130
|
-
}, currentAd.skipAfter * 1000);
|
|
131
|
-
return () => clearTimeout(timer);
|
|
132
|
-
}
|
|
133
|
-
setShowSkipButton(true);
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
setShowSkipButton(false);
|
|
137
|
-
return undefined;
|
|
138
|
-
}, [currentAd, setShowSkipButton]);
|
|
139
|
-
|
|
140
|
-
const handleAdProgress = (data: OnProgressData) => {
|
|
141
|
-
setAdCurrentTime(data.currentTime);
|
|
142
|
-
setAdDuration(data.seekableDuration || 0);
|
|
143
|
-
|
|
144
|
-
// Reset buffering state when progress updates (playback has resumed)
|
|
145
|
-
const { isAdBuffering } = useAdsPlayerStore.getState();
|
|
146
|
-
if (isAdBuffering) {
|
|
147
|
-
setIsAdBuffering(false);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (currentAd && adDuration > 0) {
|
|
151
|
-
const progress = data.currentTime / adDuration;
|
|
152
|
-
if (progress >= 0.25 && !trackedQuartilesRef.current.firstQuartile) {
|
|
153
|
-
trackedQuartilesRef.current.firstQuartile = true;
|
|
154
|
-
trackAdQuartile(currentAd, 'firstQuartile');
|
|
155
|
-
}
|
|
156
|
-
if (progress >= 0.5 && !trackedQuartilesRef.current.midpoint) {
|
|
157
|
-
trackedQuartilesRef.current.midpoint = true;
|
|
158
|
-
trackAdQuartile(currentAd, 'midpoint');
|
|
159
|
-
}
|
|
160
|
-
if (progress >= 0.75 && !trackedQuartilesRef.current.thirdQuartile) {
|
|
161
|
-
trackedQuartilesRef.current.thirdQuartile = true;
|
|
162
|
-
trackAdQuartile(currentAd, 'thirdQuartile');
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const handleAdLoadStart = (_: OnLoadStartData) => {
|
|
168
|
-
setIsAdBuffering(true);
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const handleAdLoad = () => {
|
|
172
|
-
try {
|
|
173
|
-
setIsAdBuffering(false);
|
|
174
|
-
if (currentAd) {
|
|
175
|
-
setAdDuration(currentAd.duration);
|
|
176
|
-
trackAdStart(currentAd);
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
console.error('Error in handleAdLoad:', error);
|
|
180
|
-
if (currentAd && onAdError) {
|
|
181
|
-
onAdError(
|
|
182
|
-
error instanceof Error ? error : new Error('Ad load failed'),
|
|
183
|
-
currentAd
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const handleAdEnd = () => {
|
|
190
|
-
if (!currentAd) return;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
trackAdComplete(currentAd);
|
|
194
|
-
resetAdState();
|
|
195
|
-
trackedQuartilesRef.current = {
|
|
196
|
-
firstQuartile: false,
|
|
197
|
-
midpoint: false,
|
|
198
|
-
thirdQuartile: false,
|
|
199
|
-
};
|
|
200
|
-
// Reset impression tracking for next ad
|
|
201
|
-
trackedImpressionRef.current = null;
|
|
202
|
-
onAdEnd(currentAd);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
console.error('Error in handleAdEnd:', error);
|
|
205
|
-
resetAdState();
|
|
206
|
-
trackedImpressionRef.current = null;
|
|
207
|
-
onAdEnd(currentAd);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const handleAdError = (error: {
|
|
212
|
-
error: {
|
|
213
|
-
code?: string;
|
|
214
|
-
localizedDescription?: string;
|
|
215
|
-
errorString?: string;
|
|
216
|
-
};
|
|
217
|
-
}) => {
|
|
218
|
-
if (!currentAd) return;
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
const errorMessage =
|
|
222
|
-
error.error?.localizedDescription ||
|
|
223
|
-
error.error?.errorString ||
|
|
224
|
-
'Ad playback failed';
|
|
225
|
-
console.error('Ad playback error:', errorMessage, error);
|
|
226
|
-
|
|
227
|
-
resetAdState();
|
|
228
|
-
// Reset impression tracking on error
|
|
229
|
-
trackedImpressionRef.current = null;
|
|
230
|
-
|
|
231
|
-
if (onAdError) {
|
|
232
|
-
onAdError(new Error(errorMessage), currentAd);
|
|
233
|
-
} else {
|
|
234
|
-
// Fallback: treat error as ad end
|
|
235
|
-
onAdEnd(currentAd);
|
|
236
|
-
}
|
|
237
|
-
} catch (err) {
|
|
238
|
-
console.error('Error in handleAdError:', err);
|
|
239
|
-
resetAdState();
|
|
240
|
-
trackedImpressionRef.current = null;
|
|
241
|
-
if (currentAd && onAdEnd) {
|
|
242
|
-
onAdEnd(currentAd);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// Memoize video style for performance (no scale to avoid layout overflow)
|
|
248
|
-
const videoStyle = useMemo(() => globalStyles.absoluteFill, []);
|
|
249
|
-
|
|
250
|
-
// Memoize container style — overflow hidden to prevent layout glitches
|
|
251
|
-
const containerStyle = useMemo(
|
|
252
|
-
() => [
|
|
253
|
-
globalStyles.absoluteFill,
|
|
254
|
-
{ backgroundColor: '#000', overflow: 'hidden' as const },
|
|
255
|
-
],
|
|
256
|
-
[]
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
if (!currentAd || !isAdPlaying) {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<GestureDetector gesture={tapGesture}>
|
|
265
|
-
<View
|
|
266
|
-
style={containerStyle}
|
|
267
|
-
collapsable={false}
|
|
268
|
-
pointerEvents="box-none"
|
|
269
|
-
>
|
|
270
|
-
<AdMediaControlsProvider
|
|
271
|
-
onAdSkip={onAdSkip}
|
|
272
|
-
onAdTracking={onAdTracking}
|
|
273
|
-
onClose={onClose}
|
|
274
|
-
insets={insets}
|
|
275
|
-
>
|
|
276
|
-
<Video
|
|
277
|
-
ref={adVideoRef}
|
|
278
|
-
source={{ uri: currentAd.source }}
|
|
279
|
-
paused={isAdPaused}
|
|
280
|
-
muted={isAdMuted}
|
|
281
|
-
onProgress={handleAdProgress}
|
|
282
|
-
onLoad={handleAdLoad}
|
|
283
|
-
onEnd={handleAdEnd}
|
|
284
|
-
onError={handleAdError}
|
|
285
|
-
onBuffer={({ isBuffering }: OnBufferData) => {
|
|
286
|
-
if (isBuffering) {
|
|
287
|
-
setIsAdBuffering(true);
|
|
288
|
-
} else {
|
|
289
|
-
setTimeout(() => setIsAdBuffering(false), 150);
|
|
290
|
-
}
|
|
291
|
-
}}
|
|
292
|
-
onLoadStart={handleAdLoadStart}
|
|
293
|
-
resizeMode="cover"
|
|
294
|
-
style={videoStyle}
|
|
295
|
-
controls={false}
|
|
296
|
-
playInBackground={false}
|
|
297
|
-
playWhenInactive={false}
|
|
298
|
-
pointerEvents="box-none"
|
|
299
|
-
ignoreSilentSwitch="ignore"
|
|
300
|
-
progressUpdateInterval={250}
|
|
301
|
-
/>
|
|
302
|
-
</AdMediaControlsProvider>
|
|
303
|
-
</View>
|
|
304
|
-
</GestureDetector>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
AdsPlayer.displayName = 'AdsPlayer';
|
|
310
|
-
|
|
311
|
-
export default AdsPlayer;
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import React, { memo, useCallback, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Animated,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Text,
|
|
6
|
-
View,
|
|
7
|
-
TouchableOpacity,
|
|
8
|
-
} from 'react-native';
|
|
9
|
-
import { scale, moderateScale } from 'react-native-size-matters';
|
|
10
|
-
import { RFValue } from 'react-native-responsive-fontsize';
|
|
11
|
-
import { ExternalLink } from 'lucide-react-native';
|
|
12
|
-
import { useAdsPlayerStore } from '../store/adsPlayerStore';
|
|
13
|
-
import { useAdControls } from './AdMediaControlsProvider';
|
|
14
|
-
import { useAdControlsAutoHide } from '../utils/useAdControlsAutoHide';
|
|
15
|
-
import ProgressBar from '../../VideoPlayer/components/ProgressBar';
|
|
16
|
-
import { handleAdClickThrough } from '../utils/controls';
|
|
17
|
-
import { resetAdState } from '../utils/adStateReset';
|
|
18
|
-
import { useAdTracking } from '../utils/useAdTracking';
|
|
19
|
-
|
|
20
|
-
const AdBottomControls: React.FC = () => {
|
|
21
|
-
const currentAd = useAdsPlayerStore((state) => state.currentAd);
|
|
22
|
-
const adCurrentTime = useAdsPlayerStore((state) => state.adCurrentTime);
|
|
23
|
-
const adDuration = useAdsPlayerStore((state) => state.adDuration);
|
|
24
|
-
const { onAdSkip, onAdTracking } = useAdControls();
|
|
25
|
-
const fadeAnim = useAdControlsAutoHide();
|
|
26
|
-
const { trackAdSkip, trackAdClick } = useAdTracking({ onAdTracking });
|
|
27
|
-
|
|
28
|
-
const shouldShowSkipButton = useMemo(
|
|
29
|
-
() =>
|
|
30
|
-
currentAd?.skippable &&
|
|
31
|
-
currentAd.skipAfter > 0 &&
|
|
32
|
-
adCurrentTime >= currentAd.skipAfter,
|
|
33
|
-
[currentAd?.skippable, currentAd?.skipAfter, adCurrentTime]
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
// Memoize animated style
|
|
37
|
-
const animatedOpacityStyle = useMemo(
|
|
38
|
-
() => ({ opacity: fadeAnim }),
|
|
39
|
-
[fadeAnim]
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const handleSkip = useCallback(() => {
|
|
43
|
-
if (!currentAd) return;
|
|
44
|
-
|
|
45
|
-
trackAdSkip(currentAd);
|
|
46
|
-
onAdSkip?.(currentAd);
|
|
47
|
-
resetAdState();
|
|
48
|
-
}, [currentAd, onAdSkip, trackAdSkip]);
|
|
49
|
-
|
|
50
|
-
const handleClickThrough = useCallback(() => {
|
|
51
|
-
if (currentAd) {
|
|
52
|
-
trackAdClick(currentAd);
|
|
53
|
-
handleAdClickThrough(currentAd, onAdTracking);
|
|
54
|
-
}
|
|
55
|
-
}, [currentAd, onAdTracking, trackAdClick]);
|
|
56
|
-
|
|
57
|
-
if (!currentAd) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<View style={bottomControlsStyles.container}>
|
|
63
|
-
{/* Ad Metadata */}
|
|
64
|
-
{currentAd.clickThroughUrl && (
|
|
65
|
-
<Animated.View style={animatedOpacityStyle} pointerEvents="box-none">
|
|
66
|
-
<View pointerEvents="auto">
|
|
67
|
-
<TouchableOpacity
|
|
68
|
-
style={bottomControlsStyles.metadataContainer}
|
|
69
|
-
onPress={handleClickThrough}
|
|
70
|
-
activeOpacity={0.7}
|
|
71
|
-
>
|
|
72
|
-
<View style={bottomControlsStyles.metadataContent}>
|
|
73
|
-
<View style={bottomControlsStyles.metadataTextContainer}>
|
|
74
|
-
{currentAd.title && (
|
|
75
|
-
<Text
|
|
76
|
-
style={bottomControlsStyles.metadataTitle}
|
|
77
|
-
numberOfLines={1}
|
|
78
|
-
ellipsizeMode="tail"
|
|
79
|
-
>
|
|
80
|
-
{currentAd.title}
|
|
81
|
-
</Text>
|
|
82
|
-
)}
|
|
83
|
-
{currentAd.description && (
|
|
84
|
-
<Text
|
|
85
|
-
style={bottomControlsStyles.metadataDesc}
|
|
86
|
-
numberOfLines={2}
|
|
87
|
-
ellipsizeMode="tail"
|
|
88
|
-
>
|
|
89
|
-
{currentAd.description}
|
|
90
|
-
</Text>
|
|
91
|
-
)}
|
|
92
|
-
</View>
|
|
93
|
-
<View style={bottomControlsStyles.clickIndicator}>
|
|
94
|
-
<ExternalLink size={scale(14)} color="#fff" />
|
|
95
|
-
</View>
|
|
96
|
-
</View>
|
|
97
|
-
</TouchableOpacity>
|
|
98
|
-
</View>
|
|
99
|
-
</Animated.View>
|
|
100
|
-
)}
|
|
101
|
-
|
|
102
|
-
<Animated.View style={animatedOpacityStyle}>
|
|
103
|
-
<ProgressBar
|
|
104
|
-
duration={adDuration}
|
|
105
|
-
currentTime={adCurrentTime}
|
|
106
|
-
bufferedTime={adDuration}
|
|
107
|
-
disabled={true}
|
|
108
|
-
height={moderateScale(3)}
|
|
109
|
-
/>
|
|
110
|
-
</Animated.View>
|
|
111
|
-
{shouldShowSkipButton && (
|
|
112
|
-
<View pointerEvents="auto">
|
|
113
|
-
<TouchableOpacity
|
|
114
|
-
onPress={handleSkip}
|
|
115
|
-
style={bottomControlsStyles.skipButton}
|
|
116
|
-
activeOpacity={0.7}
|
|
117
|
-
>
|
|
118
|
-
<Text style={bottomControlsStyles.skipButtonText}>Skip Ad</Text>
|
|
119
|
-
</TouchableOpacity>
|
|
120
|
-
</View>
|
|
121
|
-
)}
|
|
122
|
-
</View>
|
|
123
|
-
);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const bottomControlsStyles = StyleSheet.create({
|
|
127
|
-
container: {
|
|
128
|
-
position: 'relative',
|
|
129
|
-
width: '100%',
|
|
130
|
-
bottom: 0,
|
|
131
|
-
zIndex: 1,
|
|
132
|
-
},
|
|
133
|
-
metadataContainer: {
|
|
134
|
-
backgroundColor: 'rgba(21, 21, 21, 0.95)',
|
|
135
|
-
borderRadius: moderateScale(6),
|
|
136
|
-
overflow: 'hidden',
|
|
137
|
-
marginBottom: moderateScale(5),
|
|
138
|
-
maxWidth: moderateScale(200),
|
|
139
|
-
borderWidth: 1,
|
|
140
|
-
borderColor: 'rgba(174, 174, 174, 0.12)',
|
|
141
|
-
},
|
|
142
|
-
metadataContent: {
|
|
143
|
-
flexDirection: 'row',
|
|
144
|
-
alignItems: 'center',
|
|
145
|
-
padding: moderateScale(5),
|
|
146
|
-
},
|
|
147
|
-
metadataTextContainer: {
|
|
148
|
-
flex: 1,
|
|
149
|
-
marginRight: moderateScale(4),
|
|
150
|
-
minWidth: 0,
|
|
151
|
-
},
|
|
152
|
-
metadataTitle: {
|
|
153
|
-
color: '#FFFFFF',
|
|
154
|
-
fontSize: RFValue(10),
|
|
155
|
-
fontWeight: '400',
|
|
156
|
-
marginBottom: moderateScale(0.5),
|
|
157
|
-
letterSpacing: 0.1,
|
|
158
|
-
},
|
|
159
|
-
metadataDesc: {
|
|
160
|
-
color: 'rgba(255, 255, 255, 0.7)',
|
|
161
|
-
fontSize: RFValue(9),
|
|
162
|
-
lineHeight: RFValue(12),
|
|
163
|
-
},
|
|
164
|
-
clickIndicator: {
|
|
165
|
-
width: moderateScale(20),
|
|
166
|
-
height: moderateScale(20),
|
|
167
|
-
borderRadius: moderateScale(10),
|
|
168
|
-
backgroundColor: 'rgba(255, 255, 255, 0.20)',
|
|
169
|
-
justifyContent: 'center',
|
|
170
|
-
alignItems: 'center',
|
|
171
|
-
},
|
|
172
|
-
skipButton: {
|
|
173
|
-
position: 'absolute',
|
|
174
|
-
right: moderateScale(2),
|
|
175
|
-
bottom: moderateScale(32),
|
|
176
|
-
backgroundColor: 'rgba(21, 21, 21, 0.95)',
|
|
177
|
-
paddingHorizontal: moderateScale(15),
|
|
178
|
-
paddingVertical: moderateScale(7),
|
|
179
|
-
borderRadius: moderateScale(14),
|
|
180
|
-
zIndex: 10,
|
|
181
|
-
borderWidth: 1,
|
|
182
|
-
borderColor: 'rgba(255, 255, 255, 0.20)',
|
|
183
|
-
},
|
|
184
|
-
skipButtonText: {
|
|
185
|
-
color: '#fff',
|
|
186
|
-
fontSize: RFValue(12),
|
|
187
|
-
fontWeight: '600',
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
export default memo(AdBottomControls);
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { memo, useMemo } from 'react';
|
|
2
|
-
import { Animated, StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
|
-
import LinearGradient from 'react-native-linear-gradient';
|
|
4
|
-
import { moderateScale } from 'react-native-size-matters';
|
|
5
|
-
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
6
|
-
import { useAdControlsAutoHide } from '../utils/useAdControlsAutoHide';
|
|
7
|
-
import AdTopControls from './AdTopControls';
|
|
8
|
-
import AdMiddleControls from './AdMiddleControls';
|
|
9
|
-
import AdBottomControls from './AdBottomControls';
|
|
10
|
-
import globalStyles from '../../VideoPlayer/styles/globalStyles';
|
|
11
|
-
|
|
12
|
-
interface AdMediaControlsProps {
|
|
13
|
-
onClose?: () => void;
|
|
14
|
-
insets: EdgeInsets;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const HORIZONTAL_PADDING = moderateScale(12);
|
|
18
|
-
const BOTTOM_PADDING_EXTRA = moderateScale(8);
|
|
19
|
-
const MIN_TOP_PADDING = moderateScale(10);
|
|
20
|
-
|
|
21
|
-
const AdMediaControls: React.FC<AdMediaControlsProps> = ({
|
|
22
|
-
onClose,
|
|
23
|
-
insets,
|
|
24
|
-
}) => {
|
|
25
|
-
const { top, bottom, left, right } = insets;
|
|
26
|
-
const fadeAnim = useAdControlsAutoHide();
|
|
27
|
-
|
|
28
|
-
// Safe-area aware padding: never negative, consistent touch targets
|
|
29
|
-
const containerStyle: ViewStyle = useMemo(
|
|
30
|
-
() => ({
|
|
31
|
-
...styles.container,
|
|
32
|
-
paddingTop: top - MIN_TOP_PADDING,
|
|
33
|
-
paddingBottom: bottom + BOTTOM_PADDING_EXTRA,
|
|
34
|
-
paddingLeft: left + HORIZONTAL_PADDING,
|
|
35
|
-
paddingRight: right + HORIZONTAL_PADDING,
|
|
36
|
-
}),
|
|
37
|
-
[top, bottom, left, right]
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
// Memoize gradient overlay styles — consistent positioning, no negative glitches
|
|
41
|
-
const topGradientStyle = useMemo(
|
|
42
|
-
() => [styles.topGradientOverlay, { opacity: fadeAnim, top: -top }],
|
|
43
|
-
[fadeAnim, top]
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const bottomGradientStyle = useMemo(
|
|
47
|
-
() => [
|
|
48
|
-
styles.bottomGradientOverlay,
|
|
49
|
-
{ opacity: fadeAnim, bottom: -bottom },
|
|
50
|
-
],
|
|
51
|
-
[fadeAnim, bottom]
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<>
|
|
56
|
-
<Animated.View style={topGradientStyle} pointerEvents="none">
|
|
57
|
-
<LinearGradient
|
|
58
|
-
colors={['rgba(0, 0, 0, 0.4)', 'transparent', 'transparent']}
|
|
59
|
-
locations={[0, 0.4, 1]}
|
|
60
|
-
style={globalStyles.absoluteFill}
|
|
61
|
-
pointerEvents="none"
|
|
62
|
-
/>
|
|
63
|
-
</Animated.View>
|
|
64
|
-
<View style={containerStyle}>
|
|
65
|
-
<AdTopControls onClose={onClose} />
|
|
66
|
-
<AdMiddleControls />
|
|
67
|
-
<AdBottomControls />
|
|
68
|
-
</View>
|
|
69
|
-
<Animated.View style={bottomGradientStyle} pointerEvents="none">
|
|
70
|
-
<LinearGradient
|
|
71
|
-
colors={['transparent', 'transparent', 'rgba(0, 0, 0, 0.5)']}
|
|
72
|
-
locations={[0, 0.4, 1]}
|
|
73
|
-
style={globalStyles.absoluteFill}
|
|
74
|
-
pointerEvents="none"
|
|
75
|
-
/>
|
|
76
|
-
</Animated.View>
|
|
77
|
-
</>
|
|
78
|
-
);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export default memo(AdMediaControls);
|
|
82
|
-
|
|
83
|
-
const styles = StyleSheet.create({
|
|
84
|
-
container: {
|
|
85
|
-
...globalStyles.absoluteFill,
|
|
86
|
-
justifyContent: 'space-between',
|
|
87
|
-
},
|
|
88
|
-
topGradientOverlay: {
|
|
89
|
-
position: 'absolute',
|
|
90
|
-
left: 0,
|
|
91
|
-
right: 0,
|
|
92
|
-
width: '100%',
|
|
93
|
-
height: moderateScale(150),
|
|
94
|
-
zIndex: 0,
|
|
95
|
-
},
|
|
96
|
-
bottomGradientOverlay: {
|
|
97
|
-
position: 'absolute',
|
|
98
|
-
left: 0,
|
|
99
|
-
right: 0,
|
|
100
|
-
width: '100%',
|
|
101
|
-
height: moderateScale(150),
|
|
102
|
-
zIndex: 0,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext } from 'react';
|
|
2
|
-
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
3
|
-
|
|
4
|
-
import { useAdsPlayerStore } from '../store/adsPlayerStore';
|
|
5
|
-
import type { VideoAd } from '../../VideoPlayer/store/videoPlayer.type';
|
|
6
|
-
import AdMediaControls from './AdMediaControls';
|
|
7
|
-
|
|
8
|
-
interface AdControlsContextType {
|
|
9
|
-
onAdSkip?: (ad: VideoAd) => void;
|
|
10
|
-
onAdTracking?: ({
|
|
11
|
-
ad,
|
|
12
|
-
trackingUrl,
|
|
13
|
-
event,
|
|
14
|
-
}: {
|
|
15
|
-
ad: VideoAd;
|
|
16
|
-
trackingUrl: string;
|
|
17
|
-
event: string;
|
|
18
|
-
}) => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const AdControlsContext = createContext<AdControlsContextType>({});
|
|
22
|
-
|
|
23
|
-
export const useAdControls = () => useContext(AdControlsContext);
|
|
24
|
-
|
|
25
|
-
interface AdMediaControlsProviderProps {
|
|
26
|
-
children: React.ReactNode;
|
|
27
|
-
onAdSkip?: (ad: VideoAd) => void;
|
|
28
|
-
onAdTracking?: ({
|
|
29
|
-
ad,
|
|
30
|
-
trackingUrl,
|
|
31
|
-
event,
|
|
32
|
-
}: {
|
|
33
|
-
ad: VideoAd;
|
|
34
|
-
trackingUrl: string;
|
|
35
|
-
event: string;
|
|
36
|
-
}) => void;
|
|
37
|
-
onClose?: () => void;
|
|
38
|
-
insets: EdgeInsets;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const AdMediaControlsProvider: React.FC<AdMediaControlsProviderProps> = ({
|
|
42
|
-
children,
|
|
43
|
-
onAdSkip,
|
|
44
|
-
onAdTracking,
|
|
45
|
-
onClose,
|
|
46
|
-
insets,
|
|
47
|
-
}) => {
|
|
48
|
-
const isAdPlaying = useAdsPlayerStore((state) => state.isAdPlaying);
|
|
49
|
-
|
|
50
|
-
if (!isAdPlaying) {
|
|
51
|
-
return <>{children}</>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<AdControlsContext.Provider value={{ onAdSkip, onAdTracking }}>
|
|
56
|
-
{children}
|
|
57
|
-
<AdMediaControls onClose={onClose} insets={insets} />
|
|
58
|
-
</AdControlsContext.Provider>
|
|
59
|
-
);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export default AdMediaControlsProvider;
|