mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
refactor: update storage management to use centralized storage configuration and improve README documentation
This commit is contained in:
39
README.md
39
README.md
@@ -18,10 +18,6 @@
|
|||||||
- [Expo Router](https://docs.expo.dev/router/introduction/)
|
- [Expo Router](https://docs.expo.dev/router/introduction/)
|
||||||
- [Expo AV](https://docs.expo.dev/versions/latest/sdk/av/)
|
- [Expo AV](https://docs.expo.dev/versions/latest/sdk/av/)
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- **后端**:
|
|
||||||
- [Node.js](https://nodejs.org/)
|
|
||||||
- [Express](https://expressjs.com/)
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/)
|
|
||||||
|
|
||||||
## 📂 项目结构
|
## 📂 项目结构
|
||||||
|
|
||||||
@@ -31,7 +27,6 @@
|
|||||||
.
|
.
|
||||||
├── app/ # Expo Router 路由和页面
|
├── app/ # Expo Router 路由和页面
|
||||||
├── assets/ # 静态资源 (字体, 图片, TV 图标)
|
├── assets/ # 静态资源 (字体, 图片, TV 图标)
|
||||||
├── backend/ # 后端 Express 应用
|
|
||||||
├── components/ # React 组件
|
├── components/ # React 组件
|
||||||
├── constants/ # 应用常量 (颜色, 样式)
|
├── constants/ # 应用常量 (颜色, 样式)
|
||||||
├── hooks/ # 自定义 Hooks
|
├── hooks/ # 自定义 Hooks
|
||||||
@@ -52,24 +47,7 @@
|
|||||||
- [Xcode](https://developer.apple.com/xcode/) (用于 Apple TV 开发)
|
- [Xcode](https://developer.apple.com/xcode/) (用于 Apple TV 开发)
|
||||||
- [Android Studio](https://developer.android.com/studio) (用于 Android TV 开发)
|
- [Android Studio](https://developer.android.com/studio) (用于 Android TV 开发)
|
||||||
|
|
||||||
### 1. 后端服务
|
### 项目启动
|
||||||
|
|
||||||
首先,启动后端服务:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 进入后端目录
|
|
||||||
cd backend
|
|
||||||
|
|
||||||
# 安装依赖
|
|
||||||
yarn
|
|
||||||
|
|
||||||
# 启动开发服务器
|
|
||||||
yarn dev
|
|
||||||
```
|
|
||||||
|
|
||||||
后端服务将运行在 `http://localhost:3001`。
|
|
||||||
|
|
||||||
### 2. 前端应用
|
|
||||||
|
|
||||||
接下来,在项目根目录运行前端应用:
|
接下来,在项目根目录运行前端应用:
|
||||||
|
|
||||||
@@ -93,19 +71,9 @@ yarn android-tv
|
|||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
### 后端部署
|
推荐使用 [MoonTV](https://github.com/senshinya/MoonTV) 部署,地址可直接使用部署后的访问地址。
|
||||||
|
|
||||||
#### [Vercel](https://vercel.com/) 部署
|
如果不想依赖 MoonTV,可以使用 1.1.x 版本。
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fzimplexing%2FOrionTV&root-directory=backend)
|
|
||||||
|
|
||||||
如果 vercel 部署后遇到 `404: NOT_FOUNDCode: NOT_FOUND`,请检查 vercel 项目的根目录是否是 `beackend`
|
|
||||||
|
|
||||||
#### Docker 部署
|
|
||||||
|
|
||||||
1. `docker pull zimpel1/tv-host`
|
|
||||||
|
|
||||||
2. `docker run -d -p 3001:3001 zimpel1/tv-host`
|
|
||||||
|
|
||||||
## 其他
|
## 其他
|
||||||
|
|
||||||
@@ -153,4 +121,3 @@ OrionTV 仅作为视频搜索工具,不存储、上传或分发任何视频内
|
|||||||
|
|
||||||
- [gpt-load](https://github.com/tbphp/gpt-load) - 一个高性能的 OpenAI 格式 API 多密钥轮询代理服务器,支持负载均衡,使用 Go 语言开发
|
- [gpt-load](https://github.com/tbphp/gpt-load) - 一个高性能的 OpenAI 格式 API 多密钥轮询代理服务器,支持负载均衡,使用 Go 语言开发
|
||||||
- [one-balance](https://github.com/glidea/one-balance) - Make ai KEY rotation SMARTER and more SECURE
|
- [one-balance](https://github.com/glidea/one-balance) - Make ai KEY rotation SMARTER and more SECURE
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { api, PlayRecord as ApiPlayRecord, Favorite as ApiFavorite } from "./api";
|
import { api, PlayRecord as ApiPlayRecord, Favorite as ApiFavorite } from "./api";
|
||||||
import { useSettingsStore } from "@/stores/settingsStore";
|
import { storageConfig } from "./storageConfig";
|
||||||
|
|
||||||
// --- Storage Keys ---
|
// --- Storage Keys ---
|
||||||
const STORAGE_KEYS = {
|
const STORAGE_KEYS = {
|
||||||
@@ -83,7 +83,7 @@ export class PlayerSettingsManager {
|
|||||||
// --- FavoriteManager (Dynamic: API or LocalStorage) ---
|
// --- FavoriteManager (Dynamic: API or LocalStorage) ---
|
||||||
export class FavoriteManager {
|
export class FavoriteManager {
|
||||||
private static getStorageType() {
|
private static getStorageType() {
|
||||||
return useSettingsStore.getState().serverConfig?.StorageType;
|
return storageConfig.getStorageType();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAll(): Promise<Record<string, Favorite>> {
|
static async getAll(): Promise<Record<string, Favorite>> {
|
||||||
@@ -154,7 +154,7 @@ export class FavoriteManager {
|
|||||||
// --- PlayRecordManager (Dynamic: API or LocalStorage) ---
|
// --- PlayRecordManager (Dynamic: API or LocalStorage) ---
|
||||||
export class PlayRecordManager {
|
export class PlayRecordManager {
|
||||||
private static getStorageType() {
|
private static getStorageType() {
|
||||||
return useSettingsStore.getState().serverConfig?.StorageType;
|
return storageConfig.getStorageType();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAll(): Promise<Record<string, PlayRecord>> {
|
static async getAll(): Promise<Record<string, PlayRecord>> {
|
||||||
@@ -232,7 +232,7 @@ export class PlayRecordManager {
|
|||||||
// --- SearchHistoryManager (Dynamic: API or LocalStorage) ---
|
// --- SearchHistoryManager (Dynamic: API or LocalStorage) ---
|
||||||
export class SearchHistoryManager {
|
export class SearchHistoryManager {
|
||||||
private static getStorageType() {
|
private static getStorageType() {
|
||||||
return useSettingsStore.getState().serverConfig?.StorageType;
|
return storageConfig.getStorageType();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async get(): Promise<string[]> {
|
static async get(): Promise<string[]> {
|
||||||
@@ -270,7 +270,7 @@ export class SearchHistoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SettingsManager (Remains unchanged, uses AsyncStorage) ---
|
// --- SettingsManager (Uses AsyncStorage) ---
|
||||||
export class SettingsManager {
|
export class SettingsManager {
|
||||||
static async get(): Promise<AppSettings> {
|
static async get(): Promise<AppSettings> {
|
||||||
const defaultSettings: AppSettings = {
|
const defaultSettings: AppSettings = {
|
||||||
@@ -280,8 +280,7 @@ export class SettingsManager {
|
|||||||
enabledAll: true,
|
enabledAll: true,
|
||||||
sources: {},
|
sources: {},
|
||||||
},
|
},
|
||||||
m3uUrl:
|
m3uUrl: "",
|
||||||
"",
|
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);
|
const data = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);
|
||||||
|
|||||||
20
services/storageConfig.ts
Normal file
20
services/storageConfig.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Define a simple storage configuration service
|
||||||
|
export interface StorageConfig {
|
||||||
|
storageType: string | undefined;
|
||||||
|
getStorageType: () => string | undefined;
|
||||||
|
setStorageType: (type: string | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a singleton instance
|
||||||
|
export const storageConfig: StorageConfig = {
|
||||||
|
// Default to undefined (will fallback to local storage)
|
||||||
|
storageType: undefined,
|
||||||
|
|
||||||
|
getStorageType() {
|
||||||
|
return this.storageType;
|
||||||
|
},
|
||||||
|
|
||||||
|
setStorageType(type: string | undefined) {
|
||||||
|
this.storageType = type;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -73,18 +73,18 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
|||||||
|
|
||||||
set({
|
set({
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
currentEpisodeIndex: episodeIndex,
|
|
||||||
initialPosition: position || 0,
|
|
||||||
episodes: episodes.map((ep, index) => ({
|
|
||||||
url: ep,
|
|
||||||
title: `第 ${index + 1} 集`,
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const playRecord = await PlayRecordManager.get(detail.source, detail.id.toString());
|
const playRecord = await PlayRecordManager.get(detail.source, detail.id.toString());
|
||||||
set({
|
set({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
currentEpisodeIndex: episodeIndex,
|
||||||
|
initialPosition: position || 0,
|
||||||
|
episodes: episodes.map((ep, index) => ({
|
||||||
|
url: ep,
|
||||||
|
title: `第 ${index + 1} 集`,
|
||||||
|
})),
|
||||||
introEndTime: playRecord?.introEndTime,
|
introEndTime: playRecord?.introEndTime,
|
||||||
outroStartTime: playRecord?.outroStartTime,
|
outroStartTime: playRecord?.outroStartTime,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
import { SettingsManager } from '@/services/storage';
|
import { SettingsManager } from "@/services/storage";
|
||||||
import { api, ServerConfig } from '@/services/api';
|
import { api, ServerConfig } from "@/services/api";
|
||||||
|
import { storageConfig } from "@/services/storageConfig";
|
||||||
// import useHomeStore from './homeStore';
|
// import useHomeStore from './homeStore';
|
||||||
|
|
||||||
|
|
||||||
interface SettingsState {
|
interface SettingsState {
|
||||||
apiBaseUrl: string;
|
apiBaseUrl: string;
|
||||||
m3uUrl: string;
|
m3uUrl: string;
|
||||||
@@ -22,14 +22,14 @@ interface SettingsState {
|
|||||||
setM3uUrl: (url: string) => void;
|
setM3uUrl: (url: string) => void;
|
||||||
setRemoteInputEnabled: (enabled: boolean) => void;
|
setRemoteInputEnabled: (enabled: boolean) => void;
|
||||||
saveSettings: () => Promise<void>;
|
saveSettings: () => Promise<void>;
|
||||||
setVideoSource: (config: { enabledAll: boolean; sources: {[key: string]: boolean} }) => void;
|
setVideoSource: (config: { enabledAll: boolean; sources: { [key: string]: boolean } }) => void;
|
||||||
showModal: () => void;
|
showModal: () => void;
|
||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSettingsStore = create<SettingsState>((set, get) => ({
|
export const useSettingsStore = create<SettingsState>((set, get) => ({
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: "",
|
||||||
m3uUrl: '',
|
m3uUrl: "",
|
||||||
liveStreamSources: [],
|
liveStreamSources: [],
|
||||||
remoteInputEnabled: false,
|
remoteInputEnabled: false,
|
||||||
isModalVisible: false,
|
isModalVisible: false,
|
||||||
@@ -55,6 +55,9 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
|||||||
fetchServerConfig: async () => {
|
fetchServerConfig: async () => {
|
||||||
try {
|
try {
|
||||||
const config = await api.getServerConfig();
|
const config = await api.getServerConfig();
|
||||||
|
if (config) {
|
||||||
|
storageConfig.setStorageType(config.StorageType);
|
||||||
|
}
|
||||||
set({ serverConfig: config });
|
set({ serverConfig: config });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.info("Failed to fetch server config:", error);
|
console.info("Failed to fetch server config:", error);
|
||||||
@@ -66,7 +69,7 @@ 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();
|
||||||
await SettingsManager.save({
|
await SettingsManager.save({
|
||||||
apiBaseUrl,
|
apiBaseUrl,
|
||||||
m3uUrl,
|
m3uUrl,
|
||||||
remoteInputEnabled,
|
remoteInputEnabled,
|
||||||
@@ -78,4 +81,4 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
showModal: () => set({ isModalVisible: true }),
|
showModal: () => set({ isModalVisible: true }),
|
||||||
hideModal: () => set({ isModalVisible: false }),
|
hideModal: () => set({ isModalVisible: false }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user