mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
feat: enhance LivePlayer messages with localized text and improve M3U parsing logic
This commit is contained in:
1
app.json
1
app.json
@@ -38,6 +38,7 @@
|
||||
"android": {
|
||||
"package": "com.oriontv",
|
||||
"usesCleartextTraffic": true,
|
||||
"hardwareAcceleration": true,
|
||||
"networkSecurityConfig": "@xml/network_security_config",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"permissions": [
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function LiveScreen() {
|
||||
<View style={styles.groupColumn}>
|
||||
<FlatList
|
||||
data={channelGroups}
|
||||
keyExtractor={(item) => item}
|
||||
keyExtractor={(item, index) => `group-${item}-${index}`}
|
||||
renderItem={({ item }) => (
|
||||
<StyledButton
|
||||
text={item}
|
||||
@@ -124,7 +124,7 @@ export default function LiveScreen() {
|
||||
) : (
|
||||
<FlatList
|
||||
data={groupedChannels[selectedGroup] || []}
|
||||
keyExtractor={(item) => item.id}
|
||||
keyExtractor={(item, index) => `${item.id}-${item.group}-${index}`}
|
||||
renderItem={({ item }) => (
|
||||
<StyledButton
|
||||
text={item.name || "Unknown Channel"}
|
||||
@@ -190,6 +190,8 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
marginVertical: 4,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
},
|
||||
groupButtonText: {
|
||||
fontSize: 13,
|
||||
@@ -198,6 +200,8 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 4,
|
||||
marginVertical: 3,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
},
|
||||
channelItemText: {
|
||||
fontSize: 12,
|
||||
|
||||
@@ -42,13 +42,13 @@ export default function PlayScreen() {
|
||||
const {
|
||||
isLoading,
|
||||
showControls,
|
||||
showNextEpisodeOverlay,
|
||||
// showNextEpisodeOverlay,
|
||||
initialPosition,
|
||||
introEndTime,
|
||||
setVideoRef,
|
||||
handlePlaybackStatusUpdate,
|
||||
setShowControls,
|
||||
setShowNextEpisodeOverlay,
|
||||
// setShowNextEpisodeOverlay,
|
||||
reset,
|
||||
loadVideo,
|
||||
} = usePlayerStore();
|
||||
@@ -151,7 +151,7 @@ export default function PlayScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
<NextEpisodeOverlay visible={showNextEpisodeOverlay} onCancel={() => setShowNextEpisodeOverlay(false)} />
|
||||
{/* <NextEpisodeOverlay visible={showNextEpisodeOverlay} onCancel={() => setShowNextEpisodeOverlay(false)} /> */}
|
||||
</TouchableOpacity>
|
||||
|
||||
<EpisodeSelectionModal />
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function LivePlayer({ streamUrl, channelTitle, onPlaybackStatusUp
|
||||
if (!streamUrl) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.messageText}>Select a channel to play.</Text>
|
||||
<Text style={styles.messageText}>按向下键选择频道</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export default function LivePlayer({ streamUrl, channelTitle, onPlaybackStatusUp
|
||||
if (isTimeout) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.messageText}>Failed to load stream. It might be offline or unavailable.</Text>
|
||||
<Text style={styles.messageText}>加载失败,请重试</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export default function LivePlayer({ streamUrl, channelTitle, onPlaybackStatusUp
|
||||
{isLoading && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator size="large" color="#fff" />
|
||||
<Text style={styles.messageText}>Loading...</Text>
|
||||
<Text style={styles.messageText}>加载中...</Text>
|
||||
</View>
|
||||
)}
|
||||
{channelTitle && !isLoading && !isTimeout && (
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "OrionTV",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.2.5",
|
||||
"version": "1.2.6",
|
||||
"scripts": {
|
||||
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||
|
||||
@@ -15,23 +15,38 @@ export const parseM3U = (m3uText: string): Channel[] => {
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
currentChannelInfo = { id: '', name: '', url: '', logo: '', group: '' };
|
||||
if (trimmedLine.startsWith('#EXTINF:')) {
|
||||
const commaIndex = trimmedLine.indexOf(',');
|
||||
currentChannelInfo = {}; // Start a new channel
|
||||
const commaIndex = trimmedLine.lastIndexOf(',');
|
||||
if (commaIndex !== -1) {
|
||||
currentChannelInfo.name = trimmedLine.substring(commaIndex + 1).trim();
|
||||
const attributesPart = trimmedLine.substring(8, commaIndex);
|
||||
const logoMatch = attributesPart.match(/tvg-logo="([^"]*)"/i);
|
||||
if (logoMatch && logoMatch[1]) currentChannelInfo.logo = logoMatch[1];
|
||||
if (logoMatch && logoMatch[1]) {
|
||||
currentChannelInfo.logo = logoMatch[1];
|
||||
}
|
||||
const groupMatch = attributesPart.match(/group-title="([^"]*)"/i);
|
||||
if (groupMatch && groupMatch[1]) currentChannelInfo.group = groupMatch[1];
|
||||
if (groupMatch && groupMatch[1]) {
|
||||
currentChannelInfo.group = groupMatch[1];
|
||||
}
|
||||
} else {
|
||||
currentChannelInfo.name = trimmedLine.substring(8).trim();
|
||||
}
|
||||
} else if (currentChannelInfo && trimmedLine && !trimmedLine.startsWith('#') && trimmedLine.includes('://')) {
|
||||
currentChannelInfo.url = trimmedLine;
|
||||
currentChannelInfo.id = currentChannelInfo.url; // Use URL as ID
|
||||
parsedChannels.push(currentChannelInfo as Channel);
|
||||
|
||||
// Ensure all required fields are present, providing defaults if necessary
|
||||
const finalChannel: Channel = {
|
||||
id: currentChannelInfo.id,
|
||||
url: currentChannelInfo.url,
|
||||
name: currentChannelInfo.name || 'Unknown',
|
||||
logo: currentChannelInfo.logo || '',
|
||||
group: currentChannelInfo.group || 'Default',
|
||||
};
|
||||
|
||||
parsedChannels.push(finalChannel);
|
||||
currentChannelInfo = null; // Reset for the next channel
|
||||
}
|
||||
}
|
||||
return parsedChannels;
|
||||
@@ -56,14 +71,14 @@ export const getPlayableUrl = (originalUrl: string | null): string | null => {
|
||||
return null;
|
||||
}
|
||||
// In React Native, we use the proxy for all http streams to avoid potential issues.
|
||||
if (originalUrl.toLowerCase().startsWith('http://')) {
|
||||
// Use the baseURL from the existing api instance.
|
||||
if (!api.baseURL) {
|
||||
console.warn("API base URL is not set. Cannot create proxy URL.")
|
||||
return originalUrl; // Fallback to original URL
|
||||
}
|
||||
return `${api.baseURL}/proxy?url=${encodeURIComponent(originalUrl)}`;
|
||||
}
|
||||
// if (originalUrl.toLowerCase().startsWith('http://')) {
|
||||
// // Use the baseURL from the existing api instance.
|
||||
// if (!api.baseURL) {
|
||||
// console.warn("API base URL is not set. Cannot create proxy URL.")
|
||||
// return originalUrl; // Fallback to original URL
|
||||
// }
|
||||
// return `${api.baseURL}/proxy?url=${encodeURIComponent(originalUrl)}`;
|
||||
// }
|
||||
// HTTPS streams can be played directly.
|
||||
return originalUrl;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user