워드프레스에서 비디오 시청 진행률 추적하기: JetEngine + Elementor 완벽 가이드

온라인 교육 사이트나 강의 플랫폼을 운영하고 계신가요? 사용자가 비디오를 얼마나 시청했는지 추적하고 시각적으로 표시하고 싶으신가요?

이 글에서는 JetEngineElementor를 활용해 실시간으로 비디오 시청 진행률을 추적하고 진행률 바로 표시하는 방법을 단계별로 알려드리겠습니다.

🎯 완성 후 기대 효과

  • ✅ 10분 영상을 5분 시청 시 정확히 50% 진행률 표시
  • ✅ 사용자별 개별 진행률 저장 및 복원
  • ✅ 페이지를 새로고침해도 진행률 유지
  • ✅ 이전 시청 지점부터 이어보기 기능
  • 실시간 진행률 바 업데이트

📋 사전 준비사항

시작하기 전에 다음 플러그인들이 설치되어 있어야 합니다:

  • Elementor Pro (또는 무료 버전)
  • JetEngine
  • JetElements
  • ✅ 기본적인 PHP/JavaScript 지식

🏗️ 1단계: 데이터베이스 준비

먼저 비디오 진행률 데이터를 저장할 구조를 만들어야 합니다.

JetEngine에서 커스텀 포스트 타입 생성

  1. WordPress 관리자JetEnginePost Types 이동
  2. Add New 클릭
  3. 다음과 같이 설정:
    • Post Type Name: video_course
    • Slug: video-course
    • Menu Icon: dashicons-video-alt3

메타 필드 추가

JetEngineMeta BoxesAdd New에서 다음 필드들을 추가합니다:

필드명타입설명
video_urlURL비디오 파일 URL
video_titleText비디오 제목

💡 :

  • 메타박스는 위에서 생성한 video_course 포스트 타입에만 적용되도록 설정하세요.
  • 비디오 길이는 자동으로 가져옵니다! JavaScript의 video.duration 속성을 사용해 자동 추출하므로 별도 필드가 불필요합니다.

💻 2단계: 백엔드 PHP 코드 구현

테마의 functions.php 파일에 다음 코드를 추가합니다:

<?php
/**
 * 비디오 진행률 추적 시스템
 * functions.php에 추가
 */

// 스크립트 로딩 및 AJAX 설정
function enqueue_video_progress_scripts() {
    wp_enqueue_script('jquery');
    wp_enqueue_script(
        'video-progress-tracker', 
        get_template_directory_uri() . '/js/video-progress.js', 
        array('jquery'), 
        '1.0.0', 
        true
    );
    
    // JavaScript에 필요한 데이터 전달
    wp_localize_script('video-progress-tracker', 'ajax_object', array(
        'ajax_url' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('video_progress_nonce')
    ));
    
    wp_localize_script('video-progress-tracker', 'wp_data', array(
        'post_id' => get_the_ID(),
        'user_id' => get_current_user_id()
    ));
}
add_action('wp_enqueue_scripts', 'enqueue_video_progress_scripts');

// 데이터베이스 테이블 생성
function create_video_progress_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'video_progress';
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        post_id bigint(20) NOT NULL,
        current_time float NOT NULL,
        duration float NOT NULL,
        percentage int(3) NOT NULL,
        last_updated datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY user_post (user_id, post_id)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}
add_action('after_switch_theme', 'create_video_progress_table');

