반응형
# 조건
- 동영상의 첫 프레임을 썸네일로 사용하기
- 썸네일 위에 마우스 올라가면 자동 재생
- 스피커 on / off 버튼
# PHP
<div class="intro-y g-col-12 g-col-md-6 g-col-lg-3 zoom-in">
<div class="box" style="overflow:hidden;">
<div class="d-flex align-items-start">
<div class="w-full d-flex flex-column flex-lg-row align-items-center">
<div class="position-relative" style="margin:0 auto; width:100%;">
<div class="video-thumbnail" data-video="<?php echo htmlspecialchars($web_path); ?>" style="width:100%; height:300px; background-color:#ccc; display:flex; justify-content:center; align-items:center;">
Loading...
</div>
<?php if ($row['approveyn'] == 'Y') { ?>
<button class="btn btn-primary py-1 px-3 position-absolute top-0 end-0 m-2 fw-bold" style="font-size: 0.9rem; opacity: 0.9; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">승인</button>
<?php } ?>
</div>
</div>
</div>
<div class="text-center text-lg-start p-5">
<div class="truncate fw-medium text-nowrap"><?php echo $row['movieseq'] . " - " . htmlspecialchars($row['title']); ?></div>
<div class="d-flex align-items-center mt-1">
<div class="text-gray-600 fs-xs"><?php echo htmlspecialchars($row['nickname']); ?> [<?php echo ($row['code2'] == 'H') ? '개인회원' : '업체회원'; ?>]</div>
<div class="fs-xs text-gray-500 ms-auto"><?php echo date('Y-m-d H:i', strtotime($row['regdate'])); ?></div>
</div>
<div class="truncate text-gray-600 fs-xs text-nowrap mt-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-map w-3 h-3 me-0">
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"></polygon>
<line x1="8" y1="2" x2="8" y2="18"></line>
<line x1="16" y1="6" x2="16" y2="22"></line>
</svg> <?php echo htmlspecialchars($row['category']); ?>
</div>
</div>
<div class="text-center text-lg-end p-5 border-top border-gray-200 dark-border-dark-5">
<button class="btn btn-danger-soft py-1 px-2" onclick="scrFn_delete(<?php echo $row['movieseq']; ?>)">삭제</button>
<button class="btn btn-dark-soft py-1 px-2" onclick="scrFn_Edit('<?php echo ($row['movieseq'])?>');return false;">수정</button>
</div>
</div>
</div>
<?php endforeach; ?>
# 스크립트
// 동영상 썸네일 이미지 만들기 #2
function generateThumbnail(videoElement, canvasElement, container) {
return new Promise((resolve, reject) => {
let attempts = 0;
const maxAttempts = 5; // 최대 시도 횟수
function attemptThumbnail() {
try {
canvasElement.width = videoElement.videoWidth;
canvasElement.height = videoElement.videoHeight;
canvasElement
.getContext("2d")
.drawImage(
videoElement,
0,
0,
canvasElement.width,
canvasElement.height
);
// 이미지 데이터 확인
const imageData = canvasElement
.getContext("2d")
.getImageData(0, 0, canvasElement.width, canvasElement.height);
const pixels = imageData.data;
// 투명하지 않은 픽셀이 있는지 확인
const hasValidPixels = pixels.some(
(pixel, index) => index % 4 === 3 && pixel !== 0
);
if (hasValidPixels) {
resolve(canvasElement.toDataURL());
} else {
throw new Error("Invalid image data");
}
} catch (error) {
attempts++;
if (attempts < maxAttempts) {
// 다음 프레임으로 이동
videoElement.currentTime += 0.1;
videoElement.addEventListener("seeked", attemptThumbnail, {
once: true,
});
} else {
reject(new Error("Failed to generate thumbnail after max attempts"));
}
}
}
videoElement.addEventListener("loadedmetadata", function () {
videoElement.currentTime = 0; // 시작 시점부터 시도
videoElement.addEventListener("seeked", attemptThumbnail, { once: true });
});
videoElement.addEventListener("error", function () {
reject(new Error("Failed to load video"));
});
});
}
// 동영상 썸네일 이미지 만들기 #1
function loadVideoThumbnails() {
const thumbnailContainers = document.querySelectorAll(".video-thumbnail");
thumbnailContainers.forEach((container) => {
const videoSrc = container.dataset.video;
const video = document.createElement("video");
const canvas = document.createElement("canvas");
video.crossOrigin = "anonymous";
video.src = videoSrc;
video.style.display = "none";
video.muted = true; // 초기에는 음소거 상태로 설정
video.loop = true;
container.innerHTML = "Loading thumbnail...";
generateThumbnail(video, canvas, container)
.then((thumbnailUrl) => {
container.style.backgroundImage = `url(${thumbnailUrl})`;
container.style.backgroundSize = "cover";
container.style.backgroundPosition = "center";
container.innerHTML = "";
// 비디오 요소를 컨테이너에 추가
video.style.width = "100%";
video.style.height = "100%";
video.style.objectFit = "cover";
container.appendChild(video);
// 음소거 토글 버튼 추가
const muteToggle = document.createElement("button");
muteToggle.innerHTML = "🔇"; // 초기 음소거 상태
muteToggle.style.position = "absolute";
muteToggle.style.bottom = "10px";
muteToggle.style.right = "10px";
muteToggle.style.zIndex = "10";
muteToggle.style.background = "rgba(0,0,0,0.5)";
muteToggle.style.color = "white";
muteToggle.style.border = "none";
muteToggle.style.borderRadius = "50%";
muteToggle.style.width = "30px";
muteToggle.style.height = "30px";
muteToggle.style.cursor = "pointer";
container.appendChild(muteToggle);
muteToggle.addEventListener("click", (e) => {
e.stopPropagation(); // 이벤트 버블링 방지
video.muted = !video.muted;
muteToggle.innerHTML = video.muted ? "🔇" : "🔊";
});
// 마우스 이벤트 리스너 추가
container.addEventListener("mouseenter", () => {
video.style.display = "block";
video.play().catch((e) => console.error("Error playing video:", e));
});
container.addEventListener("mouseleave", () => {
video.pause();
video.currentTime = 0;
video.style.display = "none";
});
})
.catch((error) => {
console.error("Error generating thumbnail:", error);
container.style.backgroundImage =
'url("/path/to/default-thumbnail.jpg")';
container.style.backgroundSize = "cover";
container.style.backgroundPosition = "center";
container.innerHTML = "";
});
});
}
setTimeout(function() {
loadVideoThumbnails();
}, 5000); // 시간차가 없으면 읽어오지 못하는 경우가 생김
반응형
'자바스크립트' 카테고리의 다른 글
NODEJS, DENO, BUN 설치 (1) | 2024.11.20 |
---|---|
자바스크립트 날짜 비교하기 (1) | 2024.02.27 |
* javascript datetime / 자바스크립트 년월일 구하기 (0) | 2022.04.04 |
자바스크립트 동적 로딩 (1) | 2022.01.06 |
module.exports 스타일 (0) | 2020.11.16 |