mirror of
https://github.com/hansputera/tiktok-dl.git
synced 2026-04-05 19:51:57 +02:00
feat: added queryparams support on download endpoint, and minor changes at download page
Signed-off-by: Hanif Dwy Putra S <hanifdwyputrasembiring@gmail.com>
This commit is contained in:
@@ -3,9 +3,8 @@ import { rotateProvider } from "@/services/rotator";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getProvider } from "tiktok-dl-core";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const handleRequest = async <T>(json: T) => {
|
||||
try {
|
||||
const json = await request.json();
|
||||
const safeData = await downloadValidator.safeParseAsync(json);
|
||||
|
||||
if (safeData.error || !safeData.success) {
|
||||
@@ -50,8 +49,18 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
return Response.json({
|
||||
message: 'Currently we moved to POST method only.',
|
||||
});
|
||||
export async function GET(request: NextRequest) {
|
||||
const allParams = Object.fromEntries(request.nextUrl.searchParams.entries());
|
||||
return handleRequest(allParams);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const json = await request.json();
|
||||
return handleRequest(json);
|
||||
} catch (e) {
|
||||
return Response.json({
|
||||
message: (e as Error).message,
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { getTikTokURL } from "@/lib/utils";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { Download, ExternalLink, Loader2 } from "lucide-react";
|
||||
import z from "zod";
|
||||
|
||||
@@ -28,25 +28,111 @@ const VideoPlayer = ({
|
||||
isActive?: boolean;
|
||||
}) => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const [isVideoReady, setIsVideoReady] = useState(false);
|
||||
const [lastSyncTime, setLastSyncTime] = useState(0);
|
||||
const syncTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const playPromiseRef = useRef<Promise<void> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const syncVideoTime = useCallback((targetTime: number) => {
|
||||
if (syncTimeoutRef.current) {
|
||||
clearTimeout(syncTimeoutRef.current);
|
||||
}
|
||||
|
||||
syncTimeoutRef.current = setTimeout(() => {
|
||||
const video = videoRef.current;
|
||||
if (video && isActive && isVideoReady) {
|
||||
const timeDiff = Math.abs(video.currentTime - targetTime);
|
||||
if (timeDiff > 0.5) {
|
||||
video.currentTime = targetTime;
|
||||
setLastSyncTime(targetTime);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}, [isActive, isVideoReady]);
|
||||
|
||||
const handlePlayPause = useCallback(async (shouldPlay: boolean) => {
|
||||
const video = videoRef.current;
|
||||
if (video && isActive) {
|
||||
if (!video || !isActive || !isVideoReady) return;
|
||||
|
||||
try {
|
||||
if (playPromiseRef.current) {
|
||||
await playPromiseRef.current.catch(() => {});
|
||||
}
|
||||
|
||||
if (shouldPlay && video.paused) {
|
||||
playPromiseRef.current = video.play();
|
||||
await playPromiseRef.current;
|
||||
} else if (!shouldPlay && !video.paused) {
|
||||
video.pause();
|
||||
playPromiseRef.current = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Playback error:', error);
|
||||
onPlayStateChange(false);
|
||||
}
|
||||
}, [isActive, isVideoReady]);
|
||||
|
||||
const handleLoadedMetadata = useCallback(() => {
|
||||
setIsVideoReady(true);
|
||||
const video = videoRef.current;
|
||||
if (video && currentTime > 0) {
|
||||
video.currentTime = currentTime;
|
||||
if (isPlaying) {
|
||||
video.play().catch(() => {
|
||||
onPlayStateChange(false);
|
||||
});
|
||||
}
|
||||
}, [currentTime]);
|
||||
|
||||
const handleTimeUpdate = useCallback(() => {
|
||||
const video = videoRef.current;
|
||||
if (video && isActive && isVideoReady) {
|
||||
const currentVideoTime = video.currentTime;
|
||||
if (Math.abs(currentVideoTime - lastSyncTime) > 0.1) {
|
||||
onTimeUpdate(currentVideoTime);
|
||||
setLastSyncTime(currentVideoTime);
|
||||
}
|
||||
}
|
||||
}, [currentTime, isActive, isPlaying]);
|
||||
}, [isActive, isVideoReady, lastSyncTime]);
|
||||
|
||||
const handlePlay = useCallback(() => {
|
||||
if (isActive) {
|
||||
onPlayStateChange(true);
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
const handlePause = useCallback(() => {
|
||||
if (isActive) {
|
||||
onPlayStateChange(false);
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
const handleEnded = useCallback(() => {
|
||||
onPlayStateChange(false);
|
||||
onTimeUpdate(0);
|
||||
}, []);
|
||||
|
||||
const handleError = useCallback((e: React.SyntheticEvent<HTMLVideoElement, Event>) => {
|
||||
console.error('Video error:', e);
|
||||
onPlayStateChange(false);
|
||||
setIsVideoReady(false);
|
||||
}, []);
|
||||
|
||||
const handleWaiting = useCallback(() => {
|
||||
// Buffering video...
|
||||
}, []);
|
||||
|
||||
const handleCanPlay = useCallback(() => {
|
||||
setIsVideoReady(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (video && isActive && Math.abs(video.currentTime - currentTime) > 1) {
|
||||
video.currentTime = currentTime;
|
||||
if (isVideoReady && isActive) {
|
||||
handlePlayPause(isPlaying);
|
||||
}
|
||||
}, [currentTime, isActive]);
|
||||
}, [isPlaying, isVideoReady, isActive]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVideoReady && isActive) {
|
||||
syncVideoTime(currentTime);
|
||||
}
|
||||
}, [currentTime, isVideoReady, isActive]);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
@@ -55,34 +141,37 @@ const VideoPlayer = ({
|
||||
if (!video.paused) {
|
||||
video.pause();
|
||||
}
|
||||
} else {
|
||||
if (isPlaying && video.paused) {
|
||||
} else if (isVideoReady && isPlaying) {
|
||||
if (Math.abs(video.currentTime - currentTime) > 0.5) {
|
||||
video.currentTime = currentTime;
|
||||
video.play().catch(() => onPlayStateChange(false));
|
||||
} else if (!isPlaying && !video.paused) {
|
||||
video.pause();
|
||||
}
|
||||
if (video.paused) {
|
||||
handlePlayPause(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isPlaying, isActive, currentTime]);
|
||||
}, [isActive, isVideoReady]);
|
||||
|
||||
const handleTimeUpdate = () => {
|
||||
if (videoRef.current && isActive) {
|
||||
onTimeUpdate(videoRef.current.currentTime);
|
||||
useEffect(() => {
|
||||
setIsVideoReady(false);
|
||||
setLastSyncTime(0);
|
||||
if (playPromiseRef.current) {
|
||||
playPromiseRef.current.catch(() => {});
|
||||
playPromiseRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [videoData.video?.urls[0]]);
|
||||
|
||||
const handlePlay = () => {
|
||||
if (isActive) {
|
||||
onPlayStateChange(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePause = () => {
|
||||
if (isActive) {
|
||||
onPlayStateChange(false);
|
||||
}
|
||||
};
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (syncTimeoutRef.current) {
|
||||
clearTimeout(syncTimeoutRef.current);
|
||||
}
|
||||
if (playPromiseRef.current) {
|
||||
playPromiseRef.current.catch(() => {});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg border">
|
||||
@@ -95,17 +184,30 @@ const VideoPlayer = ({
|
||||
src={videoData.video?.urls[0]}
|
||||
poster={videoData.video?.thumb}
|
||||
preload="metadata"
|
||||
playsInline
|
||||
onLoadedMetadata={handleLoadedMetadata}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onPlay={handlePlay}
|
||||
onPause={handlePause}
|
||||
onLoadedMetadata={handleTimeUpdate}
|
||||
onEnded={handleEnded}
|
||||
onError={handleError}
|
||||
onWaiting={handleWaiting}
|
||||
onCanPlay={handleCanPlay}
|
||||
>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
|
||||
{/* Loading indicator */}
|
||||
{!isVideoReady && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 rounded-lg">
|
||||
<Loader2 className="w-8 h-8 text-white animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 text-sm text-gray-600">
|
||||
<p><strong>Provider:</strong> {videoData.provider}</p>
|
||||
<p><strong>Available formats:</strong> {videoData.video?.urls.length}</p>
|
||||
<p><strong>Status:</strong> {isVideoReady ? 'Ready' : 'Loading...'}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -218,6 +320,7 @@ export default function Home() {
|
||||
}
|
||||
|
||||
setVideoData(response);
|
||||
// Reset video state untuk video baru
|
||||
setVideoCurrentTime(0);
|
||||
setVideoIsPlaying(false);
|
||||
} catch (error) {
|
||||
@@ -247,13 +350,13 @@ export default function Home() {
|
||||
form.reset();
|
||||
};
|
||||
|
||||
const handleTimeUpdate = (time: number) => {
|
||||
const handleTimeUpdate = useCallback((time: number) => {
|
||||
setVideoCurrentTime(time);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handlePlayStateChange = (playing: boolean) => {
|
||||
const handlePlayStateChange = useCallback((playing: boolean) => {
|
||||
setVideoIsPlaying(playing);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
|
||||
Reference in New Issue
Block a user