Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fdd1fc587 | ||
|
|
4b3d1c620b | ||
|
|
1f694f9245 | ||
|
|
ec949029fa | ||
|
|
2325b76f77 | ||
|
|
4473fd6ab3 |
10
README.md
@@ -71,9 +71,11 @@ yarn android-tv
|
|||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
推荐使用 [MoonTV](https://github.com/senshinya/MoonTV) 部署,地址可直接使用部署后的访问地址。
|
- 1.2.x 以上版本需配合 [MoonTV](https://github.com/senshinya/MoonTV) 部署使用,api 地址填部MoonTV署后的访问地址。
|
||||||
|
|
||||||
如果不想依赖 MoonTV,可以使用 1.1.x 版本。
|
- **注意:** 地址后面不要带 `/` ,不要遗漏 `http://` 或者 `https://`
|
||||||
|
|
||||||
|
- 如果不想依赖 MoonTV,可以使用 1.1.x 版本。
|
||||||
|
|
||||||
## 其他
|
## 其他
|
||||||
|
|
||||||
@@ -92,9 +94,9 @@ yarn android-tv
|
|||||||
## 📸 应用截图
|
## 📸 应用截图
|
||||||
|
|
||||||

|

|
||||||

|
|
||||||

|
|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## 📝 License
|
## 📝 License
|
||||||
|
|
||||||
|
|||||||
23
app/play.tsx
@@ -96,6 +96,25 @@ export default function PlayScreen() {
|
|||||||
return () => backHandler.remove();
|
return () => backHandler.remove();
|
||||||
}, [showControls, setShowControls, router]);
|
}, [showControls, setShowControls, router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
if (usePlayerStore.getState().isLoading) {
|
||||||
|
usePlayerStore.setState({ isLoading: false });
|
||||||
|
Toast.show({ type: "error", text1: "播放超时,请重试" });
|
||||||
|
}
|
||||||
|
}, 60000); // 1 minute
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isLoading]);
|
||||||
|
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
return (
|
return (
|
||||||
<ThemedView style={[styles.container, styles.centered]}>
|
<ThemedView style={[styles.container, styles.centered]}>
|
||||||
@@ -122,10 +141,6 @@ export default function PlayScreen() {
|
|||||||
}
|
}
|
||||||
usePlayerStore.setState({ isLoading: false });
|
usePlayerStore.setState({ isLoading: false });
|
||||||
}}
|
}}
|
||||||
onError={() => {
|
|
||||||
usePlayerStore.setState({ isLoading: false });
|
|
||||||
Toast.show({ type: "error", text1: "播放失败,请更换源后重试" });
|
|
||||||
}}
|
|
||||||
onLoadStart={() => usePlayerStore.setState({ isLoading: true })}
|
onLoadStart={() => usePlayerStore.setState({ isLoading: true })}
|
||||||
useNativeControls={false}
|
useNativeControls={false}
|
||||||
shouldPlay
|
shouldPlay
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "OrionTV",
|
"name": "OrionTV",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||||
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 731 KiB After Width: | Height: | Size: 533 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 520 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 672 KiB |
@@ -68,14 +68,35 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
|||||||
setVideoSource: (config) => set({ videoSource: config }),
|
setVideoSource: (config) => set({ videoSource: config }),
|
||||||
saveSettings: async () => {
|
saveSettings: async () => {
|
||||||
const { apiBaseUrl, m3uUrl, remoteInputEnabled, videoSource } = get();
|
const { apiBaseUrl, m3uUrl, remoteInputEnabled, videoSource } = get();
|
||||||
|
|
||||||
|
let processedApiBaseUrl = apiBaseUrl.trim();
|
||||||
|
if (processedApiBaseUrl.endsWith("/")) {
|
||||||
|
processedApiBaseUrl = processedApiBaseUrl.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^https?:\/\//i.test(processedApiBaseUrl)) {
|
||||||
|
const hostPart = processedApiBaseUrl.split("/")[0];
|
||||||
|
// Simple check for IP address format.
|
||||||
|
const isIpAddress = /^((\d{1,3}\.){3}\d{1,3})(:\d+)?$/.test(hostPart);
|
||||||
|
// Check if the domain includes a port.
|
||||||
|
const hasPort = /:\d+/.test(hostPart);
|
||||||
|
|
||||||
|
if (isIpAddress || hasPort) {
|
||||||
|
processedApiBaseUrl = "http://" + processedApiBaseUrl;
|
||||||
|
} else {
|
||||||
|
processedApiBaseUrl = "https://" + processedApiBaseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await SettingsManager.save({
|
await SettingsManager.save({
|
||||||
apiBaseUrl,
|
apiBaseUrl: processedApiBaseUrl,
|
||||||
m3uUrl,
|
m3uUrl,
|
||||||
remoteInputEnabled,
|
remoteInputEnabled,
|
||||||
videoSource,
|
videoSource,
|
||||||
});
|
});
|
||||||
api.setBaseUrl(apiBaseUrl);
|
api.setBaseUrl(processedApiBaseUrl);
|
||||||
set({ isModalVisible: false });
|
// Also update the URL in the state so the input field shows the processed URL
|
||||||
|
set({ isModalVisible: false, apiBaseUrl: processedApiBaseUrl });
|
||||||
await get().fetchServerConfig();
|
await get().fetchServerConfig();
|
||||||
},
|
},
|
||||||
showModal: () => set({ isModalVisible: true }),
|
showModal: () => set({ isModalVisible: true }),
|
||||||
|
|||||||