Add feature to automatically play the next video when one finishes
Implement autoplay functionality for videos. When a video finishes, it will automatically advance to the next video in the sequence after a set delay, with a toggle for enabling/disabling the feature. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 2cd2c0bc-434c-4bc9-ad3f-b99d3897a0d1 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2cd2c0bc-434c-4bc9-ad3f-b99d3897a0d1/gjpMN2A
This commit is contained in:
parent
ac1f9aa36f
commit
de11d2a297
@ -31,7 +31,7 @@ const formatDate = (date: Date | string): string => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Share2, X, Edit3, Menu, Search, ChevronLeft, ChevronRight } from "lucide-react";
|
import { Share2, X, Edit3, Menu, Search, ChevronLeft, ChevronRight, Play, Pause } from "lucide-react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { apiRequest } from "@/lib/queryClient";
|
import { apiRequest } from "@/lib/queryClient";
|
||||||
@ -61,6 +61,8 @@ export default function VideoPage() {
|
|||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
|
const [autoplayEnabled, setAutoplayEnabled] = useState(true);
|
||||||
|
const [autoplayTimer, setAutoplayTimer] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// Fetch current video
|
// Fetch current video
|
||||||
const { data: currentVideo, isLoading: videoLoading, error: videoError } = useQuery<Video>({
|
const { data: currentVideo, isLoading: videoLoading, error: videoError } = useQuery<Video>({
|
||||||
@ -138,6 +140,12 @@ export default function VideoPage() {
|
|||||||
const currentIndex = getCurrentVideoIndex();
|
const currentIndex = getCurrentVideoIndex();
|
||||||
if (currentIndex === -1) return;
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
// Clear any existing autoplay timer
|
||||||
|
if (autoplayTimer) {
|
||||||
|
clearTimeout(autoplayTimer);
|
||||||
|
setAutoplayTimer(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Show direction feedback on dots
|
// Show direction feedback on dots
|
||||||
setActiveDot(direction === 'next' ? 'right' : 'left');
|
setActiveDot(direction === 'next' ? 'right' : 'left');
|
||||||
|
|
||||||
@ -160,6 +168,29 @@ export default function VideoPage() {
|
|||||||
setLocation(`/video/${shortId}`);
|
setLocation(`/video/${shortId}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Auto-advance to next video after a certain duration
|
||||||
|
const startAutoplayTimer = () => {
|
||||||
|
if (!autoplayEnabled || allVideos.length <= 1) return;
|
||||||
|
|
||||||
|
// Clear existing timer
|
||||||
|
if (autoplayTimer) {
|
||||||
|
clearTimeout(autoplayTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate video duration or use default (most videos are 3-5 minutes)
|
||||||
|
const estimatedDuration = currentVideo?.duration || 240000; // 4 minutes default
|
||||||
|
const autoplayDelay = Math.max(estimatedDuration * 0.9, 180000); // 90% of video or min 3 minutes
|
||||||
|
|
||||||
|
console.log(`🎬 Autoplay timer set for ${Math.round(autoplayDelay / 1000)}s`);
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
console.log('🚀 Auto-advancing to next video...');
|
||||||
|
navigateToVideo('next');
|
||||||
|
}, autoplayDelay);
|
||||||
|
|
||||||
|
setAutoplayTimer(timer);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Update page meta tags for social sharing
|
// Update page meta tags for social sharing
|
||||||
@ -268,12 +299,44 @@ export default function VideoPage() {
|
|||||||
// Use short ID for view tracking
|
// Use short ID for view tracking
|
||||||
const shortId = currentVideo.id.replace(/-/g, '').substring(0, 8);
|
const shortId = currentVideo.id.replace(/-/g, '').substring(0, 8);
|
||||||
await apiRequest("POST", `/api/videos/${shortId}/view`);
|
await apiRequest("POST", `/api/videos/${shortId}/view`);
|
||||||
|
|
||||||
|
// Start autoplay timer when video starts playing
|
||||||
|
if (autoplayEnabled) {
|
||||||
|
startAutoplayTimer();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to track video view:", error);
|
console.error("Failed to track video view:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initialize autoplay when video changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentVideo && autoplayEnabled && allVideos.length > 1) {
|
||||||
|
// Delay the autoplay timer start to ensure iframe is loaded
|
||||||
|
setTimeout(() => {
|
||||||
|
startAutoplayTimer();
|
||||||
|
}, 2000); // 2 second delay for iframe loading
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Cleanup timer when component unmounts or video changes
|
||||||
|
if (autoplayTimer) {
|
||||||
|
clearTimeout(autoplayTimer);
|
||||||
|
setAutoplayTimer(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [currentVideo?.id, autoplayEnabled]);
|
||||||
|
|
||||||
|
// Cleanup timer on component unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (autoplayTimer) {
|
||||||
|
clearTimeout(autoplayTimer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const getShareUrl = () => {
|
const getShareUrl = () => {
|
||||||
if (!currentVideo?.id) return window.location.origin;
|
if (!currentVideo?.id) return window.location.origin;
|
||||||
// Use custom domain if set, otherwise current domain
|
// Use custom domain if set, otherwise current domain
|
||||||
@ -669,17 +732,45 @@ export default function VideoPage() {
|
|||||||
{currentVideo.title}
|
{currentVideo.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="flex gap-2">
|
||||||
|
{/* Autoplay toggle button */}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
onClick={() => {
|
||||||
className="text-white hover:bg-gray-700"
|
setAutoplayEnabled(!autoplayEnabled);
|
||||||
|
if (!autoplayEnabled && autoplayTimer) {
|
||||||
|
clearTimeout(autoplayTimer);
|
||||||
|
setAutoplayTimer(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`text-white hover:bg-gray-700 ${autoplayEnabled ? 'bg-purple-600/30' : ''}`}
|
||||||
|
title={autoplayEnabled ? 'Autoplay ON - will advance to next video' : 'Autoplay OFF'}
|
||||||
>
|
>
|
||||||
<Share2 className="w-4 h-4 mr-2" />
|
{autoplayEnabled ? (
|
||||||
Share
|
<>
|
||||||
|
<Play className="w-4 h-4 mr-2" />
|
||||||
|
Auto
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Pause className="w-4 h-4 mr-2" />
|
||||||
|
Manual
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowShareMenu(!showShareMenu)}
|
||||||
|
className="text-white hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<Share2 className="w-4 h-4 mr-2" />
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
|
||||||
{showShareMenu && (
|
{showShareMenu && (
|
||||||
<div className="absolute right-0 top-full mt-2 bg-gray-800 rounded-lg shadow-lg py-2 z-50 min-w-[200px]">
|
<div className="absolute right-0 top-full mt-2 bg-gray-800 rounded-lg shadow-lg py-2 z-50 min-w-[200px]">
|
||||||
<FacebookShareButton url={getShareUrl()}>
|
<FacebookShareButton url={getShareUrl()}>
|
||||||
@ -709,6 +800,7 @@ export default function VideoPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 text-sm text-bunny-muted mb-4">
|
<div className="flex flex-wrap gap-4 text-sm text-bunny-muted mb-4">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user