diff --git a/frontend/src/components/video/VideoCard.tsx b/frontend/src/components/video/VideoCard.tsx index 8de85e7..7160b93 100644 --- a/frontend/src/components/video/VideoCard.tsx +++ b/frontend/src/components/video/VideoCard.tsx @@ -1,8 +1,7 @@ import clsx from "clsx"; import { formatTime, stringToDate, dateToTimeAgo } from "../../utils/utils.ts"; import { Link } from "react-router-dom"; -import { useEffect, useRef, useState } from "react"; -import { isThumbnailAvailable } from "../../utils/api/client.ts"; +import React, { useEffect, useRef, useState } from "react"; import { MoreVertical, Pencil, Trash2 } from "lucide-react"; import {config} from "../../config.ts"; @@ -19,12 +18,12 @@ type VideoCardProps = { itemLabel?: string, } -const fallbackThumbnail = "../../../public/default_thumbnail.png"; +const fallbackThumbnail = "public/default_thumbnail.png"; const API_URL = config.apiUrl; const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className, mediaApiPath = '/api/v1/clips', playerPath = '/video', itemLabel = 'clip' }: VideoCardProps) => { const [timeAgo, setTimeAgo] = useState(dateToTimeAgo(stringToDate(createdAt))); - const [thumbnailAvailable, setThumbnailAvailable] = useState(true); + const [imgFailed, setImgFailed] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const menuRef = useRef(null); @@ -37,12 +36,6 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className return () => clearInterval(interval); }, [createdAt]); - useEffect(() => { - isThumbnailAvailable(thumbnailUrl) - .then(setThumbnailAvailable) - .catch(() => setThumbnailAvailable(false)); - }, [thumbnailUrl]); - useEffect(() => { if (!menuOpen) return; const handleClickOutside = (e: MouseEvent) => { @@ -66,7 +59,8 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
setImgFailed(true)} alt="Video Thumbnail" className="w-full aspect-video object-cover group-hover:scale-105 transition-transform duration-200" /> diff --git a/src/main/java/com/ddf/vodsystem/controllers/ClipController.java b/src/main/java/com/ddf/vodsystem/controllers/ClipController.java index 88de8cb..517baa0 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/ClipController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/ClipController.java @@ -6,14 +6,12 @@ import com.ddf.vodsystem.controllers.dto.ClipUpdateRequest; import com.ddf.vodsystem.entities.Clip; import com.ddf.vodsystem.services.ClipService; import org.springframework.core.io.Resource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.MediaTypeFactory; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.List; +import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/v1/clips") @@ -96,7 +94,7 @@ public class ClipController { } @GetMapping("/{id}/thumbnail") - public ResponseEntity downloadThumbnail(@PathVariable Long id) { + public ResponseEntity downloadThumbnail(@PathVariable Long id) throws IOException { Resource resource = clipService.downloadThumbnail(id); if (resource == null || !resource.exists()) { @@ -104,6 +102,8 @@ public class ClipController { } return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(2, TimeUnit.DAYS).cachePublic()) + .lastModified(resource.lastModified()) .header(HttpHeaders.CONTENT_DISPOSITION, String.format(FILENAME_HEADER, resource.getFilename())) .contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)) .body(resource); diff --git a/src/main/java/com/ddf/vodsystem/controllers/VodController.java b/src/main/java/com/ddf/vodsystem/controllers/VodController.java index 60981fa..28846c5 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/VodController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/VodController.java @@ -6,14 +6,12 @@ import com.ddf.vodsystem.dto.APIResponse; import com.ddf.vodsystem.entities.Vod; import com.ddf.vodsystem.services.VodService; import org.springframework.core.io.Resource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.MediaTypeFactory; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.List; +import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/v1/vods") @@ -73,7 +71,7 @@ public class VodController { } @GetMapping("/{id}/thumbnail") - public ResponseEntity downloadThumbnail(@PathVariable Long id) { + public ResponseEntity downloadThumbnail(@PathVariable Long id) throws IOException { Resource resource = vodService.downloadThumbnail(id); if (resource == null || !resource.exists()) { @@ -81,6 +79,8 @@ public class VodController { } return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(2, TimeUnit.DAYS).cachePublic()) + .lastModified(resource.lastModified()) .header(HttpHeaders.CONTENT_DISPOSITION, String.format(FILENAME_HEADER, resource.getFilename())) .contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)) .body(resource);