// 진행률 저장 AJAX 핸들러
function save_video_progress_handler() {
    if (!wp_verify_nonce($_POST['nonce'], 'video_progress_nonce')) {
        wp_die('보안 검증 실패');
    }
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'video_progress';
    
    $user_id = intval($_POST['user_id']);
    $post_id = intval($_POST['post_id']);
    $current_time = floatval($_POST['current_time']);
    $duration = floatval($_POST['duration']);
    $percentage = intval($_POST['percentage']);
    
    // 기존 레코드 확인 후 업데이트 또는 생성
    $existing = $wpdb->get_row($wpdb->prepare(
        "SELECT id FROM $table_name WHERE user_id = %d AND post_id = %d",
        $user_id, $post_id
    ));
    
    if ($existing) {
        $result = $wpdb->update(
            $table_name,
            array(
                'current_time' => $current_time,
                'duration' => $duration,
                'percentage' => $percentage
            ),
            array('user_id' => $user_id, 'post_id' => $post_id),
            array('%f', '%f', '%d'),
            array('%d', '%d')
        );
    } else {
        $result = $wpdb->insert(
            $table_name,
            array(
                'user_id' => $user_id,
                'post_id' => $post_id,
                'current_time' => $current_time,
                'duration' => $duration,
                'percentage' => $percentage
            ),
            array('%d', '%d', '%f', '%f', '%d')
        );
    }
    
    if ($result !== false) {
        wp_send_json_success(array('percentage' => $percentage));
    } else {
        wp_send_json_error('저장 실패');
    }
}
add_action('wp_ajax_save_video_progress', 'save_video_progress_handler');
add_action('wp_ajax_nopriv_save_video_progress', 'save_video_progress_handler');

// 진행률 불러오기 AJAX 핸들러
function get_video_progress_handler() {
    if (!wp_verify_nonce($_POST['nonce'], 'video_progress_nonce')) {
        wp_die('보안 검증 실패');
    }
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'video_progress';
    
    $user_id = intval($_POST['user_id']);
    $post_id = intval($_POST['post_id']);
    
    $progress = $wpdb->get_row($wpdb->prepare(
        "SELECT current_time, duration, percentage FROM $table_name WHERE user_id = %d AND post_id = %d",
        $user_id, $post_id
    ));
    
    if ($progress) {
        wp_send_json_success(array(
            'current_time' => $progress->current_time,
            'duration' => $progress->duration,
            'percentage' => $progress->percentage
        ));
    } else {
        wp_send_json_success(array('percentage' => 0));
    }
}
add_action('wp_ajax_get_video_progress', 'get_video_progress_handler');
add_action('wp_ajax_nopriv_get_video_progress', 'get_video_progress_handler');

// 비디오 메타데이터 자동 저장 AJAX 핸들러 (선택사항)
function save_video_metadata_handler() {
    if (!wp_verify_nonce($_POST['nonce'], 'video_progress_nonce')) {
        wp_die('보안 검증 실패');
    }
    
    $post_id = intval($_POST['post_id']);
    $duration = floatval($_POST['duration']);
    $width = intval($_POST['width']);
    $height = intval($_POST['height']);
    
    // 메타 필드에 자동으로 저장
    if ($duration > 0) {
        update_post_meta($post_id, 'video_duration', $duration);
    }
    if ($width > 0 && $height > 0) {
        update_post_meta($post_id, 'video_width', $width);
        update_post_meta($post_id, 'video_height', $height);
        update_post_meta($post_id, 'video_resolution', $width . 'x' . $height);
    }
    
    wp_send_json_success('메타데이터 저장 완료');
}
add_action('wp_ajax_save_video_metadata', 'save_video_metadata_handler');
add_action('wp_ajax_nopriv_save_video_metadata', 'save_video_metadata_handler');

// JetEngine 동적 태그 등록
function get_user_video_progress($post_id = null) {
    if (!$post_id) $post_id = get_the_ID();
    $user_id = get_current_user_id();
    
    if (!$user_id) return 0;
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'video_progress';
    
    $progress = $wpdb->get_var($wpdb->prepare(
        "SELECT percentage FROM $table_name WHERE user_id = %d AND post_id = %d",
        $user_id, $post_id
    ));
    
    return $progress ? $progress : 0;
}

// JetEngine 매크로 등록
if (function_exists('jet_engine')) {
    function register_video_progress_dynamic_tag() {
        jet_engine()->listings->macros->register_callback('video_progress', 'get_user_video_progress');
    }
    add_action('init', 'register_video_progress_dynamic_tag');
}
?>

⚡ 3단계: JavaScript 프론트엔드 구현

