@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
|
@@ -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
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
2
|
-
import { StyleSheet, View } from 'react-native';
|
|
3
|
-
import TopControls from './TopControls';
|
|
4
|
-
import MiddleControls from './MiddleControls';
|
|
5
|
-
import BottomControls from './BottomControls';
|
|
6
|
-
|
|
7
|
-
export type MediaControlsProps = {
|
|
8
|
-
onClose?: () => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const MediaControls: React.FC<MediaControlsProps> = ({ onClose }) => {
|
|
12
|
-
return (
|
|
13
|
-
<View style={styles.container}>
|
|
14
|
-
<TopControls onClose={onClose} />
|
|
15
|
-
<MiddleControls />
|
|
16
|
-
<BottomControls />
|
|
17
|
-
</View>
|
|
18
|
-
);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default memo(MediaControls);
|
|
22
|
-
|
|
23
|
-
const styles = StyleSheet.create({
|
|
24
|
-
container: {
|
|
25
|
-
flex: 1,
|
|
26
|
-
flexDirection: 'column',
|
|
27
|
-
justifyContent: 'space-between',
|
|
28
|
-
minHeight: 0,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useMemo } from 'react';
|
|
2
|
-
import { TouchableOpacity, View, type ViewStyle, Animated } from 'react-native';
|
|
3
|
-
import { type EdgeInsets } from 'react-native-safe-area-context';
|
|
4
|
-
import { moderateScale } from 'react-native-size-matters';
|
|
5
|
-
import MediaControls, { type MediaControlsProps } from './MediaControls';
|
|
6
|
-
import SettingModal from '../Settings/SettingModal';
|
|
7
|
-
import { useVideoPlayerStore } from '../store/videoPlayerStore';
|
|
8
|
-
import SkipAndNextControls from '../components/SkipAndNextControls';
|
|
9
|
-
import type { MediaEpisode } from '../store/videoPlayer.type';
|
|
10
|
-
import globalStyles from '../styles/globalStyles';
|
|
11
|
-
import type { ExtendedWatchProgress } from '../utils';
|
|
12
|
-
import SubtitleView from '../components/SubtitleView';
|
|
13
|
-
|
|
14
|
-
const HORIZONTAL_PADDING = moderateScale(12);
|
|
15
|
-
const BOTTOM_PADDING_EXTRA = moderateScale(8);
|
|
16
|
-
const MIN_TOP_PADDING = moderateScale(10);
|
|
17
|
-
|
|
18
|
-
interface MediaControlsProviderProps extends MediaControlsProps {
|
|
19
|
-
children: React.ReactNode;
|
|
20
|
-
onPressEpisode: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
|
|
21
|
-
reportProgress: (event: ExtendedWatchProgress['event']) => void;
|
|
22
|
-
insets: EdgeInsets;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const MediaControlsProvider: React.FC<MediaControlsProviderProps> = ({
|
|
26
|
-
children,
|
|
27
|
-
onClose,
|
|
28
|
-
onPressEpisode,
|
|
29
|
-
reportProgress,
|
|
30
|
-
insets,
|
|
31
|
-
}) => {
|
|
32
|
-
const { top, bottom, left, right } = insets;
|
|
33
|
-
const controlsVisible = useVideoPlayerStore((state) => state.controlsVisible);
|
|
34
|
-
const setControlsVisible = useVideoPlayerStore(
|
|
35
|
-
(state) => state.setControlsVisible
|
|
36
|
-
);
|
|
37
|
-
const controlsTimer = useVideoPlayerStore((state) => state.controlsTimer);
|
|
38
|
-
const timeoutId = React.useRef<NodeJS.Timeout | null>(null);
|
|
39
|
-
const fadeAnim = useRef(new Animated.Value(0)).current;
|
|
40
|
-
|
|
41
|
-
// Safe-area padding: never negative, single source of truth (MediaControls uses View, not SafeAreaView)
|
|
42
|
-
const containerPadding: ViewStyle = useMemo(
|
|
43
|
-
() => ({
|
|
44
|
-
paddingTop: top - MIN_TOP_PADDING,
|
|
45
|
-
paddingBottom: bottom + BOTTOM_PADDING_EXTRA,
|
|
46
|
-
paddingLeft: left + HORIZONTAL_PADDING,
|
|
47
|
-
paddingRight: right + HORIZONTAL_PADDING,
|
|
48
|
-
}),
|
|
49
|
-
[top, bottom, left, right]
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Memoize animated style
|
|
53
|
-
const animatedStyle = useMemo(
|
|
54
|
-
() => [globalStyles.absoluteFill, { opacity: fadeAnim }],
|
|
55
|
-
[fadeAnim]
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
Animated.timing(fadeAnim, {
|
|
60
|
-
toValue: controlsVisible ? 1 : 0,
|
|
61
|
-
duration: 200,
|
|
62
|
-
useNativeDriver: true,
|
|
63
|
-
}).start();
|
|
64
|
-
|
|
65
|
-
if (controlsVisible) {
|
|
66
|
-
timeoutId.current = setTimeout(() => {
|
|
67
|
-
setControlsVisible(false);
|
|
68
|
-
}, controlsTimer * 1000);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return () => {
|
|
72
|
-
if (timeoutId.current) clearTimeout(timeoutId.current);
|
|
73
|
-
};
|
|
74
|
-
}, [controlsVisible, controlsTimer, setControlsVisible, fadeAnim]);
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<View style={globalStyles.flexOne}>
|
|
78
|
-
{children}
|
|
79
|
-
<Animated.View
|
|
80
|
-
pointerEvents={controlsVisible ? 'auto' : 'none'}
|
|
81
|
-
style={animatedStyle}
|
|
82
|
-
>
|
|
83
|
-
<TouchableOpacity
|
|
84
|
-
onPress={() => setControlsVisible(false)}
|
|
85
|
-
style={[globalStyles.controlsContainer, containerPadding]}
|
|
86
|
-
activeOpacity={1}
|
|
87
|
-
>
|
|
88
|
-
<MediaControls onClose={onClose} />
|
|
89
|
-
</TouchableOpacity>
|
|
90
|
-
</Animated.View>
|
|
91
|
-
<SettingModal
|
|
92
|
-
onPressEpisode={onPressEpisode}
|
|
93
|
-
reportProgress={reportProgress}
|
|
94
|
-
/>
|
|
95
|
-
<SubtitleView />
|
|
96
|
-
<SkipAndNextControls
|
|
97
|
-
onPressEpisode={onPressEpisode}
|
|
98
|
-
reportProgress={reportProgress}
|
|
99
|
-
/>
|
|
100
|
-
</View>
|
|
101
|
-
);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export default MediaControlsProvider;
|