@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,73 +0,0 @@
|
|
|
1
|
-
import { StyleSheet } from 'react-native';
|
|
2
|
-
|
|
3
|
-
const globalStyles = StyleSheet.create({
|
|
4
|
-
centerContainer: {
|
|
5
|
-
flex: 1,
|
|
6
|
-
alignItems: 'center',
|
|
7
|
-
justifyContent: 'center',
|
|
8
|
-
},
|
|
9
|
-
container: {
|
|
10
|
-
flex: 1,
|
|
11
|
-
},
|
|
12
|
-
newContainer: {
|
|
13
|
-
flex: 1,
|
|
14
|
-
},
|
|
15
|
-
flexOneJustifyContentAndAlignItemsCenter: {
|
|
16
|
-
flex: 1,
|
|
17
|
-
justifyContent: 'center',
|
|
18
|
-
alignItems: 'center',
|
|
19
|
-
},
|
|
20
|
-
flexOneJustifyContentCenterAndAlignItemsCenter: {
|
|
21
|
-
flex: 1,
|
|
22
|
-
justifyContent: 'center',
|
|
23
|
-
alignItems: 'center',
|
|
24
|
-
},
|
|
25
|
-
hundredPercentWidth: {
|
|
26
|
-
width: '100%',
|
|
27
|
-
},
|
|
28
|
-
hundredPercentHeight: {
|
|
29
|
-
height: '100%',
|
|
30
|
-
},
|
|
31
|
-
marginZeroAndPaddingZero: {
|
|
32
|
-
margin: 0,
|
|
33
|
-
padding: 0,
|
|
34
|
-
},
|
|
35
|
-
overflowHidden: {
|
|
36
|
-
overflow: 'hidden',
|
|
37
|
-
},
|
|
38
|
-
flexOne: {
|
|
39
|
-
flex: 1,
|
|
40
|
-
},
|
|
41
|
-
flexZero: {
|
|
42
|
-
flex: 0,
|
|
43
|
-
},
|
|
44
|
-
positionAbsolute: {
|
|
45
|
-
position: 'absolute',
|
|
46
|
-
},
|
|
47
|
-
twentyPercentWidth: {
|
|
48
|
-
width: '20%',
|
|
49
|
-
},
|
|
50
|
-
alignItemsCenterAndJustifyContentCenter: {
|
|
51
|
-
alignItems: 'center',
|
|
52
|
-
justifyContent: 'center',
|
|
53
|
-
},
|
|
54
|
-
textAlignCenter: {
|
|
55
|
-
textAlign: 'center',
|
|
56
|
-
},
|
|
57
|
-
flexRow_alignCenter_justifyBetween: {
|
|
58
|
-
flexDirection: 'row',
|
|
59
|
-
alignItems: 'center',
|
|
60
|
-
justifyContent: 'space-between',
|
|
61
|
-
},
|
|
62
|
-
flexOneWithBlackBackground: {
|
|
63
|
-
flex: 1,
|
|
64
|
-
backgroundColor: 'black',
|
|
65
|
-
},
|
|
66
|
-
absoluteFill: StyleSheet.absoluteFillObject,
|
|
67
|
-
controlsContainer: {
|
|
68
|
-
flex: 1,
|
|
69
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
export default globalStyles;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Dimensions } from 'react-native';
|
|
2
|
-
|
|
3
|
-
const { height, width } = Dimensions.get('window');
|
|
4
|
-
|
|
5
|
-
const setHeight = (h: number) => (height / 100) * h;
|
|
6
|
-
const setWidth = (w: number) => (width / 100) * w;
|
|
7
|
-
const fullWidth = Dimensions.get('window').width;
|
|
8
|
-
const fullHeight = Dimensions.get('window').height;
|
|
9
|
-
|
|
10
|
-
export default { setHeight, setWidth, fullWidth, fullHeight };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as Display } from './Display';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './timeFormatter';
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export const formatDuration = (
|
|
2
|
-
totalSeconds: number,
|
|
3
|
-
hideSeconds = false,
|
|
4
|
-
addSpaces = false
|
|
5
|
-
): string => {
|
|
6
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
7
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
8
|
-
const seconds = totalSeconds % 60;
|
|
9
|
-
|
|
10
|
-
const formatUnit = (value: number, label: string) =>
|
|
11
|
-
value ? `${value}${addSpaces ? ` ${label} ` : label}` : '';
|
|
12
|
-
|
|
13
|
-
const result =
|
|
14
|
-
formatUnit(hours, 'h') +
|
|
15
|
-
formatUnit(minutes, 'm') +
|
|
16
|
-
formatUnit(seconds, 's');
|
|
17
|
-
|
|
18
|
-
return hideSeconds
|
|
19
|
-
? formatUnit(hours, 'h') + formatUnit(minutes, 'm')
|
|
20
|
-
: result;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const formatTime = (seconds: number) => {
|
|
24
|
-
const h = Math.floor(seconds / 3600);
|
|
25
|
-
const m = Math.floor((seconds % 3600) / 60);
|
|
26
|
-
const s = Math.floor(seconds % 60);
|
|
27
|
-
if (h > 0) {
|
|
28
|
-
return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
29
|
-
}
|
|
30
|
-
return `${m}:${String(s).padStart(2, '0')}`;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const formatTimeWithMs = (seconds: number): string => {
|
|
34
|
-
if (isNaN(seconds) || seconds < 0) return '00:00.000';
|
|
35
|
-
const totalMs = Math.floor(seconds * 1000);
|
|
36
|
-
const ms = totalMs % 1000;
|
|
37
|
-
const totalSec = Math.floor(totalMs / 1000);
|
|
38
|
-
const min = Math.floor(totalSec / 60);
|
|
39
|
-
const sec = totalSec % 60;
|
|
40
|
-
return `${String(min).padStart(2, '0')}:${String(sec).padStart(
|
|
41
|
-
2,
|
|
42
|
-
'0'
|
|
43
|
-
)}.${String(ms).padStart(3, '0')}`;
|
|
44
|
-
};
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import type { VideoAd } from '../../store/videoPlayer.type';
|
|
3
|
-
import type { ExtendedWatchProgress } from '../player/useWatchReporter';
|
|
4
|
-
import type { MediaEpisode } from '../../store/videoPlayer.type';
|
|
5
|
-
|
|
6
|
-
interface UseAdEventHandlerParams {
|
|
7
|
-
resumeVideoFromAd: (resumeTime: number | null) => void;
|
|
8
|
-
resumeTime: number | null;
|
|
9
|
-
playerEvents: {
|
|
10
|
-
onEnd: (params: {
|
|
11
|
-
reportProgress: (event: ExtendedWatchProgress['event']) => void;
|
|
12
|
-
onPressEpisode: ({
|
|
13
|
-
episode,
|
|
14
|
-
}: {
|
|
15
|
-
episode: MediaEpisode;
|
|
16
|
-
}) => Promise<boolean>;
|
|
17
|
-
autoNext: boolean;
|
|
18
|
-
}) => void;
|
|
19
|
-
};
|
|
20
|
-
reportProgress: (event: ExtendedWatchProgress['event']) => void;
|
|
21
|
-
onPressEpisode?: ({ episode }: { episode: MediaEpisode }) => Promise<boolean>;
|
|
22
|
-
autoNext: boolean;
|
|
23
|
-
onAdEnd?: (ad: VideoAd) => void;
|
|
24
|
-
onAdError?: (error: Error, ad: VideoAd) => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Reusable hook for handling ad events (end, skip, error)
|
|
29
|
-
* Consolidates duplicate logic from handleAdEnd, handleAdSkip, and handleAdError
|
|
30
|
-
*/
|
|
31
|
-
export const useAdEventHandler = ({
|
|
32
|
-
resumeVideoFromAd,
|
|
33
|
-
resumeTime,
|
|
34
|
-
playerEvents,
|
|
35
|
-
reportProgress,
|
|
36
|
-
onPressEpisode,
|
|
37
|
-
autoNext,
|
|
38
|
-
onAdEnd,
|
|
39
|
-
onAdError,
|
|
40
|
-
}: UseAdEventHandlerParams) => {
|
|
41
|
-
const handlePostRollAd = useCallback(() => {
|
|
42
|
-
playerEvents.onEnd({
|
|
43
|
-
reportProgress,
|
|
44
|
-
onPressEpisode: onPressEpisode ?? (async () => true),
|
|
45
|
-
autoNext,
|
|
46
|
-
});
|
|
47
|
-
}, [playerEvents, reportProgress, onPressEpisode, autoNext]);
|
|
48
|
-
|
|
49
|
-
const handleNonPostRollAd = useCallback(() => {
|
|
50
|
-
resumeVideoFromAd(resumeTime);
|
|
51
|
-
}, [resumeVideoFromAd, resumeTime]);
|
|
52
|
-
|
|
53
|
-
const handleAdEnd = useCallback(
|
|
54
|
-
(endedAd: VideoAd) => {
|
|
55
|
-
onAdEnd?.(endedAd);
|
|
56
|
-
|
|
57
|
-
if (endedAd.position === 'post') {
|
|
58
|
-
handlePostRollAd();
|
|
59
|
-
} else {
|
|
60
|
-
handleNonPostRollAd();
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
[onAdEnd, handlePostRollAd, handleNonPostRollAd]
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const handleAdSkip = useCallback(
|
|
67
|
-
(skippedAd: VideoAd) => {
|
|
68
|
-
onAdEnd?.(skippedAd);
|
|
69
|
-
|
|
70
|
-
if (skippedAd.position !== 'post') {
|
|
71
|
-
handleNonPostRollAd();
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
[onAdEnd, handleNonPostRollAd]
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
const handleAdError = useCallback(
|
|
78
|
-
(error: Error, errorAd: VideoAd) => {
|
|
79
|
-
onAdError?.(error, errorAd);
|
|
80
|
-
|
|
81
|
-
if (errorAd.position !== 'post') {
|
|
82
|
-
handleNonPostRollAd();
|
|
83
|
-
} else {
|
|
84
|
-
handlePostRollAd();
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
[onAdError, handleNonPostRollAd, handlePostRollAd]
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
handleAdEnd,
|
|
92
|
-
handleAdSkip,
|
|
93
|
-
handleAdError,
|
|
94
|
-
};
|
|
95
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { lockToLandscape, lockToPortrait } from '../platform';
|
|
3
|
-
|
|
4
|
-
interface UseOrientationLockParams {
|
|
5
|
-
isFocused: boolean;
|
|
6
|
-
mode: 'fullscreen' | 'normal';
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Reusable hook for managing orientation locking
|
|
11
|
-
* Consolidates orientation logic from VideoPlayer
|
|
12
|
-
*/
|
|
13
|
-
export const useOrientationLock = ({
|
|
14
|
-
isFocused,
|
|
15
|
-
mode,
|
|
16
|
-
}: UseOrientationLockParams) => {
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (!isFocused) {
|
|
19
|
-
lockToPortrait();
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (mode === 'fullscreen') {
|
|
24
|
-
lockToLandscape();
|
|
25
|
-
} else {
|
|
26
|
-
lockToPortrait();
|
|
27
|
-
}
|
|
28
|
-
}, [isFocused, mode]);
|
|
29
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { useVideoPlayerStore } from '../../store/videoPlayerStore';
|
|
3
|
-
import { useAdsPlayerStore } from '../../../AdsPlayer/store/adsPlayerStore';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Reusable hook to pause main video when ads start playing
|
|
7
|
-
* Saves current time as resume time for pre-roll ads
|
|
8
|
-
*/
|
|
9
|
-
export const usePauseVideoOnAd = () => {
|
|
10
|
-
const { setIsPaused, currentTime, isPaused } = useVideoPlayerStore();
|
|
11
|
-
const { currentAd, isAdPlaying, resumeTime, setResumeTime } =
|
|
12
|
-
useAdsPlayerStore();
|
|
13
|
-
|
|
14
|
-
const lastResumeTimeRef = useRef<number | null>(null);
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
if (isAdPlaying && currentAd) {
|
|
18
|
-
// Only pause if not already paused to prevent unnecessary updates
|
|
19
|
-
if (!isPaused) {
|
|
20
|
-
setIsPaused(true);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Save current time as resume time if not already set (for pre-roll ads)
|
|
24
|
-
// Use ref to prevent setting the same value multiple times
|
|
25
|
-
if (
|
|
26
|
-
resumeTime === null &&
|
|
27
|
-
currentTime > 0 &&
|
|
28
|
-
lastResumeTimeRef.current !== currentTime
|
|
29
|
-
) {
|
|
30
|
-
lastResumeTimeRef.current = currentTime;
|
|
31
|
-
setResumeTime(currentTime);
|
|
32
|
-
}
|
|
33
|
-
} else if (!isAdPlaying && !currentAd) {
|
|
34
|
-
// Reset ref when no ad is playing
|
|
35
|
-
lastResumeTimeRef.current = null;
|
|
36
|
-
}
|
|
37
|
-
}, [
|
|
38
|
-
isAdPlaying,
|
|
39
|
-
currentAd,
|
|
40
|
-
isPaused,
|
|
41
|
-
setIsPaused,
|
|
42
|
-
currentTime,
|
|
43
|
-
resumeTime,
|
|
44
|
-
setResumeTime,
|
|
45
|
-
]);
|
|
46
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { useEffect, useCallback, useRef } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
BackHandler,
|
|
4
|
-
Platform,
|
|
5
|
-
AppState,
|
|
6
|
-
type AppStateStatus,
|
|
7
|
-
} from 'react-native';
|
|
8
|
-
import { lockToPortrait } from '../platform/lockOrientation';
|
|
9
|
-
import { useVideoPlayerStore } from '../../store/videoPlayerStore';
|
|
10
|
-
|
|
11
|
-
type UseVideoPlayerBackProps = {
|
|
12
|
-
navigation?: any;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const useVideoPlayerBack = ({
|
|
16
|
-
navigation,
|
|
17
|
-
}: UseVideoPlayerBackProps = {}) => {
|
|
18
|
-
const { resetStore } = useVideoPlayerStore();
|
|
19
|
-
const hasClosed = useRef(false);
|
|
20
|
-
|
|
21
|
-
const handleClose = useCallback(() => {
|
|
22
|
-
if (hasClosed.current) return;
|
|
23
|
-
hasClosed.current = true;
|
|
24
|
-
|
|
25
|
-
resetStore();
|
|
26
|
-
lockToPortrait();
|
|
27
|
-
|
|
28
|
-
if (navigation?.canGoBack()) {
|
|
29
|
-
navigation.goBack();
|
|
30
|
-
}
|
|
31
|
-
}, [resetStore, navigation]);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const backHandler =
|
|
35
|
-
Platform.OS === 'android'
|
|
36
|
-
? BackHandler.addEventListener('hardwareBackPress', () => {
|
|
37
|
-
handleClose();
|
|
38
|
-
return true;
|
|
39
|
-
})
|
|
40
|
-
: undefined;
|
|
41
|
-
|
|
42
|
-
const unsubscribe = navigation?.addListener('beforeRemove', (e: any) => {
|
|
43
|
-
e.preventDefault();
|
|
44
|
-
handleClose();
|
|
45
|
-
navigation.dispatch(e.data.action);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
|
49
|
-
if (nextAppState === 'inactive') {
|
|
50
|
-
handleClose();
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const appStateListener = AppState.addEventListener(
|
|
54
|
-
'change',
|
|
55
|
-
handleAppStateChange
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
return () => {
|
|
59
|
-
backHandler?.remove();
|
|
60
|
-
unsubscribe?.();
|
|
61
|
-
appStateListener.remove();
|
|
62
|
-
};
|
|
63
|
-
}, [handleClose, navigation]);
|
|
64
|
-
|
|
65
|
-
return handleClose;
|
|
66
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import type { MediaTrack } from '../../store/videoPlayer.type';
|
|
4
|
-
|
|
5
|
-
export interface StreamInfo {
|
|
6
|
-
bandwidth: number;
|
|
7
|
-
width: number | string;
|
|
8
|
-
height: number | string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function getHLSBandwidthAndResolutions(
|
|
12
|
-
m3u8Url: string,
|
|
13
|
-
headers?: Record<string, string>
|
|
14
|
-
): Promise<StreamInfo[]> {
|
|
15
|
-
try {
|
|
16
|
-
if (!m3u8Url?.endsWith('.m3u8')) return [];
|
|
17
|
-
|
|
18
|
-
const { data } = await axios.get<string>(m3u8Url, {
|
|
19
|
-
headers: headers ?? undefined,
|
|
20
|
-
});
|
|
21
|
-
if (!data) return [];
|
|
22
|
-
|
|
23
|
-
const lines = data.split('\n');
|
|
24
|
-
const streams: StreamInfo[] = [];
|
|
25
|
-
|
|
26
|
-
let bandwidth: number | null = null;
|
|
27
|
-
let width: number | null = null;
|
|
28
|
-
let height: number | null = null;
|
|
29
|
-
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
if (line.startsWith('#EXT-X-STREAM-INF')) {
|
|
32
|
-
const bandwidthMatch = line.match(/BANDWIDTH=(\d+)/);
|
|
33
|
-
const resolutionMatch = line.match(/RESOLUTION=(\d+)x(\d+)/);
|
|
34
|
-
|
|
35
|
-
bandwidth = bandwidthMatch ? Number(bandwidthMatch[1]) : null;
|
|
36
|
-
if (resolutionMatch) {
|
|
37
|
-
width = Number(resolutionMatch[1]);
|
|
38
|
-
height = Number(resolutionMatch[2]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
line.trim().endsWith('.m3u8') &&
|
|
44
|
-
bandwidth !== null &&
|
|
45
|
-
width !== null &&
|
|
46
|
-
height !== null
|
|
47
|
-
) {
|
|
48
|
-
streams.push({ bandwidth, width, height });
|
|
49
|
-
bandwidth = null;
|
|
50
|
-
width = null;
|
|
51
|
-
height = null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
streams.sort((a, b) => (b.height as number) - (a.height as number));
|
|
56
|
-
|
|
57
|
-
const autoStream: StreamInfo = {
|
|
58
|
-
bandwidth: 0,
|
|
59
|
-
width: 'auto',
|
|
60
|
-
height: 'auto',
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return [autoStream, ...streams];
|
|
64
|
-
} catch {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface Resolution {
|
|
70
|
-
height: number | 'auto';
|
|
71
|
-
bandwidth: number | null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const resolutionCache: Record<string, Resolution[]> = {};
|
|
75
|
-
|
|
76
|
-
export const useVideoResolutions = (track: MediaTrack | null) => {
|
|
77
|
-
const [resolutions, setResolutions] = useState<Resolution[]>([
|
|
78
|
-
{ height: 'auto', bandwidth: null },
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (!track) return;
|
|
83
|
-
|
|
84
|
-
const source = track.isTrailer ? track.trailerSource : track.sourceLink;
|
|
85
|
-
if (!source) return;
|
|
86
|
-
|
|
87
|
-
if (resolutionCache[source]) {
|
|
88
|
-
setResolutions(resolutionCache[source]);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let cancelled = false;
|
|
93
|
-
|
|
94
|
-
const fetchResolutions = async () => {
|
|
95
|
-
try {
|
|
96
|
-
const data = await getHLSBandwidthAndResolutions(source);
|
|
97
|
-
if (cancelled) return;
|
|
98
|
-
|
|
99
|
-
const filteredData = data.filter(
|
|
100
|
-
(item): item is StreamInfo & { height: number } =>
|
|
101
|
-
typeof item.height === 'number'
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const newResolutions: Resolution[] = [
|
|
105
|
-
{ height: 'auto', bandwidth: null },
|
|
106
|
-
...filteredData.map((item) => ({
|
|
107
|
-
height: item.height,
|
|
108
|
-
bandwidth: item.bandwidth,
|
|
109
|
-
})),
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
resolutionCache[source] = newResolutions;
|
|
113
|
-
setResolutions(newResolutions);
|
|
114
|
-
} catch {}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
fetchResolutions();
|
|
118
|
-
|
|
119
|
-
return () => {
|
|
120
|
-
cancelled = true;
|
|
121
|
-
};
|
|
122
|
-
}, [track]);
|
|
123
|
-
|
|
124
|
-
return resolutions;
|
|
125
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Platform } from 'react-native';
|
|
2
|
-
|
|
3
|
-
class PlatformSelector {
|
|
4
|
-
public isAndroid(): boolean {
|
|
5
|
-
return Platform.OS === 'android';
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
public isIOS(): boolean {
|
|
9
|
-
return Platform.OS === 'ios';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default new PlatformSelector();
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { StatusBar } from 'react-native';
|
|
2
|
-
import Orientation from 'react-native-orientation-locker';
|
|
3
|
-
import SystemNavigationBar from 'react-native-system-navigation-bar';
|
|
4
|
-
import PlatformSelector from './PlatformSelector';
|
|
5
|
-
|
|
6
|
-
export const lockToPortrait = () => {
|
|
7
|
-
try {
|
|
8
|
-
// Lock orientation immediately for fast response
|
|
9
|
-
Orientation.lockToPortrait();
|
|
10
|
-
|
|
11
|
-
// Update UI instantly without fade animation for smooth, fast rotation
|
|
12
|
-
StatusBar.setHidden(false, 'none');
|
|
13
|
-
|
|
14
|
-
if (PlatformSelector.isAndroid()) {
|
|
15
|
-
// Execute Android-specific updates synchronously for maximum speed
|
|
16
|
-
SystemNavigationBar.fullScreen(false);
|
|
17
|
-
SystemNavigationBar.navigationShow();
|
|
18
|
-
}
|
|
19
|
-
} catch (error) {
|
|
20
|
-
console.warn('Error locking to portrait:', error);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const lockToLandscape = () => {
|
|
25
|
-
try {
|
|
26
|
-
// Lock orientation immediately for fast response
|
|
27
|
-
Orientation.lockToLandscape();
|
|
28
|
-
|
|
29
|
-
// Update UI instantly without fade animation for smooth, fast rotation
|
|
30
|
-
StatusBar.setHidden(true, 'none');
|
|
31
|
-
|
|
32
|
-
if (PlatformSelector.isAndroid()) {
|
|
33
|
-
// Execute Android-specific updates synchronously for maximum speed
|
|
34
|
-
SystemNavigationBar.fullScreen(true);
|
|
35
|
-
SystemNavigationBar.stickyImmersive();
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.warn('Error locking to landscape:', error);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
OnBufferData,
|
|
3
|
-
OnLoadData,
|
|
4
|
-
OnLoadStartData,
|
|
5
|
-
OnProgressData,
|
|
6
|
-
} from 'react-native-video';
|
|
7
|
-
import { mmkvStorage, useVideoPlayerStore } from '../../store/videoPlayerStore';
|
|
8
|
-
|
|
9
|
-
import { handleNext } from '../video/videoControl';
|
|
10
|
-
import type { MediaEpisode } from '../../store/videoPlayer.type';
|
|
11
|
-
import type { ExtendedWatchProgress } from './useWatchReporter';
|
|
12
|
-
|
|
13
|
-
export const createPlayerEvents = () => {
|
|
14
|
-
const onProgress = ({
|
|
15
|
-
currentTime = 0,
|
|
16
|
-
playableDuration = 0,
|
|
17
|
-
}: OnProgressData) => {
|
|
18
|
-
const store = useVideoPlayerStore.getState();
|
|
19
|
-
store.setCurrentTime(currentTime);
|
|
20
|
-
store.setPlayableDuration(playableDuration);
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
mmkvStorage.setItem('currentTime', String(currentTime));
|
|
24
|
-
} catch (e) {
|
|
25
|
-
console.warn('MMKV save failed', e);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (store.isBuffering) store.setIsBuffering(false);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const onLoad = (data: OnLoadData) => {
|
|
32
|
-
const store = useVideoPlayerStore.getState();
|
|
33
|
-
store.setDuration(data.duration || 0);
|
|
34
|
-
store.setOnLoad(data);
|
|
35
|
-
|
|
36
|
-
mmkvStorage.setItem('current_watch_time', '0');
|
|
37
|
-
|
|
38
|
-
store.setError(null);
|
|
39
|
-
store.setIsViewCounted(false);
|
|
40
|
-
store.setStartWatchTime(Date.now());
|
|
41
|
-
store.setIsNextEpisodeVisible(true);
|
|
42
|
-
store.setIsSkipIntroVisible(true);
|
|
43
|
-
store.setIsBuffering(false);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const onBuffer = ({ isBuffering }: OnBufferData) => {
|
|
47
|
-
const store = useVideoPlayerStore.getState();
|
|
48
|
-
if (isBuffering) store.setIsBuffering(true);
|
|
49
|
-
else setTimeout(() => store.setIsBuffering(false), 150);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const onEnd = ({
|
|
53
|
-
onPressEpisode,
|
|
54
|
-
reportProgress,
|
|
55
|
-
autoNext,
|
|
56
|
-
}: {
|
|
57
|
-
reportProgress: (event: ExtendedWatchProgress['event']) => void;
|
|
58
|
-
onPressEpisode: ({
|
|
59
|
-
episode,
|
|
60
|
-
}: {
|
|
61
|
-
episode: MediaEpisode;
|
|
62
|
-
}) => Promise<boolean>;
|
|
63
|
-
autoNext: boolean;
|
|
64
|
-
}) => {
|
|
65
|
-
reportProgress('COMPLETED');
|
|
66
|
-
const store = useVideoPlayerStore.getState();
|
|
67
|
-
if (!store.activeTrack?.isTrailer && store.activeTrack?.type === 'series') {
|
|
68
|
-
handleNext({ onPressEpisode, autoNext });
|
|
69
|
-
} else {
|
|
70
|
-
store.setIsPaused(true);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const onLoadStart = (_: OnLoadStartData) => {
|
|
75
|
-
const store = useVideoPlayerStore.getState();
|
|
76
|
-
store.setIsBuffering(true);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const LeaveVideoPlayer = () => {
|
|
80
|
-
const store = useVideoPlayerStore.getState();
|
|
81
|
-
store.setDuration(0);
|
|
82
|
-
store.setCurrentTime(0);
|
|
83
|
-
store.setPlayableDuration(0);
|
|
84
|
-
store.setIsPaused(false);
|
|
85
|
-
store.setSettingsModal({ isVisible: false, action: 'none' });
|
|
86
|
-
store.setIsBuffering(false);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
onProgress,
|
|
91
|
-
onLoad,
|
|
92
|
-
onBuffer,
|
|
93
|
-
onEnd,
|
|
94
|
-
onLoadStart,
|
|
95
|
-
LeaveVideoPlayer,
|
|
96
|
-
};
|
|
97
|
-
};
|