테마 폴더에 js/video-progress.js 파일을 생성하고 다음 코드를 추가합니다:

jQuery(document).ready(function($) {
    const video = document.querySelector('video');
    const progressBar = document.querySelector('.jet-progress-bar__fill');
    const progressText = document.querySelector('.jet-progress-bar__percent');
    
    const postId = wp_data.post_id;
    const userId = wp_data.user_id;
    
    if (!video) return;
    
    // 페이지 로드 시 저장된 진행률 불러오기
    loadSavedProgress();
    
    // 비디오 메타데이터 로드 시 자동으로 길이 정보 저장
    video.addEventListener('loadedmetadata', function() {
        const duration = video.duration;
        console.log('비디오 총 길이:', duration, '초');
        
        // 비디오 메타데이터를 서버에 자동 저장 (선택사항)
        saveVideoMetadata(postId, duration, video.videoWidth, video.videoHeight);
    });
    
    // 비디오 재생 시간 업데이트 시마다 실행
    video.addEventListener('timeupdate', function() {
        const currentTime = video.currentTime;
        const duration = video.duration;
        
        if (duration > 0) {
            const progress = Math.round((currentTime / duration) * 100);
            updateProgressBar(progress);
            
            // 5초마다 서버에 저장
            if (Math.floor(currentTime) % 5 === 0) {
                saveProgress(postId, userId, currentTime, duration, progress);
            }
        }
    });
    
    // 비디오 종료 시
    video.addEventListener('ended', function() {
        updateProgressBar(100);
        saveProgress(postId, userId, video.duration, video.duration, 100);
    });
    
    // 일시정지 시 현재 진행률 저장
    video.addEventListener('pause', function() {
        const currentTime = video.currentTime;
        const duration = video.duration;
        const progress = Math.round((currentTime / duration) * 100);
        saveProgress(postId, userId, currentTime, duration, progress);
    });
    
    // 진행률 바 업데이트 함수
    function updateProgressBar(percentage) {
        if (progressBar) {
            progressBar.style.width = percentage + '%';
        }
        if (progressText) {
            progressText.textContent = percentage + '%';
        }
    }
    
    // 서버에 진행률 저장
    function saveProgress(postId, userId, currentTime, duration, percentage) {
        $.ajax({
            url: ajax_object.ajax_url,
            type: 'POST',
            data: {
                action: 'save_video_progress',
                post_id: postId,
                user_id: userId,
                current_time: currentTime,
                duration: duration,
                percentage: percentage,
                nonce: ajax_object.nonce
            },
            success: function(response) {
                if (response.success) {
                    console.log('진행률 저장:', percentage + '%');
                }
            }
        });
    }
    
    // 저장된 진행률 불러오기
    function loadSavedProgress() {
        $.ajax({
            url: ajax_object.ajax_url,
            type: 'POST',
            data: {
                action: 'get_video_progress',
                post_id: postId,
                user_id: userId,
                nonce: ajax_object.nonce
            },
            success: function(response) {
                if (response.success && response.data.percentage) {
                    const savedPercentage = response.data.percentage;
                    const savedTime = response.data.current_time;
                    
                    updateProgressBar(savedPercentage);
                    
                    // 이어보기 옵션 제공
                    if (savedPercentage > 5 && savedPercentage < 100) {
                        if (confirm('이전 시청 지점(' + savedPercentage + '%)부터 계속 보시겠습니까?')) {
                            video.currentTime = savedTime;
                        }
                    }
                }
            }
        });
    }
    
    // 비디오 메타데이터 자동 저장 (선택사항)
    function saveVideoMetadata(postId, duration, width, height) {
        $.ajax({
            url: ajax_object.ajax_url,
            type: 'POST',
            data: {
                action: 'save_video_metadata',
                post_id: postId,
                duration: duration,
                width: width,
                height: height,
                nonce: ajax_object.nonce
            },
            success: function(response) {
                if (response.success) {
                    console.log('비디오 메타데이터 자동 저장 완료');
                }
            }
        });
    }
});

🎨 4단계: Elementor에서 화면 구성

