mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
- Added login/logout buttons to the HomeScreen and SettingsScreen. - Integrated authentication state management using Zustand and cookies. - Updated API to support username and password for login. - Enhanced PlayScreen to handle video playback based on user authentication. - Created a new detailStore to manage video details and sources. - Refactored playerStore to utilize detailStore for episode management. - Added sourceStore to manage video source toggling. - Updated settingsStore to fetch server configuration. - Improved error handling and user feedback with Toast notifications. - Cleaned up unused code and optimized imports across components.
141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
import { create } from "zustand";
|
|
import { SearchResult, api } from "@/services/api";
|
|
import { getResolutionFromM3U8 } from "@/services/m3u8";
|
|
import { useSettingsStore } from "@/stores/settingsStore";
|
|
|
|
export type SearchResultWithResolution = SearchResult & { resolution?: string | null };
|
|
|
|
interface DetailState {
|
|
q: string | null;
|
|
searchResults: SearchResultWithResolution[];
|
|
sources: { source: string; source_name: string; resolution: string | null | undefined }[];
|
|
detail: SearchResultWithResolution | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
allSourcesLoaded: boolean;
|
|
controller: AbortController | null
|
|
|
|
init: (q: string) => void;
|
|
setDetail: (detail: SearchResultWithResolution) => void;
|
|
abort: () => void;
|
|
}
|
|
|
|
const useDetailStore = create<DetailState>((set, get) => ({
|
|
q: null,
|
|
searchResults: [],
|
|
sources: [],
|
|
detail: null,
|
|
loading: true,
|
|
error: null,
|
|
allSourcesLoaded: false,
|
|
controller: null,
|
|
|
|
init: async (q) => {
|
|
const { controller: oldController } = get();
|
|
if (oldController) {
|
|
oldController.abort();
|
|
}
|
|
const newController = new AbortController();
|
|
const signal = newController.signal;
|
|
|
|
set({
|
|
q,
|
|
loading: true,
|
|
searchResults: [],
|
|
detail: null,
|
|
error: null,
|
|
allSourcesLoaded: false,
|
|
controller: newController,
|
|
});
|
|
|
|
const { videoSource } = useSettingsStore.getState();
|
|
|
|
try {
|
|
const processAndSetResults = async (
|
|
results: SearchResult[]
|
|
) => {
|
|
const resultsWithResolution = await Promise.all(
|
|
results.map(async (searchResult) => {
|
|
let resolution;
|
|
try {
|
|
if (searchResult.episodes && searchResult.episodes.length > 0) {
|
|
resolution = await getResolutionFromM3U8(
|
|
searchResult.episodes[0],
|
|
signal
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if ((e as Error).name !== "AbortError") {
|
|
console.error(
|
|
`Failed to get resolution for ${searchResult.source_name}`,
|
|
e
|
|
);
|
|
}
|
|
}
|
|
return { ...searchResult, resolution };
|
|
})
|
|
);
|
|
|
|
if (signal.aborted) return;
|
|
|
|
set((state) => {
|
|
const existingSources = new Set(state.searchResults.map((r) => r.source));
|
|
const newResults = resultsWithResolution.filter(
|
|
(r) => !existingSources.has(r.source)
|
|
);
|
|
const finalResults = [...state.searchResults, ...newResults];
|
|
return {
|
|
searchResults: finalResults,
|
|
sources: finalResults.map((r) => ({
|
|
source: r.source,
|
|
source_name: r.source_name,
|
|
resolution: r.resolution,
|
|
})),
|
|
detail: state.detail ?? finalResults[0] ?? null,
|
|
};
|
|
});
|
|
};
|
|
|
|
// Background fetch for all sources
|
|
const { results: allResults } = await api.searchVideos(q);
|
|
if (signal.aborted) return;
|
|
|
|
const filteredResults = videoSource.enabledAll
|
|
? allResults
|
|
: allResults.filter((result) => videoSource.sources[result.source]);
|
|
|
|
if (filteredResults.length > 0) {
|
|
await processAndSetResults(filteredResults);
|
|
}
|
|
|
|
if (get().searchResults.length === 0) {
|
|
if (!videoSource.enabledAll) {
|
|
set({ error: "请到设置页面启用的播放源" });
|
|
} else {
|
|
set({ error: "未找到播放源" });
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if ((e as Error).name !== "AbortError") {
|
|
set({ error: e instanceof Error ? e.message : "获取数据失败" });
|
|
}
|
|
} finally {
|
|
if (!signal.aborted) {
|
|
set({ loading: false, allSourcesLoaded: true });
|
|
}
|
|
}
|
|
},
|
|
|
|
setDetail: (detail) => {
|
|
set({ detail });
|
|
},
|
|
|
|
abort: () => {
|
|
get().controller?.abort();
|
|
},
|
|
}));
|
|
|
|
export const sourcesSelector = (state: DetailState) => state.sources;
|
|
export default useDetailStore;
|
|
export const episodesSelectorBySource = (source: string) => (state: DetailState) =>
|
|
state.searchResults.find((r) => r.source === source)?.episodes || []; |