修改资源URL模式,不再存储https://domain 前缀
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useRef } from "react";
|
||||
import { Pause, Play } from "lucide-react";
|
||||
import type { AwemeData, ImageData, Neighbors, VideoData } from "./types";
|
||||
import type { AwemeData, ImageData, Neighbors, VideoData } from "./types.ts";
|
||||
import { BackgroundCanvas } from "./components/BackgroundCanvas";
|
||||
import { CommentPanel } from "./components/CommentPanel";
|
||||
import { ImageCarousel } from "./components/ImageCarousel";
|
||||
@@ -41,7 +41,7 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
const backgroundCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
// 图文轮播状态
|
||||
const images = isVideo ? [] : (data as ImageData).images;
|
||||
const images = isVideo ? [] : (data as ImageData).images;
|
||||
const imageCarouselState = useImageCarousel({
|
||||
images,
|
||||
isPlaying: playerState.isPlaying,
|
||||
@@ -80,17 +80,17 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
return;
|
||||
}
|
||||
if (!images?.length) return;
|
||||
|
||||
|
||||
// 计算每张图片的时长
|
||||
const durations = images.map(img => img.duration ?? SEGMENT_MS);
|
||||
const totalDuration = durations.reduce((sum, d) => sum + d, 0);
|
||||
const targetTime = ratio * totalDuration;
|
||||
|
||||
|
||||
// 找到目标时间对应的图片索引和进度
|
||||
let accumulatedTime = 0;
|
||||
let targetIdx = 0;
|
||||
let remainder = 0;
|
||||
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
if (accumulatedTime + durations[i] > targetTime) {
|
||||
targetIdx = i;
|
||||
@@ -107,7 +107,7 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
imageCarouselState.idxRef.current = targetIdx;
|
||||
imageCarouselState.setIdx(targetIdx);
|
||||
imageCarouselState.segStartRef.current = performance.now() - remainder * durations[targetIdx];
|
||||
|
||||
|
||||
// 重新计算总进度
|
||||
let totalProgress = 0;
|
||||
for (let i = 0; i < targetIdx; i++) {
|
||||
@@ -115,7 +115,7 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
}
|
||||
totalProgress += remainder;
|
||||
playerState.setProgress(totalProgress / images.length);
|
||||
|
||||
|
||||
// 虚拟滚动不需要实际滚动 DOM
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
if (isVideo) {
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
if (v.paused) await v.play().catch(() => {});
|
||||
if (v.paused) await v.play().catch(() => { });
|
||||
else v.pause();
|
||||
return;
|
||||
}
|
||||
@@ -131,8 +131,8 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
if (!playerState.isPlaying) {
|
||||
playerState.setIsPlaying(true);
|
||||
try {
|
||||
await el?.play().catch(() => {});
|
||||
} catch {}
|
||||
await el?.play().catch(() => { });
|
||||
} catch { }
|
||||
} else {
|
||||
playerState.setIsPlaying(false);
|
||||
el?.pause();
|
||||
@@ -142,12 +142,12 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
if (document.body.requestFullscreen) {
|
||||
document.body.requestFullscreen().catch(() => {});
|
||||
document.body.requestFullscreen().catch(() => { });
|
||||
return;
|
||||
}
|
||||
const vRef = videoRef.current;
|
||||
if (vRef && vRef.requestFullscreen) {
|
||||
vRef.requestFullscreen().catch(() => {});
|
||||
vRef.requestFullscreen().catch(() => { });
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
@@ -156,7 +156,7 @@ export default function AwemeDetailClient({ data, neighbors }: AwemeDetailClient
|
||||
vRef.webkitEnterFullscreen();
|
||||
}
|
||||
} else {
|
||||
document.exitFullscreen().catch(() => {});
|
||||
document.exitFullscreen().catch(() => { });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ThumbsUp, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import type { Comment, User } from "../types";
|
||||
import type { Comment, User } from "../types.ts";
|
||||
import { formatRelativeTime, formatAbsoluteUTC } from "../utils";
|
||||
import { CommentText } from "./CommentText";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { X } from "lucide-react";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import type { Comment, User } from "../types";
|
||||
import type { Comment, User } from "../types.ts";
|
||||
import { CommentList } from "./CommentList";
|
||||
|
||||
interface CommentPanelProps {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||
import type { ImageData } from "../types";
|
||||
import type { ImageData } from "../types.ts";
|
||||
|
||||
interface ImageCarouselProps {
|
||||
images: ImageData["images"];
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
VolumeX,
|
||||
} from "lucide-react";
|
||||
import type { RefObject } from "react";
|
||||
import type { LoopMode, ObjectFit, User } from "../types";
|
||||
import type { LoopMode, ObjectFit, User } from "../types.ts";
|
||||
import { formatRelativeTime, formatTime, formatAbsoluteUTC } from "../utils";
|
||||
import { ProgressBar } from "./ProgressBar";
|
||||
import { SegmentedProgressBar } from "./SegmentedProgressBar";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChevronDown, ChevronUp, MessageSquareText, ThumbsUp } from "lucide-react";
|
||||
import type { Neighbors } from "../types";
|
||||
import type { Neighbors } from "../types.ts";
|
||||
|
||||
interface NavigationButtonsProps {
|
||||
neighbors: Neighbors;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { forwardRef } from "react";
|
||||
import type { ObjectFit } from "../types";
|
||||
import type { ObjectFit } from "../types.ts";
|
||||
|
||||
interface VideoPlayerProps {
|
||||
videoUrl: string;
|
||||
|
||||
@@ -89,7 +89,7 @@ export function useBackgroundCanvas({
|
||||
ctx.drawImage(sourceElement, offsetX, offsetY, drawWidth, drawHeight);
|
||||
};
|
||||
|
||||
const intervalId = setInterval(drawMediaToCanvas, 20);
|
||||
const intervalId = setInterval(drawMediaToCanvas, 34);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { ImageData, LoopMode, Neighbors } from "../types";
|
||||
import type { ImageData, LoopMode, Neighbors } from "../types.ts";
|
||||
|
||||
interface UseImageCarouselProps {
|
||||
images: ImageData["images"];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { Neighbors } from "../types";
|
||||
import type { Neighbors } from "../types.ts";
|
||||
|
||||
interface UseNavigationProps {
|
||||
neighbors: Neighbors;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { LoopMode, ObjectFit } from "../types";
|
||||
import type { LoopMode, ObjectFit } from "../types.ts";
|
||||
import { getNumberFromStorage, getStringFromStorage, saveToStorage } from "../utils";
|
||||
|
||||
export function usePlayerState() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { LoopMode, Neighbors } from "../types";
|
||||
import type { LoopMode, Neighbors } from "../types.ts";
|
||||
|
||||
interface UseVideoPlayerProps {
|
||||
awemeId: string;
|
||||
|
||||
@@ -2,14 +2,8 @@ import { prisma } from "@/lib/prisma";
|
||||
import BackButton from "@/app/components/BackButton";
|
||||
import AwemeDetailClient from "./Client";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
function ms(v?: number | null) {
|
||||
if (!v) return "";
|
||||
const s = Math.round(v / 1000);
|
||||
const m = Math.floor(s / 60);
|
||||
const r = s % 60;
|
||||
return `${m}:${r.toString().padStart(2, "0")}`;
|
||||
}
|
||||
import { getFileUrl } from "@/lib/minio";
|
||||
import { AwemeData } from "./types";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ awemeId: string }> }): Promise<Metadata> {
|
||||
const id = (await params).awemeId;
|
||||
@@ -59,40 +53,45 @@ export default async function AwemeDetail({ params }: { params: Promise<{ awemeI
|
||||
if (!video && !post) return <main className="p-8">找不到该作品</main>;
|
||||
|
||||
const isVideo = !!video;
|
||||
|
||||
|
||||
// 获取评论总数
|
||||
const commentsCount = await prisma.comment.count({
|
||||
where: isVideo ? { videoId: id } : { imagePostId: id },
|
||||
});
|
||||
|
||||
const data = isVideo
|
||||
? {
|
||||
type: "video" as const,
|
||||
aweme_id: video!.aweme_id,
|
||||
desc: video!.desc,
|
||||
created_at: video!.created_at,
|
||||
duration_ms: video!.duration_ms,
|
||||
video_url: video!.video_url,
|
||||
width: video!.width ?? null,
|
||||
height: video!.height ?? null,
|
||||
author: { nickname: video!.author.nickname, avatar_url: video!.author.avatar_url },
|
||||
commentsCount,
|
||||
likesCount: Number(video!.digg_count),
|
||||
const aweme = isVideo ? video : post;
|
||||
|
||||
const data: AwemeData = {
|
||||
aweme_id: aweme!.aweme_id,
|
||||
desc: aweme!.desc,
|
||||
created_at: aweme!.created_at,
|
||||
likesCount: Number(aweme!.digg_count),
|
||||
commentsCount,
|
||||
author: { nickname: aweme!.author.nickname, avatar_url: getFileUrl(aweme!.author.avatar_url || 'default-avatar.png') },
|
||||
...(() => {
|
||||
if (isVideo) {
|
||||
const aweme = video!
|
||||
return {
|
||||
type: "video" as const,
|
||||
duration_ms: aweme!.duration_ms,
|
||||
video_url: aweme!.video_url,
|
||||
width: aweme!.width ?? null,
|
||||
height: aweme!.height ?? null,
|
||||
}
|
||||
} else {
|
||||
const aweme = post!
|
||||
return {
|
||||
type: "image" as const,
|
||||
images: aweme!.images.map(img => ({ ...img, url: getFileUrl(img.url) })),
|
||||
music_url: aweme!.music_url,
|
||||
};
|
||||
}
|
||||
: {
|
||||
type: "image" as const,
|
||||
aweme_id: post!.aweme_id,
|
||||
desc: post!.desc,
|
||||
created_at: post!.created_at,
|
||||
images: post!.images,
|
||||
music_url: post!.music_url,
|
||||
author: { nickname: post!.author.nickname, avatar_url: post!.author.avatar_url },
|
||||
commentsCount,
|
||||
likesCount: Number(post!.digg_count),
|
||||
};
|
||||
})()
|
||||
}
|
||||
|
||||
|
||||
// Compute prev/next neighbors by created_at across videos and image posts
|
||||
const currentCreatedAt = (isVideo ? video!.created_at : post!.created_at) as unknown as Date;
|
||||
const currentCreatedAt = (isVideo ? video!.created_at : post!.created_at);
|
||||
const [newerVideo, newerPost, olderVideo, olderPost] = await Promise.all([
|
||||
prisma.video.findFirst({ where: { created_at: { gt: currentCreatedAt } }, orderBy: { created_at: "asc" }, select: { aweme_id: true, created_at: true } }),
|
||||
prisma.imagePost.findFirst({ where: { created_at: { gt: currentCreatedAt } }, orderBy: { created_at: "asc" }, select: { aweme_id: true, created_at: true } }),
|
||||
@@ -101,16 +100,16 @@ export default async function AwemeDetail({ params }: { params: Promise<{ awemeI
|
||||
]);
|
||||
const pickPrev = (() => {
|
||||
const cands: { aweme_id: string; created_at: Date }[] = [];
|
||||
if (newerVideo) cands.push({ aweme_id: newerVideo.aweme_id, created_at: newerVideo.created_at as unknown as Date });
|
||||
if (newerPost) cands.push({ aweme_id: newerPost.aweme_id, created_at: newerPost.created_at as unknown as Date });
|
||||
if (newerVideo) cands.push({ aweme_id: newerVideo.aweme_id, created_at: newerVideo.created_at });
|
||||
if (newerPost) cands.push({ aweme_id: newerPost.aweme_id, created_at: newerPost.created_at });
|
||||
if (cands.length === 0) return null;
|
||||
cands.sort((a, b) => +a.created_at - +b.created_at);
|
||||
return { aweme_id: cands[0].aweme_id };
|
||||
})();
|
||||
const pickNext = (() => {
|
||||
const cands: { aweme_id: string; created_at: Date }[] = [];
|
||||
if (olderVideo) cands.push({ aweme_id: olderVideo.aweme_id, created_at: olderVideo.created_at as unknown as Date });
|
||||
if (olderPost) cands.push({ aweme_id: olderPost.aweme_id, created_at: olderPost.created_at as unknown as Date });
|
||||
if (olderVideo) cands.push({ aweme_id: olderVideo.aweme_id, created_at: olderVideo.created_at });
|
||||
if (olderPost) cands.push({ aweme_id: olderPost.aweme_id, created_at: olderPost.created_at });
|
||||
if (cands.length === 0) return null;
|
||||
cands.sort((a, b) => +b.created_at - +a.created_at);
|
||||
return { aweme_id: cands[0].aweme_id };
|
||||
@@ -126,7 +125,7 @@ export default async function AwemeDetail({ params }: { params: Promise<{ awemeI
|
||||
className="inline-flex items-center justify-center w-9 h-9 rounded-full bg-white/15 text-white border border-white/20 backdrop-blur hover:bg-white/25"
|
||||
/>
|
||||
</div>
|
||||
<AwemeDetailClient data={data as any} neighbors={neighbors as any} />
|
||||
<AwemeDetailClient data={data} neighbors={neighbors} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ export type VideoData = {
|
||||
aweme_id: string;
|
||||
desc: string;
|
||||
created_at: string | Date;
|
||||
duration_ms?: number | null;
|
||||
duration_ms: number | null;
|
||||
video_url: string;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
author: User;
|
||||
commentsCount: number;
|
||||
likesCount: number;
|
||||
@@ -28,8 +28,8 @@ export type ImageData = {
|
||||
aweme_id: string;
|
||||
desc: string;
|
||||
created_at: string | Date;
|
||||
images: { id: string; url: string; width?: number; height?: number; animated?: string; duration?: number }[];
|
||||
music_url?: string | null;
|
||||
images: { id: string; url: string; width: number | null; height: number | null; animated: string | null; duration: number | null }[];
|
||||
music_url: string | null;
|
||||
author: User;
|
||||
commentsCount: number;
|
||||
likesCount: number;
|
||||
|
||||
Reference in New Issue
Block a user