비디오 위젯 추가

  1. Elementor 편집기에서 페이지 편집
  2. Video 위젯 드래그 앤 드롭
  3. SourceDynamic 선택
  4. Dynamic TagsMeta Datavideo_url 선택

진행률 바 위젯 추가

  1. JetElements에서 Progress Bar 위젯 검색
  2. 위젯을 비디오 아래에 배치
  3. 다음과 같이 설정:
    • Title: “시청 진행률”
    • Percentage: {% video_progress %} 입력
    • Type: “Bar Inside” 선택
    • Animation: “On” (선택사항)

![Elementor 설정 화면 스크린샷 자리]

고급 설정

Advanced 탭에서:

  • CSS Classes: video-progress-bar 추가
  • Custom CSS (선택사항):
.video-progress-bar .jet-progress-bar__fill {
    background: linear-gradient(90deg, #4CAF50, #8BC34A);
    transition: width 0.3s ease;
}

.video-progress-bar .jet-progress-bar__percent {
    font-weight: bold;
    color: #333;
}

🎯 5단계: JetEngine 리스팅 설정 (선택사항)

리스팅 그리드 생성

  1. JetEngineListingsAdd New
  2. Listing Source: 앞서 생성한 video_course 선택
  3. Type: “Template” 선택

동적 콘텐츠 추가

리스팅 템플릿에서:

  • 동적 필드 위젯으로 video_title 표시
  • Video 위젯으로 video_url 표시
  • Progress Bar 위젯으로 {% video_progress %} 표시

🧪 6단계: 테스트 및 검증

기능 테스트 체크리스트

  • [ ] 비디오 재생 시 진행률 바 실시간 업데이트
  • [ ] 페이지 새로고침 후 진행률 유지
  • [ ] 일시정지 시 진행률 저장
  • [ ] 비디오 완료 시 100% 표시
  • [ ] 이어보기 팝업 정상 작동
  • [ ] 사용자별 개별 진행률 저장

브라우저 콘솔 확인

F12Console에서 다음 메시지들이 정상적으로 출력되는지 확인:

  • 진행률 저장: XX%
  • 비디오 재생 시작
  • 비디오 시청 완료!

🚨 문제 해결 가이드

진행률이 저장되지 않는 경우

  1. AJAX 오류 확인 // 콘솔에서 네트워크 탭 확인 // 500 에러: PHP 문법 오류 // 403 에러: nonce 검증 실패
  2. 데이터베이스 테이블 확인 -- phpMyAdmin에서 실행 SELECT * FROM wp_video_progress;
  3. 파일 경로 확인
    • video-progress.js 파일이 올바른 위치에 있는지
    • functions.php에 문법 오류가 없는지

진행률 바가 업데이트되지 않는 경우

  1. CSS 선택자 확인
    • 브라우저 개발자 도구로 실제 클래스명 확인
    • JetElements 버전에 따라 클래스명이 다를 수 있음
  2. 동적 태그 확인
    • {% video_progress %} 가 올바르게 입력되었는지
    • JetEngine 매크로가 정상 등록되었는지

🚀 고급 기능 추가하기

비디오 메타데이터 자동 추출

// 비디오가 로드되면 자동으로 메타데이터 추출
video.addEventListener('loadedmetadata', function() {
    const metadata = {
        duration: video.duration,
        width: video.videoWidth,
        height: video.videoHeight,
        aspect_ratio: (video.videoWidth / video.videoHeight).toFixed(2)
    };
    
    console.log('추출된 메타데이터:', metadata);
    // 자동으로 포스트 메타에 저장
    saveVideoMetadata(postId, metadata);
});

이점:

  • ✅ 관리자가 수동으로 입력할 필요 없음
  • ✅ 정확한 비디오 길이 자동 계산
  • ✅ 해상도 정보로 반응형 플레이어 최적화

시청 완료 시 자동 진행

// 시청 완료 시 다음 강의로 자동 이동
function redirect_to_next_video() {
    // 현재 포스트의 다음 포스트 가져오기
    $next_post = get_next_post();
    if ($next_post) {
        return get_permalink($next_post->ID);
    }
    return false;
}

시청 시간 통계

// 사용자별 총 시청 시간 계산
function get_user_total_watch_time($user_id) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'video_progress';
    
    $total_time = $wpdb->get_var($wpdb->prepare(
        "SELECT SUM(current_time) FROM $table_name WHERE user_id = %d",
        $user_id
    ));
    
    return $total_time ? $total_time : 0;
}

