mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-06-12 17:03:10 +08:00
Refactor LivePlayer component to improve loading state handling and error messaging
This commit is contained in:
20
app/live.tsx
20
app/live.tsx
@@ -16,7 +16,6 @@ export default function LiveScreen() {
|
|||||||
|
|
||||||
const [currentChannelIndex, setCurrentChannelIndex] = useState(0);
|
const [currentChannelIndex, setCurrentChannelIndex] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isPlayerLoading, setIsPlayerLoading] = useState(true);
|
|
||||||
const [isChannelListVisible, setIsChannelListVisible] = useState(false);
|
const [isChannelListVisible, setIsChannelListVisible] = useState(false);
|
||||||
const [channelTitle, setChannelTitle] = useState<string | null>(null);
|
const [channelTitle, setChannelTitle] = useState<string | null>(null);
|
||||||
const titleTimer = useRef<NodeJS.Timeout | null>(null);
|
const titleTimer = useRef<NodeJS.Timeout | null>(null);
|
||||||
@@ -51,14 +50,6 @@ export default function LiveScreen() {
|
|||||||
loadChannels();
|
loadChannels();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
|
||||||
if (status.isLoaded) {
|
|
||||||
setIsPlayerLoading(!status.isPlaying && !status.isBuffering);
|
|
||||||
} else {
|
|
||||||
setIsPlayerLoading(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const showChannelTitle = (title: string) => {
|
const showChannelTitle = (title: string) => {
|
||||||
setChannelTitle(title);
|
setChannelTitle(title);
|
||||||
if (titleTimer.current) clearTimeout(titleTimer.current);
|
if (titleTimer.current) clearTimeout(titleTimer.current);
|
||||||
@@ -98,16 +89,7 @@ export default function LiveScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemedView style={styles.container}>
|
<ThemedView style={styles.container}>
|
||||||
<LivePlayer
|
<LivePlayer streamUrl={selectedChannelUrl} channelTitle={channelTitle} onPlaybackStatusUpdate={() => {}} />
|
||||||
streamUrl={selectedChannelUrl}
|
|
||||||
channelTitle={channelTitle}
|
|
||||||
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
|
||||||
/>
|
|
||||||
{isPlayerLoading && (
|
|
||||||
<View style={styles.loadingOverlay}>
|
|
||||||
<ActivityIndicator size="large" color="#fff" />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Modal
|
<Modal
|
||||||
animationType="slide"
|
animationType="slide"
|
||||||
transparent={true}
|
transparent={true}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
import { View, StyleSheet, Text, ActivityIndicator } from "react-native";
|
import { View, StyleSheet, Text, ActivityIndicator } from "react-native";
|
||||||
import { Video, ResizeMode, AVPlaybackStatus } from "expo-av";
|
import { Video, ResizeMode, AVPlaybackStatus } from "expo-av";
|
||||||
|
|
||||||
@@ -8,13 +8,73 @@ interface LivePlayerProps {
|
|||||||
onPlaybackStatusUpdate: (status: AVPlaybackStatus) => void;
|
onPlaybackStatusUpdate: (status: AVPlaybackStatus) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PLAYBACK_TIMEOUT = 15000; // 15 seconds
|
||||||
|
|
||||||
export default function LivePlayer({ streamUrl, channelTitle, onPlaybackStatusUpdate }: LivePlayerProps) {
|
export default function LivePlayer({ streamUrl, channelTitle, onPlaybackStatusUpdate }: LivePlayerProps) {
|
||||||
const video = useRef<Video>(null);
|
const video = useRef<Video>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isTimeout, setIsTimeout] = useState(false);
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streamUrl) {
|
||||||
|
setIsLoading(true);
|
||||||
|
setIsTimeout(false);
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
setIsTimeout(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
}, PLAYBACK_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsTimeout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [streamUrl]);
|
||||||
|
|
||||||
|
const handlePlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
||||||
|
if (status.isLoaded) {
|
||||||
|
if (status.isPlaying) {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsTimeout(false);
|
||||||
|
} else if (status.isBuffering) {
|
||||||
|
setIsLoading(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (status.error) {
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsTimeout(true);
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onPlaybackStatusUpdate(status);
|
||||||
|
};
|
||||||
|
|
||||||
if (!streamUrl) {
|
if (!streamUrl) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text>Select a channel to play.</Text>
|
<Text style={styles.messageText}>Select a channel to play.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTimeout) {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.messageText}>Failed to load stream. It might be offline or unavailable.</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -29,9 +89,19 @@ export default function LivePlayer({ streamUrl, channelTitle, onPlaybackStatusUp
|
|||||||
}}
|
}}
|
||||||
resizeMode={ResizeMode.CONTAIN}
|
resizeMode={ResizeMode.CONTAIN}
|
||||||
shouldPlay
|
shouldPlay
|
||||||
onPlaybackStatusUpdate={onPlaybackStatusUpdate}
|
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
||||||
|
onError={(e) => {
|
||||||
|
setIsTimeout(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{channelTitle && (
|
{isLoading && (
|
||||||
|
<View style={styles.loadingOverlay}>
|
||||||
|
<ActivityIndicator size="large" color="#fff" />
|
||||||
|
<Text style={styles.messageText}>Loading...</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{channelTitle && !isLoading && !isTimeout && (
|
||||||
<View style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
<Text style={styles.title}>{channelTitle}</Text>
|
<Text style={styles.title}>{channelTitle}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -63,4 +133,15 @@ const styles = StyleSheet.create({
|
|||||||
color: "#fff",
|
color: "#fff",
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
},
|
},
|
||||||
|
messageText: {
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 16,
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
loadingOverlay: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user