emby分类获取
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
|
||||
import CapsuleSwitch from '@/components/CapsuleSwitch';
|
||||
@@ -25,8 +25,15 @@ interface Video {
|
||||
mediaType: 'movie' | 'tv';
|
||||
}
|
||||
|
||||
interface EmbyView {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export default function PrivateLibraryPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [source, setSource] = useState<LibrarySource>('openlist');
|
||||
const [videos, setVideos] = useState<Video[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -34,19 +41,138 @@ export default function PrivateLibraryPage() {
|
||||
const [error, setError] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [embyViews, setEmbyViews] = useState<EmbyView[]>([]);
|
||||
const [selectedView, setSelectedView] = useState<string>('all');
|
||||
const [loadingViews, setLoadingViews] = useState(false);
|
||||
const pageSize = 20;
|
||||
const observerTarget = useRef<HTMLDivElement>(null);
|
||||
const isFetchingRef = useRef(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isDraggingRef = useRef(false);
|
||||
const startXRef = useRef(0);
|
||||
const scrollLeftRef = useRef(0);
|
||||
const isInitializedRef = useRef(false);
|
||||
|
||||
// 切换源时重置所有状态
|
||||
// 从URL初始化状态
|
||||
useEffect(() => {
|
||||
const urlSource = searchParams.get('source') as LibrarySource;
|
||||
const urlView = searchParams.get('view');
|
||||
|
||||
if (urlSource && (urlSource === 'openlist' || urlSource === 'emby')) {
|
||||
setSource(urlSource);
|
||||
}
|
||||
|
||||
if (urlView) {
|
||||
setSelectedView(urlView);
|
||||
}
|
||||
|
||||
isInitializedRef.current = true;
|
||||
}, [searchParams]);
|
||||
|
||||
// 更新URL参数
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) return;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set('source', source);
|
||||
if (source === 'emby' && selectedView !== 'all') {
|
||||
params.set('view', selectedView);
|
||||
}
|
||||
router.replace(`/private-library?${params.toString()}`, { scroll: false });
|
||||
}, [source, selectedView, router]);
|
||||
|
||||
// 切换源时重置所有状态(但不在初始化时执行)
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) return;
|
||||
|
||||
setPage(1);
|
||||
setVideos([]);
|
||||
setHasMore(true);
|
||||
setError('');
|
||||
setSelectedView('all');
|
||||
isFetchingRef.current = false;
|
||||
}, [source]);
|
||||
|
||||
// 切换分类时重置状态(但不在初始化时执行)
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) return;
|
||||
|
||||
setPage(1);
|
||||
setVideos([]);
|
||||
setHasMore(true);
|
||||
setError('');
|
||||
isFetchingRef.current = false;
|
||||
}, [selectedView]);
|
||||
|
||||
// 获取 Emby 媒体库列表
|
||||
useEffect(() => {
|
||||
if (source !== 'emby') return;
|
||||
|
||||
const fetchEmbyViews = async () => {
|
||||
setLoadingViews(true);
|
||||
try {
|
||||
const response = await fetch('/api/emby/views');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
console.error('获取 Emby 媒体库列表失败:', data.error);
|
||||
setEmbyViews([]);
|
||||
} else {
|
||||
setEmbyViews(data.views || []);
|
||||
|
||||
// 分类加载完成后,检查URL中是否有view参数
|
||||
const urlView = searchParams.get('view');
|
||||
if (urlView && data.views && data.views.length > 0) {
|
||||
// 检查该view是否存在于分类列表中
|
||||
const viewExists = data.views.some((v: EmbyView) => v.id === urlView);
|
||||
if (viewExists) {
|
||||
setSelectedView(urlView);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取 Emby 媒体库列表失败:', err);
|
||||
setEmbyViews([]);
|
||||
} finally {
|
||||
setLoadingViews(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchEmbyViews();
|
||||
}, [source]);
|
||||
|
||||
// 鼠标拖动滚动
|
||||
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!scrollContainerRef.current) return;
|
||||
isDraggingRef.current = true;
|
||||
startXRef.current = e.pageX - scrollContainerRef.current.offsetLeft;
|
||||
scrollLeftRef.current = scrollContainerRef.current.scrollLeft;
|
||||
scrollContainerRef.current.style.cursor = 'grabbing';
|
||||
scrollContainerRef.current.style.userSelect = 'none';
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (!scrollContainerRef.current) return;
|
||||
isDraggingRef.current = false;
|
||||
scrollContainerRef.current.style.cursor = 'grab';
|
||||
scrollContainerRef.current.style.userSelect = 'auto';
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!scrollContainerRef.current) return;
|
||||
isDraggingRef.current = false;
|
||||
scrollContainerRef.current.style.cursor = 'grab';
|
||||
scrollContainerRef.current.style.userSelect = 'auto';
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!isDraggingRef.current || !scrollContainerRef.current) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - scrollContainerRef.current.offsetLeft;
|
||||
const walk = (x - startXRef.current) * 2; // 滚动速度倍数
|
||||
scrollContainerRef.current.scrollLeft = scrollLeftRef.current - walk;
|
||||
};
|
||||
|
||||
// 加载数据的函数
|
||||
useEffect(() => {
|
||||
const fetchVideos = async () => {
|
||||
@@ -69,7 +195,7 @@ export default function PrivateLibraryPage() {
|
||||
|
||||
const endpoint = source === 'openlist'
|
||||
? `/api/openlist/list?page=${page}&pageSize=${pageSize}`
|
||||
: `/api/emby/list?page=${page}&pageSize=${pageSize}`;
|
||||
: `/api/emby/list?page=${page}&pageSize=${pageSize}${selectedView !== 'all' ? `&parentId=${selectedView}` : ''}`;
|
||||
|
||||
const response = await fetch(endpoint);
|
||||
|
||||
@@ -116,7 +242,7 @@ export default function PrivateLibraryPage() {
|
||||
};
|
||||
|
||||
fetchVideos();
|
||||
}, [source, page]);
|
||||
}, [source, page, selectedView]);
|
||||
|
||||
const handleVideoClick = (video: Video) => {
|
||||
// 跳转到播放页面
|
||||
@@ -175,6 +301,54 @@ export default function PrivateLibraryPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Emby 分类选择器 */}
|
||||
{source === 'emby' && (
|
||||
<div className='mb-6'>
|
||||
{loadingViews ? (
|
||||
<div className='flex justify-center'>
|
||||
<div className='w-5 h-5 border-2 border-blue-600 border-t-transparent rounded-full animate-spin' />
|
||||
</div>
|
||||
) : embyViews.length > 0 ? (
|
||||
<div className='relative'>
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className='overflow-x-auto scrollbar-hide cursor-grab active:cursor-grabbing'
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<div className='flex gap-2 px-4 min-w-min'>
|
||||
<button
|
||||
onClick={() => setSelectedView('all')}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap flex-shrink-0 ${
|
||||
selectedView === 'all'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
全部
|
||||
</button>
|
||||
{embyViews.map((view) => (
|
||||
<button
|
||||
key={view.id}
|
||||
onClick={() => setSelectedView(view.id)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap flex-shrink-0 ${
|
||||
selectedView === view.id
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{view.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className='bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6'>
|
||||
<p className='text-red-800 dark:text-red-200'>{error}</p>
|
||||
|
||||
Reference in New Issue
Block a user