진행률별 뱃지 시스템

// 진행률에 따른 뱃지 표시
function get_progress_badge($percentage) {
    if ($percentage >= 100) return '🏆 완주';
    if ($percentage >= 75) return '🥈 거의 다';
    if ($percentage >= 50) return '🥉 반 완주';
    if ($percentage >= 25) return '📚 진행중';
    return '▶️ 시작';
}

📊 성능 최적화 팁

데이터베이스 최적화

  1. 인덱스 추가 ALTER TABLE wp_video_progress ADD INDEX idx_user_post (user_id, post_id);
  2. 오래된 데이터 정리 // 30일 이상 된 데이터 삭제 function cleanup_old_progress_data() { global $wpdb; $table_name = $wpdb->prefix . 'video_progress'; $wpdb->query( "DELETE FROM $table_name WHERE last_updated < DATE_SUB(NOW(), INTERVAL 30 DAY)" ); }

프론트엔드 최적화

  1. 저장 빈도 조절: 5초마다 저장 (너무 자주 저장하면 서버 부하)
  2. 로컬 캐싱: 진행률을 로컬스토리지에도 저장
  3. 압축 및 최소화: JavaScript 파일 압축

📱 모바일 최적화

반응형 진행률 바

@media (max-width: 768px) {
    .video-progress-bar {
        margin: 10px 0;
    }
    
    .jet-progress-bar__percent {
        font-size: 14px;
    }
}

터치 이벤트 고려

// 모바일에서 터치 이벤트도 고려
video.addEventListener('touchend', function() {
    // 터치 종료 시에도 진행률 저장
    saveCurrentProgress();
});

🔒 보안 고려사항

데이터 검증 강화

// 입력값 검증 추가
function validate_progress_data($current_time, $duration, $percentage) {
    // 음수 값 체크
    if ($current_time < 0 || $duration < 0 || $percentage < 0) {
        return false;
    }
    
    // 논리적 범위 체크
    if ($percentage > 100 || $current_time > $duration) {
        return false;
    }
    
    return true;
}

SQL 인젝션 방지

  • 모든 데이터베이스 쿼리에 $wpdb->prepare() 사용
  • 사용자 입력값 sanitize 처리

🎉 마무리

축하합니다! 이제 워드프레스 사이트에서 완전히 작동하는 비디오 진행률 추적 시스템을 구축했습니다.

구현된 기능들

실시간 진행률 추적: 비디오 시청과 동시에 진행률 업데이트
개인별 데이터 저장: 사용자마다 독립적인 진행률 관리
이어보기 기능: 이전 시청 지점부터 계속 시청 가능
시각적 진행률 바: JetElements로 아름다운 진행률 표시
자동 저장: 일시정지나 페이지 이탈 시 자동 저장

다음 단계

이제 이 시스템을 기반으로 다음과 같은 기능들을 추가해보세요:

  • 🏆 수료증 발급 시스템
  • 📊 학습 진도 대시보드
  • 🎮 게이미피케이션 요소
  • 📱 모바일 앱 연동

💬 마지막으로

이 가이드가 도움이 되셨나요? 댓글로 여러분의 구현 경험을 공유해주세요!

막히는 부분이 있으시면 언제든 질문해주시고, 더 나은 기능 아이디어가 있으시면 함께 발전시켜 나가요! 🚀


태그: #워드프레스 #JetEngine #Elementor #비디오추적 #온라인교육 #LMS #웹개발

공유하기: 이 포스팅이 유용했다면 SNS에 공유해주세요! 💪

공유하기

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다