반응형

 

 

 

#    조건

        -   동영상의 첫 프레임을 썸네일로 사용하기

        -   썸네일 위에 마우스 올라가면 자동 재생

        -   스피커 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); // 시간차가 없으면 읽어오지 못하는 경우가 생김
반응형