mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 11:44:44 +08:00
refactor: update play time property and enhance player settings management
- Changed the play time property from 'play_time' to 'time' in HomeScreen. - Removed unused player control functions from PlayScreen. - Added PlayerSettings interface and implemented PlayerSettingsManager for local storage of player settings. - Refactored PlayRecordManager to merge API records with local player settings. - Updated authentication logic in authStore to handle optional username parameter in login function. - Cleaned up and optimized imports across various components.
This commit is contained in:
@@ -96,7 +96,7 @@ export default function HomeScreen() {
|
||||
year={item.year}
|
||||
rate={item.rate}
|
||||
progress={item.progress}
|
||||
playTime={item.play_time}
|
||||
playTime={item.time}
|
||||
episodeIndex={item.episodeIndex}
|
||||
sourceName={item.sourceName}
|
||||
totalEpisodes={item.totalEpisodes}
|
||||
|
||||
@@ -26,7 +26,6 @@ export default function PlayScreen() {
|
||||
|
||||
const { detail } = useDetailStore();
|
||||
const {
|
||||
status,
|
||||
isLoading,
|
||||
showControls,
|
||||
showNextEpisodeOverlay,
|
||||
@@ -37,9 +36,6 @@ export default function PlayScreen() {
|
||||
setShowControls,
|
||||
setShowNextEpisodeOverlay,
|
||||
reset,
|
||||
playEpisode,
|
||||
togglePlayPause,
|
||||
seek,
|
||||
} = usePlayerStore();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface PlayRecord {
|
||||
play_time: number;
|
||||
total_time: number;
|
||||
save_time: number;
|
||||
year: string;
|
||||
}
|
||||
|
||||
export interface ApiSite {
|
||||
@@ -108,7 +109,7 @@ export class API {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async login(username: string | undefined, password: string): Promise<{ ok: boolean }> {
|
||||
async login(username?: string | undefined, password?: string): Promise<{ ok: boolean }> {
|
||||
const response = await this._fetch("/api/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
@@ -4,6 +4,7 @@ import { api, PlayRecord as ApiPlayRecord, Favorite as ApiFavorite } from "./api
|
||||
// --- Storage Keys ---
|
||||
const STORAGE_KEYS = {
|
||||
SETTINGS: "mytv_settings",
|
||||
PLAYER_SETTINGS: "mytv_player_settings",
|
||||
} as const;
|
||||
|
||||
// --- Type Definitions (aligned with api.ts) ---
|
||||
@@ -14,6 +15,11 @@ export type PlayRecord = ApiPlayRecord & {
|
||||
};
|
||||
export type Favorite = ApiFavorite;
|
||||
|
||||
export interface PlayerSettings {
|
||||
introEndTime?: number;
|
||||
outroStartTime?: number;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
apiBaseUrl: string;
|
||||
remoteInputEnabled: boolean;
|
||||
@@ -29,6 +35,47 @@ export interface AppSettings {
|
||||
// --- Helper ---
|
||||
const generateKey = (source: string, id: string) => `${source}+${id}`;
|
||||
|
||||
// --- PlayerSettingsManager (Uses AsyncStorage) ---
|
||||
export class PlayerSettingsManager {
|
||||
static async getAll(): Promise<Record<string, PlayerSettings>> {
|
||||
try {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAYER_SETTINGS);
|
||||
return data ? JSON.parse(data) : {};
|
||||
} catch (error) {
|
||||
console.error("Failed to get all player settings:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static async get(source: string, id: string): Promise<PlayerSettings | null> {
|
||||
const allSettings = await this.getAll();
|
||||
return allSettings[generateKey(source, id)] || null;
|
||||
}
|
||||
|
||||
static async save(source: string, id: string, settings: PlayerSettings): Promise<void> {
|
||||
const allSettings = await this.getAll();
|
||||
const key = generateKey(source, id);
|
||||
// Only save if there are actual values to save
|
||||
if (settings.introEndTime !== undefined || settings.outroStartTime !== undefined) {
|
||||
allSettings[key] = { ...allSettings[key], ...settings };
|
||||
} else {
|
||||
// If both are undefined, remove the key
|
||||
delete allSettings[key];
|
||||
}
|
||||
await AsyncStorage.setItem(STORAGE_KEYS.PLAYER_SETTINGS, JSON.stringify(allSettings));
|
||||
}
|
||||
|
||||
static async remove(source: string, id: string): Promise<void> {
|
||||
const allSettings = await this.getAll();
|
||||
delete allSettings[generateKey(source, id)];
|
||||
await AsyncStorage.setItem(STORAGE_KEYS.PLAYER_SETTINGS, JSON.stringify(allSettings));
|
||||
}
|
||||
|
||||
static async clearAll(): Promise<void> {
|
||||
await AsyncStorage.removeItem(STORAGE_KEYS.PLAYER_SETTINGS);
|
||||
}
|
||||
}
|
||||
|
||||
// --- FavoriteManager (Refactored to use API) ---
|
||||
export class FavoriteManager {
|
||||
static async getAll(): Promise<Record<string, Favorite>> {
|
||||
@@ -67,30 +114,53 @@ export class FavoriteManager {
|
||||
}
|
||||
}
|
||||
|
||||
// --- PlayRecordManager (Refactored to use API) ---
|
||||
// --- PlayRecordManager (Refactored to use API and local settings) ---
|
||||
export class PlayRecordManager {
|
||||
static async getAll(): Promise<Record<string, PlayRecord>> {
|
||||
return (await api.getPlayRecords()) as Record<string, PlayRecord>;
|
||||
const apiRecords = await api.getPlayRecords();
|
||||
const localSettings = await PlayerSettingsManager.getAll();
|
||||
|
||||
const mergedRecords: Record<string, PlayRecord> = {};
|
||||
for (const key in apiRecords) {
|
||||
mergedRecords[key] = {
|
||||
...apiRecords[key],
|
||||
...localSettings[key],
|
||||
};
|
||||
}
|
||||
return mergedRecords;
|
||||
}
|
||||
|
||||
static async save(source: string, id: string, record: Omit<PlayRecord, "save_time">): Promise<void> {
|
||||
const key = generateKey(source, id);
|
||||
// The API will handle setting the save_time
|
||||
await api.savePlayRecord(key, record);
|
||||
const { introEndTime, outroStartTime, ...apiRecord } = record;
|
||||
|
||||
// Save player settings locally
|
||||
await PlayerSettingsManager.save(source, id, { introEndTime, outroStartTime });
|
||||
|
||||
// Save core record to API
|
||||
await api.savePlayRecord(key, apiRecord);
|
||||
}
|
||||
|
||||
static async get(source: string, id: string): Promise<PlayRecord | null> {
|
||||
const key = generateKey(source, id);
|
||||
// This can be optimized, but for consistency, we call getAll
|
||||
const records = await this.getAll();
|
||||
return records[generateKey(source, id)] || null;
|
||||
return records[key] || null;
|
||||
}
|
||||
|
||||
static async remove(source: string, id: string): Promise<void> {
|
||||
const key = generateKey(source, id);
|
||||
// Remove from API first
|
||||
await api.deletePlayRecord(key);
|
||||
// Then remove from local settings
|
||||
await PlayerSettingsManager.remove(source, id);
|
||||
}
|
||||
|
||||
static async clearAll(): Promise<void> {
|
||||
// Clear from API first
|
||||
await api.deletePlayRecord();
|
||||
// Then clear from local settings
|
||||
await PlayerSettingsManager.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ const useAuthStore = create<AuthState>((set) => ({
|
||||
hideLoginModal: () => set({ isLoginModalVisible: false }),
|
||||
checkLoginStatus: async () => {
|
||||
try {
|
||||
const { ok } = await api.login();
|
||||
if (ok) {
|
||||
set({ isLoggedIn: true });
|
||||
return;
|
||||
}
|
||||
const cookies = await Cookies.get(api.baseURL);
|
||||
const isLoggedIn = cookies && !!cookies.auth;
|
||||
set({ isLoggedIn });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { api, SearchResult, PlayRecord } from '@/services/api';
|
||||
import { PlayRecordManager } from '@/services/storage';
|
||||
import useAuthStore from './authStore';
|
||||
import { create } from "zustand";
|
||||
import { api, SearchResult, PlayRecord } from "@/services/api";
|
||||
import { PlayRecordManager } from "@/services/storage";
|
||||
import useAuthStore from "./authStore";
|
||||
|
||||
export type RowItem = (SearchResult | PlayRecord) & {
|
||||
id: string;
|
||||
@@ -20,18 +20,38 @@ export type RowItem = (SearchResult | PlayRecord) & {
|
||||
|
||||
export interface Category {
|
||||
title: string;
|
||||
type?: 'movie' | 'tv' | 'record';
|
||||
type?: "movie" | "tv" | "record";
|
||||
tag?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
const initialCategories: Category[] = [
|
||||
{ title: '最近播放', type: 'record' },
|
||||
{ title: '热门剧集', type: 'tv', tag: '热门' },
|
||||
{ title: '电视剧', type: 'tv', tags: [ '国产剧', '美剧', '英剧', '韩剧', '日剧', '港剧', '日本动画', '动画'] },
|
||||
{ title: '电影', type: 'movie', tags: ['热门', '最新', '经典', '豆瓣高分', '冷门佳片', '华语', '欧美', '韩国', '日本', '动作', '喜剧', '爱情', '科幻', '悬疑', '恐怖'] },
|
||||
{ title: '综艺', type: 'tv', tag: '综艺' },
|
||||
{ title: '豆瓣 Top250', type: 'movie', tag: 'top250' },
|
||||
{ title: "最近播放", type: "record" },
|
||||
{ title: "热门剧集", type: "tv", tag: "热门" },
|
||||
{ title: "电视剧", type: "tv", tags: ["国产剧", "美剧", "英剧", "韩剧", "日剧", "港剧", "日本动画", "动画"] },
|
||||
{
|
||||
title: "电影",
|
||||
type: "movie",
|
||||
tags: [
|
||||
"热门",
|
||||
"最新",
|
||||
"经典",
|
||||
"豆瓣高分",
|
||||
"冷门佳片",
|
||||
"华语",
|
||||
"欧美",
|
||||
"韩国",
|
||||
"日本",
|
||||
"动作",
|
||||
"喜剧",
|
||||
"爱情",
|
||||
"科幻",
|
||||
"悬疑",
|
||||
"恐怖",
|
||||
],
|
||||
},
|
||||
{ title: "综艺", type: "tv", tag: "综艺" },
|
||||
{ title: "豆瓣 Top250", type: "movie", tag: "top250" },
|
||||
];
|
||||
|
||||
interface HomeState {
|
||||
@@ -73,7 +93,7 @@ const useHomeStore = create<HomeState>((set, get) => ({
|
||||
}
|
||||
|
||||
try {
|
||||
if (selectedCategory.type === 'record') {
|
||||
if (selectedCategory.type === "record") {
|
||||
const { isLoggedIn } = useAuthStore.getState();
|
||||
if (!isLoggedIn) {
|
||||
set({ contentData: [], hasMore: false });
|
||||
@@ -82,24 +102,35 @@ const useHomeStore = create<HomeState>((set, get) => ({
|
||||
const records = await PlayRecordManager.getAll();
|
||||
const rowItems = Object.entries(records)
|
||||
.map(([key, record]) => {
|
||||
const [source, id] = key.split('+');
|
||||
return { ...record, id, source, progress: record.play_time / record.total_time, poster: record.cover, sourceName: record.source_name, episodeIndex: record.index, totalEpisodes: record.total_episodes, lastPlayed: record.save_time, play_time: record.play_time };
|
||||
const [source, id] = key.split("+");
|
||||
return {
|
||||
...record,
|
||||
id,
|
||||
source,
|
||||
progress: record.play_time / record.total_time,
|
||||
poster: record.cover,
|
||||
sourceName: record.source_name,
|
||||
episodeIndex: record.index,
|
||||
totalEpisodes: record.total_episodes,
|
||||
lastPlayed: record.save_time,
|
||||
play_time: record.play_time,
|
||||
};
|
||||
})
|
||||
.filter(record => record.progress !== undefined && record.progress > 0 && record.progress < 1)
|
||||
.filter((record) => record.progress !== undefined && record.progress > 0 && record.progress < 1)
|
||||
.sort((a, b) => (b.lastPlayed || 0) - (a.lastPlayed || 0));
|
||||
|
||||
|
||||
set({ contentData: rowItems, hasMore: false });
|
||||
} else if (selectedCategory.type && selectedCategory.tag) {
|
||||
const result = await api.getDoubanData(selectedCategory.type, selectedCategory.tag, 20, pageStart);
|
||||
if (result.list.length === 0) {
|
||||
set({ hasMore: false });
|
||||
} else {
|
||||
const newItems = result.list.map(item => ({
|
||||
const newItems = result.list.map((item) => ({
|
||||
...item,
|
||||
id: item.title,
|
||||
source: 'douban',
|
||||
source: "douban",
|
||||
})) as RowItem[];
|
||||
set(state => ({
|
||||
set((state) => ({
|
||||
contentData: pageStart === 0 ? newItems : [...state.contentData, ...newItems],
|
||||
pageStart: state.pageStart + result.list.length,
|
||||
hasMore: true,
|
||||
@@ -112,10 +143,10 @@ const useHomeStore = create<HomeState>((set, get) => ({
|
||||
set({ hasMore: false });
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.message === 'API_URL_NOT_SET') {
|
||||
set({ error: '请点击右上角设置按钮,配置您的 API 地址' });
|
||||
if (err.message === "API_URL_NOT_SET") {
|
||||
set({ error: "请点击右上角设置按钮,配置您的 API 地址" });
|
||||
} else {
|
||||
set({ error: '加载失败,请重试' });
|
||||
set({ error: "加载失败,请重试" });
|
||||
}
|
||||
} finally {
|
||||
set({ loading: false, loadingMore: false });
|
||||
@@ -134,12 +165,12 @@ const useHomeStore = create<HomeState>((set, get) => ({
|
||||
refreshPlayRecords: async () => {
|
||||
const { isLoggedIn } = useAuthStore.getState();
|
||||
if (!isLoggedIn) {
|
||||
set(state => {
|
||||
const recordCategoryExists = state.categories.some(c => c.type === 'record');
|
||||
set((state) => {
|
||||
const recordCategoryExists = state.categories.some((c) => c.type === "record");
|
||||
if (recordCategoryExists) {
|
||||
const newCategories = state.categories.filter(c => c.type !== 'record');
|
||||
if (state.selectedCategory.type === 'record') {
|
||||
get().selectCategory(newCategories[0] || null);
|
||||
const newCategories = state.categories.filter((c) => c.type !== "record");
|
||||
if (state.selectedCategory.type === "record") {
|
||||
get().selectCategory(newCategories[0] || null);
|
||||
}
|
||||
return { categories: newCategories };
|
||||
}
|
||||
@@ -149,24 +180,24 @@ const useHomeStore = create<HomeState>((set, get) => ({
|
||||
}
|
||||
const records = await PlayRecordManager.getAll();
|
||||
const hasRecords = Object.keys(records).length > 0;
|
||||
set(state => {
|
||||
const recordCategoryExists = state.categories.some(c => c.type === 'record');
|
||||
set((state) => {
|
||||
const recordCategoryExists = state.categories.some((c) => c.type === "record");
|
||||
if (hasRecords && !recordCategoryExists) {
|
||||
return { categories: [initialCategories[0], ...state.categories] };
|
||||
}
|
||||
if (!hasRecords && recordCategoryExists) {
|
||||
const newCategories = state.categories.filter(c => c.type !== 'record');
|
||||
if (state.selectedCategory.type === 'record') {
|
||||
get().selectCategory(newCategories[0] || null);
|
||||
const newCategories = state.categories.filter((c) => c.type !== "record");
|
||||
if (state.selectedCategory.type === "record") {
|
||||
get().selectCategory(newCategories[0] || null);
|
||||
}
|
||||
return { categories: newCategories };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
if (get().selectedCategory.type === 'record') {
|
||||
if (get().selectedCategory.type === "record") {
|
||||
get().fetchInitialData();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
export default useHomeStore;
|
||||
export default useHomeStore;
|
||||
|
||||
@@ -27,11 +27,7 @@ interface PlayerState {
|
||||
introEndTime?: number;
|
||||
outroStartTime?: number;
|
||||
setVideoRef: (ref: RefObject<Video>) => void;
|
||||
loadVideo: (
|
||||
source: string,
|
||||
episodeIndex: number,
|
||||
position?: number
|
||||
) => Promise<void>;
|
||||
loadVideo: (source: string, episodeIndex: number, position?: number) => Promise<void>;
|
||||
playEpisode: (index: number) => void;
|
||||
togglePlayPause: () => void;
|
||||
seek: (duration: number) => void;
|
||||
@@ -86,10 +82,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
});
|
||||
|
||||
try {
|
||||
const playRecord = await PlayRecordManager.get(
|
||||
detail.source,
|
||||
detail.id.toString()
|
||||
);
|
||||
const playRecord = await PlayRecordManager.get(detail.source, detail.id.toString());
|
||||
set({
|
||||
isLoading: false,
|
||||
introEndTime: playRecord?.introEndTime,
|
||||
@@ -101,7 +94,6 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
playEpisode: (index) => {
|
||||
const { episodes, videoRef } = get();
|
||||
if (index >= 0 && index < episodes.length) {
|
||||
@@ -209,12 +201,13 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
};
|
||||
PlayRecordManager.save(detail.source, detail.id.toString(), {
|
||||
title: detail.title,
|
||||
poster: detail.poster || "",
|
||||
cover: detail.poster || "",
|
||||
index: currentEpisodeIndex,
|
||||
total_episodes: episodes.length,
|
||||
play_time: status.positionMillis,
|
||||
total_time: status.durationMillis || 0,
|
||||
source_name: detail.source_name,
|
||||
year: detail.year || "",
|
||||
...existingRecord,
|
||||
...updates,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user