移动云盘在线播放
This commit is contained in:
@@ -14,6 +14,7 @@ import type { DanmakuComment,DanmakuSelection } from '@/lib/danmaku/types';
|
||||
import { generateStorageKey, getCachedPlayRecordsSnapshot } from '@/lib/db.client';
|
||||
import { isEpisodeHiddenByFilter } from '@/lib/episode-filter';
|
||||
import { loadAllLocalEpisodeProgressRecords } from '@/lib/episode-progress';
|
||||
import { isNetdiskSource } from '@/lib/netdisk/source';
|
||||
import { EpisodeFilterConfig,SearchResult } from '@/lib/types';
|
||||
import { getVideoResolutionFromM3u8 } from '@/lib/utils';
|
||||
|
||||
@@ -1001,7 +1002,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
||||
{/* 源名称和集数信息 - 垂直居中 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className={`text-xs px-2 py-1 border rounded text-gray-700 dark:text-gray-300 ${
|
||||
source.source === 'xiaoya' ? 'border-blue-500' : source.source === 'quark-temp' ? 'border-purple-500' : source.source === 'openlist' || source.source === 'emby' || source.source?.startsWith('emby_')
|
||||
source.source === 'xiaoya' ? 'border-blue-500' : isNetdiskSource(source.source) ? 'border-purple-500' : source.source === 'openlist' || source.source === 'emby' || source.source?.startsWith('emby_')
|
||||
? 'border-yellow-500'
|
||||
: 'border-gray-500/60'
|
||||
}`}>
|
||||
|
||||
@@ -5,9 +5,10 @@ import { AlertCircle, Copy, ExternalLink, Loader2, RefreshCw } from 'lucide-reac
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import Toast, { ToastProps } from '@/components/Toast';
|
||||
import { PansouLink, PansouSearchResult } from '@/lib/pansou.client';
|
||||
|
||||
import Toast, { ToastProps } from '@/components/Toast';
|
||||
|
||||
interface PansouSearchProps {
|
||||
keyword: string;
|
||||
triggerSearch?: boolean; // 触发搜索的标志
|
||||
@@ -108,7 +109,7 @@ export default function PansouSearch({
|
||||
}
|
||||
|
||||
searchPansou();
|
||||
}, [triggerSearch, searchPansou]); // 依赖 triggerSearch 和 searchPansou
|
||||
}, [triggerSearch]); // 只在触发标志变化时搜索,避免 keyword 变化自动搜索
|
||||
|
||||
const handleCopy = async (text: string, url: string) => {
|
||||
try {
|
||||
@@ -159,10 +160,10 @@ export default function PansouSearch({
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuarkInstantPlay = async (link: PansouLink) => {
|
||||
const handleNetdiskInstantPlay = async (cloudType: string, link: PansouLink) => {
|
||||
try {
|
||||
setPlayingUrl(link.url);
|
||||
const response = await fetch('/api/netdisk/quark/instant-play', {
|
||||
const response = await fetch(cloudType === 'mobile' ? '/api/netdisk/mobile/instant-play' : '/api/netdisk/quark/instant-play', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -180,7 +181,7 @@ export default function PansouSearch({
|
||||
}
|
||||
|
||||
router.push(
|
||||
`/play?source=quark-temp&id=${encodeURIComponent(data.id)}&title=${encodeURIComponent(data.title || keyword)}`
|
||||
`/play?source=${encodeURIComponent(data.source || (cloudType === 'mobile' ? 'netdisk-mobile' : 'netdisk-quark'))}&id=${encodeURIComponent(data.id)}&title=${encodeURIComponent(data.title || keyword)}`
|
||||
);
|
||||
} catch (err: any) {
|
||||
setToast({
|
||||
@@ -338,24 +339,26 @@ export default function PansouSearch({
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className='flex items-center gap-1 flex-shrink-0'>
|
||||
{cloudType === 'quark' && (
|
||||
{(cloudType === 'quark' || cloudType === 'mobile') && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => handleQuarkInstantPlay(link)}
|
||||
onClick={() => handleNetdiskInstantPlay(cloudType, link)}
|
||||
disabled={playingUrl === link.url}
|
||||
className='px-2 py-1 rounded-md bg-green-600 hover:bg-green-700 text-white text-xs transition-colors disabled:opacity-60'
|
||||
title='立即播放'
|
||||
>
|
||||
{playingUrl === link.url ? '处理中...' : '立即播放'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleQuarkTransfer(link)}
|
||||
disabled={transferingUrl === link.url}
|
||||
className='px-2 py-1 rounded-md bg-purple-600 hover:bg-purple-700 text-white text-xs transition-colors disabled:opacity-60'
|
||||
title='转存到配置目录'
|
||||
>
|
||||
{transferingUrl === link.url ? '转存中...' : '转存'}
|
||||
</button>
|
||||
{cloudType === 'quark' && (
|
||||
<button
|
||||
onClick={() => handleQuarkTransfer(link)}
|
||||
disabled={transferingUrl === link.url}
|
||||
className='px-2 py-1 rounded-md bg-purple-600 hover:bg-purple-700 text-white text-xs transition-colors disabled:opacity-60'
|
||||
title='转存到配置目录'
|
||||
>
|
||||
{transferingUrl === link.url ? '转存中...' : '转存'}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any,react-hooks/exhaustive-deps,@typescript-eslint/no-empty-function */
|
||||
|
||||
import { ExternalLink, Heart, Info, Link, PlayCircleIcon, Radio, Sparkles, Trash2 } from 'lucide-react';
|
||||
import { Cloud, ExternalLink, Heart, Info, Link, PlayCircleIcon, Radio, Sparkles, Trash2 } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, {
|
||||
@@ -21,11 +21,12 @@ import {
|
||||
saveFavorite,
|
||||
subscribeToDataUpdates,
|
||||
} from '@/lib/db.client';
|
||||
import { isNetdiskSource } from '@/lib/netdisk/source';
|
||||
import {
|
||||
processImageUrl,
|
||||
base58Decode,
|
||||
tryApplyDoubanImageFallback,
|
||||
getDoubanImageFallbackUrl,
|
||||
processImageUrl,
|
||||
tryApplyDoubanImageFallback,
|
||||
} from '@/lib/utils';
|
||||
import { useLongPress } from '@/hooks/useLongPress';
|
||||
|
||||
@@ -112,7 +113,25 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
const router = useRouter();
|
||||
const actualTitle = title;
|
||||
const actualPoster = poster;
|
||||
const processedPoster = useMemo(() => processImageUrl(actualPoster), [actualPoster]);
|
||||
const netdiskPosterPlaceholder = useMemo(() => {
|
||||
return `data:image/svg+xml;utf8,${encodeURIComponent(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600">
|
||||
<rect width="400" height="600" fill="#f3f4f6"/>
|
||||
<g fill="none" stroke="#9ca3af" stroke-width="16" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M118 332c-30.9 0-56-25.1-56-56 0-28.5 21.3-52 48.9-55.4C120.6 184.7 154.8 160 195 160c51.1 0 92.9 39.2 97.1 89.2 27.3 4.2 47.9 27.7 47.9 56.8 0 32-26 58-58 58H118z"/>
|
||||
</g>
|
||||
</svg>
|
||||
`)}`;
|
||||
}, []);
|
||||
const processedPoster = useMemo(
|
||||
() =>
|
||||
actualPoster
|
||||
? processImageUrl(actualPoster)
|
||||
: isNetdiskSource(source)
|
||||
? netdiskPosterPlaceholder
|
||||
: '',
|
||||
[actualPoster, source, netdiskPosterPlaceholder]
|
||||
);
|
||||
const [favorited, setFavorited] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showMobileActions, setShowMobileActions] = useState(false);
|
||||
@@ -747,6 +766,10 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
<div className='absolute inset-0 flex items-center justify-center bg-gray-200/80 dark:bg-gray-700/80'>
|
||||
<Link className='w-8 h-8 text-blue-500' />
|
||||
</div>
|
||||
) : (isNetdiskSource(actualSource) && !actualPoster && displayPoster === netdiskPosterPlaceholder) ? (
|
||||
<div className='absolute inset-0 flex flex-col items-center justify-center bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400'>
|
||||
<Cloud className='w-10 h-10 opacity-80' />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
src={displayPoster}
|
||||
@@ -1061,7 +1084,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
>
|
||||
<span
|
||||
className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/60 ${
|
||||
actualSource === 'xiaoya' ? 'border-blue-500' : actualSource === 'quark-temp' ? 'border-purple-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : origin === 'live' ? 'border-red-500' : 'border-white/60'
|
||||
actualSource === 'xiaoya' ? 'border-blue-500' : isNetdiskSource(actualSource) ? 'border-purple-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : origin === 'live' ? 'border-red-500' : 'border-white/60'
|
||||
}`}
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
@@ -1385,7 +1408,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
|
||||
{config.showSourceName && source_name && !cmsData && (
|
||||
<span
|
||||
className={`inline-block border rounded px-1 py-0.5 text-[8px] text-white/90 bg-black/30 backdrop-blur-sm ${
|
||||
actualSource === 'xiaoya' ? 'border-blue-500' : actualSource === 'quark-temp' ? 'border-purple-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : 'border-white/60'
|
||||
actualSource === 'xiaoya' ? 'border-blue-500' : isNetdiskSource(actualSource) ? 'border-purple-500' : actualSource === 'openlist' || actualSource === 'emby' || actualSource?.startsWith('emby_') ? 'border-yellow-500' : 'border-white/60'
|
||||
}`}
|
||||
style={{
|
||||
WebkitUserSelect: 'none',
|
||||
|
||||
Reference in New Issue
Block a user