文章目录
-
- 随着在线教育、知识分享和内容创作行业的蓬勃发展,视频内容已成为网站不可或缺的组成部分。然而,大多数网站的视频播放功能相对基础,缺乏个性化控制选项。本文将详细介绍如何通过WordPress代码二次开发,为网站视频播放器添加速度控制与章节标记功能,从而提升用户体验和内容互动性。我们将从需求分析、技术选型、代码实现到测试部署,全面解析这一实用工具的构建过程。
-
- 大多数WordPress网站使用默认的视频播放器或第三方插件,这些方案通常存在以下局限性: 播放速度固定,无法根据用户需求调整 缺乏章节标记功能,长视频导航困难 用户无法自定义播放体验 互动性差,用户参与度低
- 我们的开发目标是为WordPress网站视频播放器添加以下核心功能: 多级播放速度控制:提供0.5x到3.0x之间的多档速度选择 智能章节标记系统:允许内容创作者为视频添加章节标记 用户友好的交互界面:直观的控制面板,不影响观看体验 数据持久化:记住用户的播放偏好设置 响应式设计:适配各种设备和屏幕尺寸
- WordPress作为开源CMS系统,提供了丰富的API和钩子机制,使我们能够: 通过JavaScript操作HTML5视频元素 使用PHP扩展视频元数据处理 利用WordPress数据库存储章节信息 通过AJAX实现前后端数据交互
-
- 在开始开发前,需要准备以下环境: 本地开发环境:XAMPP/MAMP或Local by Flywheel WordPress安装:最新版本的WordPress(5.8+) 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:用于调试JavaScript和CSS 版本控制系统:Git(可选但推荐)
- 我们将创建一个独立的WordPress插件来管理所有功能代码: wp-content/plugins/video-enhancer-tool/ ├── video-enhancer.php # 主插件文件 ├── includes/ │ ├── class-video-processor.php # 视频处理类 │ ├── class-chapter-manager.php # 章节管理类 │ └── class-settings-handler.php # 设置处理类 ├── assets/ │ ├── css/ │ │ ├── video-controls.css # 控制界面样式 │ │ └── admin-styles.css # 后台管理样式 │ ├── js/ │ │ ├── video-controls.js # 前端控制逻辑 │ │ ├── chapter-editor.js # 章节编辑器 │ │ └── admin-scripts.js # 后台脚本 │ └── images/ # 图标和图片资源 ├── templates/ # 前端模板文件 │ └── video-player-enhanced.php └── languages/ # 国际化文件
- 我们将使用以下关键技术: HTML5 Video API:控制视频播放的核心JavaScript接口 WordPress REST API:处理章节数据的存储和检索 jQuery:简化DOM操作和事件处理(WordPress已内置) LocalStorage:存储用户偏好设置 CSS3 Flexbox/Grid:构建响应式控制界面
-
- 首先,我们需要确保网站的视频使用HTML5播放器,这是实现高级控制的基础: // 在video-enhancer.php中 function vet_replace_video_shortcode($output, $tag) { if ('video' !== $tag) { return $output; } // 为视频元素添加ID和类名以便JavaScript操作 $output = preg_replace('/<video/', '<video data-vet-enhanced="true"', $output); return $output; } add_filter('do_shortcode_tag', 'vet_replace_video_shortcode', 10, 2);
- 创建直观的速度控制界面,包含按钮和下拉菜单: /* assets/css/video-controls.css */ .vet-controls-container { position: relative; display: flex; align-items: center; justify-content: space-between; background: rgba(0, 0, 0, 0.7); padding: 10px 15px; border-radius: 5px; margin-top: 10px; color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } .vet-speed-control { display: flex; align-items: center; } .vet-speed-label { margin-right: 10px; font-size: 14px; opacity: 0.9; } .vet-speed-buttons { display: flex; gap: 5px; } .vet-speed-btn { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color: white; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 13px; transition: all 0.2s ease; } .vet-speed-btn:hover { background: rgba(255, 255, 255, 0.2); } .vet-speed-btn.active { background: #0073aa; border-color: #0073aa; } .vet-speed-dropdown { position: relative; display: inline-block; } .vet-speed-dropdown-btn { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color: white; padding: 5px 15px 5px 10px; border-radius: 3px; cursor: pointer; font-size: 13px; position: relative; } .vet-speed-dropdown-btn:after { content: "▼"; font-size: 10px; margin-left: 5px; } .vet-speed-dropdown-content { display: none; position: absolute; bottom: 100%; left: 0; background: rgba(0, 0, 0, 0.9); min-width: 80px; border-radius: 3px; z-index: 1000; margin-bottom: 5px; } .vet-speed-dropdown-content.show { display: block; } .vet-speed-option { color: white; padding: 8px 12px; text-decoration: none; display: block; cursor: pointer; font-size: 13px; } .vet-speed-option:hover { background: rgba(255, 255, 255, 0.1); }
- 实现播放速度控制的核心JavaScript代码: // assets/js/video-controls.js (function($) { 'use strict'; // 视频增强工具主对象 var VideoEnhancer = { // 初始化 init: function() { this.setupVideoPlayers(); this.bindEvents(); this.loadUserPreferences(); }, // 查找页面上的视频元素并添加控制 setupVideoPlayers: function() { $('video[data-vet-enhanced="true"]').each(function() { var $video = $(this); // 确保视频有ID if (!$video.attr('id')) { $video.attr('id', 'vet-video-' + Math.random().toString(36).substr(2, 9)); } // 添加控制容器 VideoEnhancer.addControls($video); }); }, // 为视频添加控制界面 addControls: function($video) { var videoId = $video.attr('id'); var controlsHtml = ` <div class="vet-controls-container" data-video-id="${videoId}"> <div class="vet-speed-control"> <span class="vet-speed-label">播放速度:</span> <div class="vet-speed-buttons"> <button class="vet-speed-btn" data-speed="0.5">0.5x</button> <button class="vet-speed-btn" data-speed="0.75">0.75x</button> <button class="vet-speed-btn active" data-speed="1">1x</button> <button class="vet-speed-btn" data-speed="1.25">1.25x</button> <button class="vet-speed-btn" data-speed="1.5">1.5x</button> <button class="vet-speed-btn" data-speed="2">2x</button> </div> <div class="vet-speed-dropdown"> <button class="vet-speed-dropdown-btn">更多速度</button> <div class="vet-speed-dropdown-content"> <div class="vet-speed-option" data-speed="0.25">0.25x</div> <div class="vet-speed-option" data-speed="0.5">0.5x</div> <div class="vet-speed-option" data-speed="0.75">0.75x</div> <div class="vet-speed-option" data-speed="1">1x</div> <div class="vet-speed-option" data-speed="1.25">1.25x</div> <div class="vet-speed-option" data-speed="1.5">1.5x</div> <div class="vet-speed-option" data-speed="1.75">1.75x</div> <div class="vet-speed-option" data-speed="2">2x</div> <div class="vet-speed-option" data-speed="2.5">2.5x</div> <div class="vet-speed-option" data-speed="3">3x</div> </div> </div> </div> <div class="vet-chapter-control"> <button class="vet-chapters-toggle-btn">章节</button> <div class="vet-chapters-panel"> <div class="vet-chapters-list"></div> </div> </div> </div> `; // 将控制容器插入到视频后面 $video.after(controlsHtml); // 初始化章节功能 VideoEnhancer.loadChaptersForVideo(videoId); }, // 绑定事件处理 bindEvents: function() { // 速度按钮点击事件 $(document).on('click', '.vet-speed-btn', function() { var speed = $(this).data('speed'); VideoEnhancer.setPlaybackSpeed(speed, $(this)); }); // 下拉速度选项点击事件 $(document).on('click', '.vet-speed-option', function() { var speed = $(this).data('speed'); VideoEnhancer.setPlaybackSpeed(speed, $(this)); }); // 下拉菜单显示/隐藏 $(document).on('click', '.vet-speed-dropdown-btn', function(e) { e.stopPropagation(); $(this).siblings('.vet-speed-dropdown-content').toggleClass('show'); }); // 点击其他地方关闭下拉菜单 $(document).on('click', function() { $('.vet-speed-dropdown-content').removeClass('show'); }); // 章节切换按钮 $(document).on('click', '.vet-chapters-toggle-btn', function() { $(this).siblings('.vet-chapters-panel').toggleClass('show'); }); }, // 设置播放速度 setPlaybackSpeed: function(speed, $element) { // 获取对应的视频元素 var videoId = $element.closest('.vet-controls-container').data('video-id'); var video = document.getElementById(videoId); if (video) { // 设置播放速度 video.playbackRate = speed; // 更新按钮状态 $('.vet-speed-btn').removeClass('active'); $('.vet-speed-btn[data-speed="' + speed + '"]').addClass('active'); // 更新下拉按钮文本 var $dropdownBtn = $element.closest('.vet-controls-container').find('.vet-speed-dropdown-btn'); $dropdownBtn.text(speed + 'x'); // 保存用户偏好 VideoEnhancer.saveUserPreference('playbackSpeed', speed); // 显示速度变化提示 VideoEnhancer.showSpeedNotification(speed); } }, // 显示速度变化提示 showSpeedNotification: function(speed) { // 创建或获取通知元素 var $notification = $('.vet-speed-notification'); if ($notification.length === 0) { $notification = $('<div class="vet-speed-notification"></div>'); $('body').append($notification); } // 设置内容和显示 $notification.text('播放速度: ' + speed + 'x').addClass('show'); // 2秒后隐藏 setTimeout(function() { $notification.removeClass('show'); }, 2000); }, // 加载用户偏好设置 loadUserPreferences: function() { var savedSpeed = localStorage.getItem('vet_playback_speed'); if (savedSpeed) { // 页面加载后应用保存的速度 $(document).ready(function() { setTimeout(function() { $('.vet-speed-btn[data-speed="' + savedSpeed + '"]').click(); }, 500); }); } }, // 保存用户偏好 saveUserPreference: function(key, value) { if (key === 'playbackSpeed') { localStorage.setItem('vet_playback_speed', value); } }, // 加载视频章节 loadChaptersForVideo: function(videoId) { // 通过AJAX获取章节数据 $.ajax({ url: vet_ajax.ajax_url, type: 'POST', data: { action: 'vet_get_chapters', video_id: videoId, nonce: vet_ajax.nonce }, success: function(response) { if (response.success && response.data.chapters) { VideoEnhancer.renderChapters(videoId, response.data.chapters); } } }); }, // 渲染章节列表 renderChapters: function(videoId, chapters) { var $chaptersList = $('.vet-controls-container[data-video-id="' + videoId + '"] .vet-chapters-list'); var html = ''; if (chapters.length > 0) { html += '<div class="vet-chapters-header">视频章节</div>'; chapters.forEach(function(chapter) { html += ` <div class="vet-chapter-item" data-timestamp="${chapter.timestamp}"> <div class="vet-chapter-time">${VideoEnhancer.formatTime(chapter.timestamp)}</div> <div class="vet-chapter-title">${chapter.title}</div> </div> `; }); // 绑定章节点击事件 $chaptersList.html(html); $chaptersList.find('.vet-chapter-item').on('click', function() { var timestamp = $(this).data('timestamp'); var video = document.getElementById(videoId); if (video) { video.currentTime = timestamp; video.play(); } }); } else { $chaptersList.html('<div class="vet-no-chapters">暂无章节标记</div>'); } }, // 格式化时间显示 formatTime: function(seconds) { var hrs = Math.floor(seconds / 3600); var mins = Math.floor((seconds % 3600) / 60); var secs = Math.floor(seconds % 60); if (hrs > 0) { return hrs + ':' + (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs; } else { return mins + ':' + (secs < 10 ? '0' : '') + secs; } } }; // 初始化 $(document).ready(function() { VideoEnhancer.init(); }); })(jQuery);
- 创建PHP类来处理速度控制相关的后端逻辑: // includes/class-video-processor.php class Video_Enhancer_Processor { // 初始化 public function __construct() { add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); add_action('wp_ajax_vet_get_chapters', array($this, 'ajax_get_chapters')); add_action('wp_ajax_nopriv_vet_get_chapters', array($this, 'ajax_get_chapters')); add_action('wp_ajax_vet_save_chapters', array($this, 'ajax_save_chapters')); } // 加载前端资源 public function enqueue_frontend_assets() { // 加载CSS wp_enqueue_style( 'video-enhancer-controls', plugin_dir_url(__FILE__) . '../assets/css/video-controls.css', array(), '1.0.0' ); // 加载JavaScript wp_enqueue_script( 'video-enhancer-controls', plugin_dir_url(__FILE__) . '../assets/js/video-controls.js', array('jquery'), '1.0.0', true ); // 传递AJAX参数到前端 wp_localize_script('video-enhancer-controls', 'vet_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('vet_ajax_nonce') )); } // 加载后台资源 public function enqueue_admin_assets($hook) { // 只在文章编辑页面加载 if ('post.php' !== $hook && 'post-new.php' !== $hook) { return; } wp_enqueue_style( 'video-enhancer-admin', plugin_dir_url(__FILE__) . '../assets/css/admin-styles.css', array(), '1.0.0' ); wp_enqueue_script( 'video-enhancer-admin', plugin_dir_url(__FILE__) . '../assets/js/admin-scripts.js', array('jquery', 'jquery-ui-sortable'), '1.0.0', true ); } // AJAX获取章节数据 public function ajax_get_chapters() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'vet_ajax_nonce')) { wp_die('安全验证失败'); } $video_id = sanitize_text_field($_POST['video_id']); $post_id = get_the_ID(); // 获取章节数据 $chapters = $this->get_chapters_for_video($post_id, $video_id); wp_send_json_success(array( 'chapters' => $chapters )); } // 获取视频章节 private function get_chapters_for_video($post_id, $video_id) { $chapters = get_post_meta($post_id, '_vet_video_chapters_' . $video_id, true); if (empty($chapters) || !is_array($chapters)) { return array(); } // 按时间戳排序 usort($chapters, function($a, $b) { return $a['timestamp'] - $b['timestamp']; }); return $chapters; } }
-
- 章节数据需要包含以下信息: 时间戳(秒) 章节标题 章节描述(可选) 缩略图(可选) // includes/class-chapter-manager.php class Video_Enhancer_Chapter_Manager { // 初始化 public function __construct() { add_action('add_meta_boxes', array($this, 'add_chapter_meta_box')); add_action('save_post', array($this, 'save_chapter_data')); } // 添加章节管理元框 public function add_chapter_meta_box() { $post_types = apply_filters('vet_supported_post_types', array('post', 'page')); foreach ($post_types as $post_type) { add_meta_box( 'vet-chapter-manager', '视频章节管理', array($this, 'render_chapter_meta_box'), $post_type, 'normal', 'high' ); } } // 渲染章节管理界面 public function render_chapter_meta_box($post) { // 获取文章中的视频 $videos = $this->extract_videos_from_content($post->post_content); if (empty($videos)) { echo '<p>本文中没有找到视频。请先添加视频到内容中。</p>'; return; } // 添加非ce字段 wp_nonce_field('vet_save_chapters', 'vet_chapter_nonce'); echo '<div class="vet-chapter-manager">'; foreach ($videos as $index => $video) { $video_id = $video['id']; $chapters = $this->get_chapters_for_video($post->ID, $video_id); echo '<div class="vet-video-section">'; echo '<h3>视频 #' . ($index + 1) . '</h3>'; echo '<div class="vet-video-preview">' . $video['html'] . '</div>'; // 章节管理界面 $this->render_chapter_editor($video_id, $chapters); echo '</div>'; } echo '</div>'; } // 渲染章节编辑器 private function render_chapter_editor($video_id, $chapters) { ?> <div class="vet-chapter-editor" data-video-id="<?php echo esc_attr($video_id); ?>"> <div class="vet-chapter-header"> <h4>章节管理</h4> <button type="button" class="button vet-add-chapter">添加章节</button> </div> <div class="vet-chapters-list"> <?php if (!empty($chapters)): ?> <?php foreach ($chapters as $chapter): ?> <div class="vet-chapter-item" data-timestamp="<?php echo esc_attr($chapter['timestamp']); ?>"> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][timestamp][]" value="<?php echo esc_attr($chapter['timestamp']); ?>"> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][title][]" value="<?php echo esc_attr($chapter['title']); ?>"> <div class="vet-chapter-preview"> <span class="vet-chapter-time"><?php echo $this->format_time($chapter['timestamp']); ?></span> <span class="vet-chapter-title"><?php echo esc_html($chapter['title']); ?></span> </div> <div class="vet-chapter-actions"> <button type="button" class="button vet-edit-chapter">编辑</button> <button type="button" class="button button-link vet-remove-chapter">删除</button> </div> </div> <?php endforeach; ?> <?php else: ?> <p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p> <?php endif; ?> </div> <!-- 章节编辑模板 --> <div class="vet-chapter-template" style="display: none;"> <div class="vet-chapter-item"> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][timestamp][]" value=""> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][title][]" value=""> <div class="vet-chapter-preview"> <span class="vet-chapter-time">00:00</span> <span class="vet-chapter-title">新章节</span> </div> <div class="vet-chapter-actions"> <button type="button" class="button vet-edit-chapter">编辑</button> <button type="button" class="button button-link vet-remove-chapter">删除</button> </div> </div> </div> </div> <?php } // 从内容中提取视频 private function extract_videos_from_content($content) { $videos = array(); // 匹配视频短代码 preg_match_all('/]*]/', $content, $shortcode_matches); foreach ($shortcode_matches[0] as $shortcode) { $video_id = 'video-' . md5($shortcode); $videos[] = array( 'id' => $video_id, 'html' => do_shortcode($shortcode) ); } // 匹配HTML5视频标签 preg_match_all('/<video[^>]*>/', $content, $html_matches); foreach ($html_matches[0] as $index => $video_tag) { $video_id = 'html5-video-' . $index; $videos[] = array( 'id' => $video_id, 'html' => $video_tag . '</video>' ); } return $videos; } // 保存章节数据 public function save_chapter_data($post_id) { // 检查nonce if (!isset($_POST['vet_chapter_nonce']) || !wp_verify_nonce($_POST['vet_chapter_nonce'], 'vet_save_chapters')) { return; } // 检查权限 if (!current_user_can('edit_post', $post_id)) { return; } // 保存章节数据 if (isset($_POST['vet_chapters']) && is_array($_POST['vet_chapters'])) { foreach ($_POST['vet_chapters'] as $video_id => $chapters_data) { $chapters = array(); if (isset($chapters_data['timestamp']) && isset($chapters_data['title'])) { $timestamps = $chapters_data['timestamp']; $titles = $chapters_data['title']; for ($i = 0; $i < count($timestamps); $i++) { if (!empty($timestamps[$i]) && !empty($titles[$i])) { $chapters[] = array( 'timestamp' => floatval($timestamps[$i]), 'title' => sanitize_text_field($titles[$i]) ); } } } // 按时间戳排序 usort($chapters, function($a, $b) { return $a['timestamp'] - $b['timestamp']; }); // 保存到post meta update_post_meta($post_id, '_vet_video_chapters_' . sanitize_key($video_id), $chapters); } } } // 格式化时间显示 private function format_time($seconds) { $mins = floor($seconds / 60); $secs = floor($seconds % 60); return sprintf('%02d:%02d', $mins, $secs); } }
- // assets/js/admin-scripts.js (function($) { 'use strict'; // 章节编辑器对象 var ChapterEditor = { init: function() { this.bindEvents(); this.initSortable(); }, bindEvents: function() { // 添加章节按钮 $(document).on('click', '.vet-add-chapter', function() { ChapterEditor.addNewChapter($(this).closest('.vet-chapter-editor')); }); // 编辑章节按钮 $(document).on('click', '.vet-edit-chapter', function() { ChapterEditor.editChapter($(this).closest('.vet-chapter-item')); }); // 删除章节按钮 $(document).on('click', '.vet-remove-chapter', function() { ChapterEditor.removeChapter($(this).closest('.vet-chapter-item')); }); // 视频时间点击事件 $(document).on('click', '.vet-video-preview video', function() { var $video = $(this); var currentTime = $video[0].currentTime; var $editor = $video.closest('.vet-video-section').find('.vet-chapter-editor'); // 在章节编辑器中显示当前时间 ChapterEditor.showCurrentTime($editor, currentTime); }); }, // 初始化可排序 initSortable: function() { $('.vet-chapters-list').sortable({ handle: '.vet-chapter-preview', update: function() { ChapterEditor.updateChapterOrder($(this)); } }); }, // 添加新章节 addNewChapter: function($editor) { var $template = $editor.find('.vet-chapter-template .vet-chapter-item').clone(); var $list = $editor.find('.vet-chapters-list'); // 移除"暂无章节"提示 $list.find('.vet-no-chapters').remove(); // 添加到列表 $list.append($template); // 编辑新章节 ChapterEditor.editChapter($template); }, // 编辑章节 editChapter: function($chapterItem) { var $preview = $chapterItem.find('.vet-chapter-preview'); var $timeSpan = $preview.find('.vet-chapter-time'); var $titleSpan = $preview.find('.vet-chapter-title'); var currentTime = $timeSpan.text(); var currentTitle = $titleSpan.text(); // 创建编辑表单 var $form = $('<div class="vet-chapter-edit-form"></div>'); $form.html(` <div class="vet-edit-fields"> <div class="vet-field"> <label>时间戳 (秒):</label> <input type="number" class="vet-time-input" step="0.1" min="0" value="${ChapterEditor.timeToSeconds(currentTime)}"> </div> <div class="vet-field"> <label>章节标题:</label> <input type="text" class="vet-title-input" value="${currentTitle}"> </div> <div class="vet-edit-actions"> <button type="button" class="button button-primary vet-save-chapter">保存</button> <button type="button" class="button vet-cancel-edit">取消</button> </div> </div> `); // 替换预览为编辑表单 $preview.hide(); $chapterItem.find('.vet-chapter-actions').hide(); $chapterItem.append($form); // 绑定保存事件 $form.find('.vet-save-chapter').on('click', function() { var timeInput = $form.find('.vet-time-input').val(); var titleInput = $form.find('.vet-title-input').val(); if (timeInput && titleInput) { // 更新时间戳和标题 $chapterItem.find('input[name*="timestamp"]').val(timeInput); $chapterItem.find('input[name*="title"]').val(titleInput); // 更新预览显示 $timeSpan.text(ChapterEditor.secondsToTime(timeInput)); $titleSpan.text(titleInput); } // 恢复显示 $form.remove(); $preview.show(); $chapterItem.find('.vet-chapter-actions').show(); }); // 绑定取消事件 $form.find('.vet-cancel-edit').on('click', function() { $form.remove(); $preview.show(); $chapterItem.find('.vet-chapter-actions').show(); }); }, // 删除章节 removeChapter: function($chapterItem) { if (confirm('确定要删除这个章节吗?')) { $chapterItem.remove(); // 如果没有章节了,显示提示 var $list = $chapterItem.closest('.vet-chapters-list'); if ($list.children('.vet-chapter-item').length === 0) { $list.html('<p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p>'); } } }, // 显示当前时间 showCurrentTime: function($editor, currentTime) { // 创建或更新时间提示 var $timeHint = $editor.find('.vet-current-time-hint'); if ($timeHint.length === 0) { $timeHint = $('<div class="vet-current-time-hint"></div>'); $editor.find('.vet-chapter-header').after($timeHint); } var timeStr = ChapterEditor.secondsToTime(currentTime); $timeHint.html('当前视频时间: <strong>' + timeStr + '</strong> (点击视频可获取当前时间)'); // 3秒后淡出 $timeHint.show(); setTimeout(function() { $timeHint.fadeOut(); }, 3000); }, // 更新章节顺序 updateChapterOrder: function($list) { // 重新排序后,可以在这里添加额外的处理逻辑 console.log('章节顺序已更新'); }, // 时间字符串转秒数 timeToSeconds: function(timeStr) { var parts = timeStr.split(':'); if (parts.length === 2) { return parseInt(parts[0]) * 60 + parseInt(parts[1]); } return 0; }, // 秒数转时间字符串 secondsToTime: function(seconds) { var mins = Math.floor(seconds / 60); var secs = Math.floor(seconds % 60); return (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs; } }; // 初始化 $(document).ready(function() { ChapterEditor.init(); }); })(jQuery);
- /* assets/css/admin-styles.css */ /* 章节管理器样式 */ .vet-chapter-manager { padding: 15px; background: #f8f9fa; border-radius: 5px; } .vet-video-section { margin-bottom: 30px; padding: 20px; background: white; border: 1px solid #ddd; border-radius: 5px; } .vet-video-section h3 { margin-top: 0; color: #23282d; border-bottom: 2px solid #0073aa; padding-bottom: 10px; } .vet-video-preview { margin: 15px 0; text-align: center; } .vet-video-preview video {
随着在线教育、知识分享和内容创作行业的蓬勃发展,视频内容已成为网站不可或缺的组成部分。然而,大多数网站的视频播放功能相对基础,缺乏个性化控制选项。本文将详细介绍如何通过WordPress代码二次开发,为网站视频播放器添加速度控制与章节标记功能,从而提升用户体验和内容互动性。我们将从需求分析、技术选型、代码实现到测试部署,全面解析这一实用工具的构建过程。
大多数WordPress网站使用默认的视频播放器或第三方插件,这些方案通常存在以下局限性:
- 播放速度固定,无法根据用户需求调整
- 缺乏章节标记功能,长视频导航困难
- 用户无法自定义播放体验
- 互动性差,用户参与度低
我们的开发目标是为WordPress网站视频播放器添加以下核心功能:
- 多级播放速度控制:提供0.5x到3.0x之间的多档速度选择
- 智能章节标记系统:允许内容创作者为视频添加章节标记
- 用户友好的交互界面:直观的控制面板,不影响观看体验
- 数据持久化:记住用户的播放偏好设置
- 响应式设计:适配各种设备和屏幕尺寸
WordPress作为开源CMS系统,提供了丰富的API和钩子机制,使我们能够:
- 通过JavaScript操作HTML5视频元素
- 使用PHP扩展视频元数据处理
- 利用WordPress数据库存储章节信息
- 通过AJAX实现前后端数据交互
在开始开发前,需要准备以下环境:
- 本地开发环境:XAMPP/MAMP或Local by Flywheel
- WordPress安装:最新版本的WordPress(5.8+)
- 代码编辑器:VS Code、Sublime Text或PHPStorm
- 浏览器开发者工具:用于调试JavaScript和CSS
- 版本控制系统:Git(可选但推荐)
我们将创建一个独立的WordPress插件来管理所有功能代码:
wp-content/plugins/video-enhancer-tool/
├── video-enhancer.php # 主插件文件
├── includes/
│ ├── class-video-processor.php # 视频处理类
│ ├── class-chapter-manager.php # 章节管理类
│ └── class-settings-handler.php # 设置处理类
├── assets/
│ ├── css/
│ │ ├── video-controls.css # 控制界面样式
│ │ └── admin-styles.css # 后台管理样式
│ ├── js/
│ │ ├── video-controls.js # 前端控制逻辑
│ │ ├── chapter-editor.js # 章节编辑器
│ │ └── admin-scripts.js # 后台脚本
│ └── images/ # 图标和图片资源
├── templates/ # 前端模板文件
│ └── video-player-enhanced.php
└── languages/ # 国际化文件
我们将使用以下关键技术:
- HTML5 Video API:控制视频播放的核心JavaScript接口
- WordPress REST API:处理章节数据的存储和检索
- jQuery:简化DOM操作和事件处理(WordPress已内置)
- LocalStorage:存储用户偏好设置
- CSS3 Flexbox/Grid:构建响应式控制界面
首先,我们需要确保网站的视频使用HTML5播放器,这是实现高级控制的基础:
// 在video-enhancer.php中
function vet_replace_video_shortcode($output, $tag) {
if ('video' !== $tag) {
return $output;
}
// 为视频元素添加ID和类名以便JavaScript操作
$output = preg_replace('/<video/', '<video data-vet-enhanced="true"', $output);
return $output;
}
add_filter('do_shortcode_tag', 'vet_replace_video_shortcode', 10, 2);
创建直观的速度控制界面,包含按钮和下拉菜单:
/* assets/css/video-controls.css */
.vet-controls-container {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(0, 0, 0, 0.7);
padding: 10px 15px;
border-radius: 5px;
margin-top: 10px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.vet-speed-control {
display: flex;
align-items: center;
}
.vet-speed-label {
margin-right: 10px;
font-size: 14px;
opacity: 0.9;
}
.vet-speed-buttons {
display: flex;
gap: 5px;
}
.vet-speed-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s ease;
}
.vet-speed-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.vet-speed-btn.active {
background: #0073aa;
border-color: #0073aa;
}
.vet-speed-dropdown {
position: relative;
display: inline-block;
}
.vet-speed-dropdown-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 5px 15px 5px 10px;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
position: relative;
}
.vet-speed-dropdown-btn:after {
content: "▼";
font-size: 10px;
margin-left: 5px;
}
.vet-speed-dropdown-content {
display: none;
position: absolute;
bottom: 100%;
left: 0;
background: rgba(0, 0, 0, 0.9);
min-width: 80px;
border-radius: 3px;
z-index: 1000;
margin-bottom: 5px;
}
.vet-speed-dropdown-content.show {
display: block;
}
.vet-speed-option {
color: white;
padding: 8px 12px;
text-decoration: none;
display: block;
cursor: pointer;
font-size: 13px;
}
.vet-speed-option:hover {
background: rgba(255, 255, 255, 0.1);
}
实现播放速度控制的核心JavaScript代码:
// assets/js/video-controls.js
(function($) {
'use strict';
// 视频增强工具主对象
var VideoEnhancer = {
// 初始化
init: function() {
this.setupVideoPlayers();
this.bindEvents();
this.loadUserPreferences();
},
// 查找页面上的视频元素并添加控制
setupVideoPlayers: function() {
$('video[data-vet-enhanced="true"]').each(function() {
var $video = $(this);
// 确保视频有ID
if (!$video.attr('id')) {
$video.attr('id', 'vet-video-' + Math.random().toString(36).substr(2, 9));
}
// 添加控制容器
VideoEnhancer.addControls($video);
});
},
// 为视频添加控制界面
addControls: function($video) {
var videoId = $video.attr('id');
var controlsHtml = `
<div class="vet-controls-container" data-video-id="${videoId}">
<div class="vet-speed-control">
<span class="vet-speed-label">播放速度:</span>
<div class="vet-speed-buttons">
<button class="vet-speed-btn" data-speed="0.5">0.5x</button>
<button class="vet-speed-btn" data-speed="0.75">0.75x</button>
<button class="vet-speed-btn active" data-speed="1">1x</button>
<button class="vet-speed-btn" data-speed="1.25">1.25x</button>
<button class="vet-speed-btn" data-speed="1.5">1.5x</button>
<button class="vet-speed-btn" data-speed="2">2x</button>
</div>
<div class="vet-speed-dropdown">
<button class="vet-speed-dropdown-btn">更多速度</button>
<div class="vet-speed-dropdown-content">
<div class="vet-speed-option" data-speed="0.25">0.25x</div>
<div class="vet-speed-option" data-speed="0.5">0.5x</div>
<div class="vet-speed-option" data-speed="0.75">0.75x</div>
<div class="vet-speed-option" data-speed="1">1x</div>
<div class="vet-speed-option" data-speed="1.25">1.25x</div>
<div class="vet-speed-option" data-speed="1.5">1.5x</div>
<div class="vet-speed-option" data-speed="1.75">1.75x</div>
<div class="vet-speed-option" data-speed="2">2x</div>
<div class="vet-speed-option" data-speed="2.5">2.5x</div>
<div class="vet-speed-option" data-speed="3">3x</div>
</div>
</div>
</div>
<div class="vet-chapter-control">
<button class="vet-chapters-toggle-btn">章节</button>
<div class="vet-chapters-panel">
<div class="vet-chapters-list"></div>
</div>
</div>
</div>
`;
// 将控制容器插入到视频后面
$video.after(controlsHtml);
// 初始化章节功能
VideoEnhancer.loadChaptersForVideo(videoId);
},
// 绑定事件处理
bindEvents: function() {
// 速度按钮点击事件
$(document).on('click', '.vet-speed-btn', function() {
var speed = $(this).data('speed');
VideoEnhancer.setPlaybackSpeed(speed, $(this));
});
// 下拉速度选项点击事件
$(document).on('click', '.vet-speed-option', function() {
var speed = $(this).data('speed');
VideoEnhancer.setPlaybackSpeed(speed, $(this));
});
// 下拉菜单显示/隐藏
$(document).on('click', '.vet-speed-dropdown-btn', function(e) {
e.stopPropagation();
$(this).siblings('.vet-speed-dropdown-content').toggleClass('show');
});
// 点击其他地方关闭下拉菜单
$(document).on('click', function() {
$('.vet-speed-dropdown-content').removeClass('show');
});
// 章节切换按钮
$(document).on('click', '.vet-chapters-toggle-btn', function() {
$(this).siblings('.vet-chapters-panel').toggleClass('show');
});
},
// 设置播放速度
setPlaybackSpeed: function(speed, $element) {
// 获取对应的视频元素
var videoId = $element.closest('.vet-controls-container').data('video-id');
var video = document.getElementById(videoId);
if (video) {
// 设置播放速度
video.playbackRate = speed;
// 更新按钮状态
$('.vet-speed-btn').removeClass('active');
$('.vet-speed-btn[data-speed="' + speed + '"]').addClass('active');
// 更新下拉按钮文本
var $dropdownBtn = $element.closest('.vet-controls-container').find('.vet-speed-dropdown-btn');
$dropdownBtn.text(speed + 'x');
// 保存用户偏好
VideoEnhancer.saveUserPreference('playbackSpeed', speed);
// 显示速度变化提示
VideoEnhancer.showSpeedNotification(speed);
}
},
// 显示速度变化提示
showSpeedNotification: function(speed) {
// 创建或获取通知元素
var $notification = $('.vet-speed-notification');
if ($notification.length === 0) {
$notification = $('<div class="vet-speed-notification"></div>');
$('body').append($notification);
}
// 设置内容和显示
$notification.text('播放速度: ' + speed + 'x').addClass('show');
// 2秒后隐藏
setTimeout(function() {
$notification.removeClass('show');
}, 2000);
},
// 加载用户偏好设置
loadUserPreferences: function() {
var savedSpeed = localStorage.getItem('vet_playback_speed');
if (savedSpeed) {
// 页面加载后应用保存的速度
$(document).ready(function() {
setTimeout(function() {
$('.vet-speed-btn[data-speed="' + savedSpeed + '"]').click();
}, 500);
});
}
},
// 保存用户偏好
saveUserPreference: function(key, value) {
if (key === 'playbackSpeed') {
localStorage.setItem('vet_playback_speed', value);
}
},
// 加载视频章节
loadChaptersForVideo: function(videoId) {
// 通过AJAX获取章节数据
$.ajax({
url: vet_ajax.ajax_url,
type: 'POST',
data: {
action: 'vet_get_chapters',
video_id: videoId,
nonce: vet_ajax.nonce
},
success: function(response) {
if (response.success && response.data.chapters) {
VideoEnhancer.renderChapters(videoId, response.data.chapters);
}
}
});
},
// 渲染章节列表
renderChapters: function(videoId, chapters) {
var $chaptersList = $('.vet-controls-container[data-video-id="' + videoId + '"] .vet-chapters-list');
var html = '';
if (chapters.length > 0) {
html += '<div class="vet-chapters-header">视频章节</div>';
chapters.forEach(function(chapter) {
html += `
<div class="vet-chapter-item" data-timestamp="${chapter.timestamp}">
<div class="vet-chapter-time">${VideoEnhancer.formatTime(chapter.timestamp)}</div>
<div class="vet-chapter-title">${chapter.title}</div>
</div>
`;
});
// 绑定章节点击事件
$chaptersList.html(html);
$chaptersList.find('.vet-chapter-item').on('click', function() {
var timestamp = $(this).data('timestamp');
var video = document.getElementById(videoId);
if (video) {
video.currentTime = timestamp;
video.play();
}
});
} else {
$chaptersList.html('<div class="vet-no-chapters">暂无章节标记</div>');
}
},
// 格式化时间显示
formatTime: function(seconds) {
var hrs = Math.floor(seconds / 3600);
var mins = Math.floor((seconds % 3600) / 60);
var secs = Math.floor(seconds % 60);
if (hrs > 0) {
return hrs + ':' + (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs;
} else {
return mins + ':' + (secs < 10 ? '0' : '') + secs;
}
}
};
// 初始化
$(document).ready(function() {
VideoEnhancer.init();
});
})(jQuery);
创建PHP类来处理速度控制相关的后端逻辑:
// includes/class-video-processor.php
class Video_Enhancer_Processor {
// 初始化
public function __construct() {
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
add_action('wp_ajax_vet_get_chapters', array($this, 'ajax_get_chapters'));
add_action('wp_ajax_nopriv_vet_get_chapters', array($this, 'ajax_get_chapters'));
add_action('wp_ajax_vet_save_chapters', array($this, 'ajax_save_chapters'));
}
// 加载前端资源
public function enqueue_frontend_assets() {
// 加载CSS
wp_enqueue_style(
'video-enhancer-controls',
plugin_dir_url(__FILE__) . '../assets/css/video-controls.css',
array(),
'1.0.0'
);
// 加载JavaScript
wp_enqueue_script(
'video-enhancer-controls',
plugin_dir_url(__FILE__) . '../assets/js/video-controls.js',
array('jquery'),
'1.0.0',
true
);
// 传递AJAX参数到前端
wp_localize_script('video-enhancer-controls', 'vet_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('vet_ajax_nonce')
));
}
// 加载后台资源
public function enqueue_admin_assets($hook) {
// 只在文章编辑页面加载
if ('post.php' !== $hook && 'post-new.php' !== $hook) {
return;
}
wp_enqueue_style(
'video-enhancer-admin',
plugin_dir_url(__FILE__) . '../assets/css/admin-styles.css',
array(),
'1.0.0'
);
wp_enqueue_script(
'video-enhancer-admin',
plugin_dir_url(__FILE__) . '../assets/js/admin-scripts.js',
array('jquery', 'jquery-ui-sortable'),
'1.0.0',
true
);
}
// AJAX获取章节数据
public function ajax_get_chapters() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'vet_ajax_nonce')) {
wp_die('安全验证失败');
}
$video_id = sanitize_text_field($_POST['video_id']);
$post_id = get_the_ID();
// 获取章节数据
$chapters = $this->get_chapters_for_video($post_id, $video_id);
wp_send_json_success(array(
'chapters' => $chapters
));
}
// 获取视频章节
private function get_chapters_for_video($post_id, $video_id) {
$chapters = get_post_meta($post_id, '_vet_video_chapters_' . $video_id, true);
if (empty($chapters) || !is_array($chapters)) {
return array();
}
// 按时间戳排序
usort($chapters, function($a, $b) {
return $a['timestamp'] - $b['timestamp'];
});
return $chapters;
}
}
章节数据需要包含以下信息:
- 时间戳(秒)
- 章节标题
- 章节描述(可选)
- 缩略图(可选)
// includes/class-chapter-manager.php
class Video_Enhancer_Chapter_Manager {
// 初始化
public function __construct() {
add_action('add_meta_boxes', array($this, 'add_chapter_meta_box'));
add_action('save_post', array($this, 'save_chapter_data'));
}
// 添加章节管理元框
public function add_chapter_meta_box() {
$post_types = apply_filters('vet_supported_post_types', array('post', 'page'));
foreach ($post_types as $post_type) {
add_meta_box(
'vet-chapter-manager',
'视频章节管理',
array($this, 'render_chapter_meta_box'),
$post_type,
'normal',
'high'
);
}
}
// 渲染章节管理界面
public function render_chapter_meta_box($post) {
// 获取文章中的视频
$videos = $this->extract_videos_from_content($post->post_content);
if (empty($videos)) {
echo '<p>本文中没有找到视频。请先添加视频到内容中。</p>';
return;
}
// 添加非ce字段
wp_nonce_field('vet_save_chapters', 'vet_chapter_nonce');
echo '<div class="vet-chapter-manager">';
foreach ($videos as $index => $video) {
$video_id = $video['id'];
$chapters = $this->get_chapters_for_video($post->ID, $video_id);
echo '<div class="vet-video-section">';
echo '<h3>视频 #' . ($index + 1) . '</h3>';
echo '<div class="vet-video-preview">' . $video['html'] . '</div>';
// 章节管理界面
$this->render_chapter_editor($video_id, $chapters);
echo '</div>';
}
echo '</div>';
}
// 渲染章节编辑器
private function render_chapter_editor($video_id, $chapters) {
?>
<div class="vet-chapter-editor" data-video-id="<?php echo esc_attr($video_id); ?>">
<div class="vet-chapter-header">
<h4>章节管理</h4>
<button type="button" class="button vet-add-chapter">添加章节</button>
</div>
<div class="vet-chapters-list">
<?php if (!empty($chapters)): ?>
<?php foreach ($chapters as $chapter): ?>
<div class="vet-chapter-item" data-timestamp="<?php echo esc_attr($chapter['timestamp']); ?>">
<input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][timestamp][]" value="<?php echo esc_attr($chapter['timestamp']); ?>">
<input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][title][]" value="<?php echo esc_attr($chapter['title']); ?>">
<div class="vet-chapter-preview">
<span class="vet-chapter-time"><?php echo $this->format_time($chapter['timestamp']); ?></span>
<span class="vet-chapter-title"><?php echo esc_html($chapter['title']); ?></span>
</div>
<div class="vet-chapter-actions">
<button type="button" class="button vet-edit-chapter">编辑</button>
<button type="button" class="button button-link vet-remove-chapter">删除</button>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p>
<?php endif; ?>
</div>
<!-- 章节编辑模板 -->
<div class="vet-chapter-template" style="display: none;">
<div class="vet-chapter-item">
<input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][timestamp][]" value="">
<input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][title][]" value="">
<div class="vet-chapter-preview">
<span class="vet-chapter-time">00:00</span>
<span class="vet-chapter-title">新章节</span>
</div>
<div class="vet-chapter-actions">
<button type="button" class="button vet-edit-chapter">编辑</button>
<button type="button" class="button button-link vet-remove-chapter">删除</button>
</div>
</div>
</div>
</div>
<?php
}
// 从内容中提取视频
private function extract_videos_from_content($content) {
$videos = array();
// 匹配视频短代码
preg_match_all('/]*]/', $content, $shortcode_matches);
foreach ($shortcode_matches[0] as $shortcode) {
$video_id = 'video-' . md5($shortcode);
$videos[] = array(
'id' => $video_id,
'html' => do_shortcode($shortcode)
);
}
// 匹配HTML5视频标签
preg_match_all('/<video[^>]*>/', $content, $html_matches);
foreach ($html_matches[0] as $index => $video_tag) {
$video_id = 'html5-video-' . $index;
$videos[] = array(
'id' => $video_id,
'html' => $video_tag . '</video>'
);
}
return $videos;
}
// 保存章节数据
public function save_chapter_data($post_id) {
// 检查nonce
if (!isset($_POST['vet_chapter_nonce']) ||
!wp_verify_nonce($_POST['vet_chapter_nonce'], 'vet_save_chapters')) {
return;
}
// 检查权限
if (!current_user_can('edit_post', $post_id)) {
return;
}
// 保存章节数据
if (isset($_POST['vet_chapters']) && is_array($_POST['vet_chapters'])) {
foreach ($_POST['vet_chapters'] as $video_id => $chapters_data) {
$chapters = array();
if (isset($chapters_data['timestamp']) && isset($chapters_data['title'])) {
$timestamps = $chapters_data['timestamp'];
$titles = $chapters_data['title'];
for ($i = 0; $i < count($timestamps); $i++) {
if (!empty($timestamps[$i]) && !empty($titles[$i])) {
$chapters[] = array(
'timestamp' => floatval($timestamps[$i]),
'title' => sanitize_text_field($titles[$i])
);
}
}
}
// 按时间戳排序
usort($chapters, function($a, $b) {
return $a['timestamp'] - $b['timestamp'];
});
// 保存到post meta
update_post_meta($post_id, '_vet_video_chapters_' . sanitize_key($video_id), $chapters);
}
}
}
// 格式化时间显示
private function format_time($seconds) {
$mins = floor($seconds / 60);
$secs = floor($seconds % 60);
return sprintf('%02d:%02d', $mins, $secs);
}
}
// assets/js/admin-scripts.js
(function($) {
'use strict';
// 章节编辑器对象
var ChapterEditor = {
init: function() {
this.bindEvents();
this.initSortable();
},
bindEvents: function() {
// 添加章节按钮
$(document).on('click', '.vet-add-chapter', function() {
ChapterEditor.addNewChapter($(this).closest('.vet-chapter-editor'));
});
// 编辑章节按钮
$(document).on('click', '.vet-edit-chapter', function() {
ChapterEditor.editChapter($(this).closest('.vet-chapter-item'));
});
// 删除章节按钮
$(document).on('click', '.vet-remove-chapter', function() {
ChapterEditor.removeChapter($(this).closest('.vet-chapter-item'));
});
// 视频时间点击事件
$(document).on('click', '.vet-video-preview video', function() {
var $video = $(this);
var currentTime = $video[0].currentTime;
var $editor = $video.closest('.vet-video-section').find('.vet-chapter-editor');
// 在章节编辑器中显示当前时间
ChapterEditor.showCurrentTime($editor, currentTime);
});
},
// 初始化可排序
initSortable: function() {
$('.vet-chapters-list').sortable({
handle: '.vet-chapter-preview',
update: function() {
ChapterEditor.updateChapterOrder($(this));
}
});
},
// 添加新章节
addNewChapter: function($editor) {
var $template = $editor.find('.vet-chapter-template .vet-chapter-item').clone();
var $list = $editor.find('.vet-chapters-list');
// 移除"暂无章节"提示
$list.find('.vet-no-chapters').remove();
// 添加到列表
$list.append($template);
// 编辑新章节
ChapterEditor.editChapter($template);
},
// 编辑章节
editChapter: function($chapterItem) {
var $preview = $chapterItem.find('.vet-chapter-preview');
var $timeSpan = $preview.find('.vet-chapter-time');
var $titleSpan = $preview.find('.vet-chapter-title');
var currentTime = $timeSpan.text();
var currentTitle = $titleSpan.text();
// 创建编辑表单
var $form = $('<div class="vet-chapter-edit-form"></div>');
$form.html(`
<div class="vet-edit-fields">
<div class="vet-field">
<label>时间戳 (秒):</label>
<input type="number" class="vet-time-input" step="0.1" min="0" value="${ChapterEditor.timeToSeconds(currentTime)}">
</div>
<div class="vet-field">
<label>章节标题:</label>
<input type="text" class="vet-title-input" value="${currentTitle}">
</div>
<div class="vet-edit-actions">
<button type="button" class="button button-primary vet-save-chapter">保存</button>
<button type="button" class="button vet-cancel-edit">取消</button>
</div>
</div>
`);
// 替换预览为编辑表单
$preview.hide();
$chapterItem.find('.vet-chapter-actions').hide();
$chapterItem.append($form);
// 绑定保存事件
$form.find('.vet-save-chapter').on('click', function() {
var timeInput = $form.find('.vet-time-input').val();
var titleInput = $form.find('.vet-title-input').val();
if (timeInput && titleInput) {
// 更新时间戳和标题
$chapterItem.find('input[name*="timestamp"]').val(timeInput);
$chapterItem.find('input[name*="title"]').val(titleInput);
// 更新预览显示
$timeSpan.text(ChapterEditor.secondsToTime(timeInput));
$titleSpan.text(titleInput);
}
// 恢复显示
$form.remove();
$preview.show();
$chapterItem.find('.vet-chapter-actions').show();
});
// 绑定取消事件
$form.find('.vet-cancel-edit').on('click', function() {
$form.remove();
$preview.show();
$chapterItem.find('.vet-chapter-actions').show();
});
},
// 删除章节
removeChapter: function($chapterItem) {
if (confirm('确定要删除这个章节吗?')) {
$chapterItem.remove();
// 如果没有章节了,显示提示
var $list = $chapterItem.closest('.vet-chapters-list');
if ($list.children('.vet-chapter-item').length === 0) {
$list.html('<p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p>');
}
}
},
// 显示当前时间
showCurrentTime: function($editor, currentTime) {
// 创建或更新时间提示
var $timeHint = $editor.find('.vet-current-time-hint');
if ($timeHint.length === 0) {
$timeHint = $('<div class="vet-current-time-hint"></div>');
$editor.find('.vet-chapter-header').after($timeHint);
}
var timeStr = ChapterEditor.secondsToTime(currentTime);
$timeHint.html('当前视频时间: <strong>' + timeStr + '</strong> (点击视频可获取当前时间)');
// 3秒后淡出
$timeHint.show();
setTimeout(function() {
$timeHint.fadeOut();
}, 3000);
},
// 更新章节顺序
updateChapterOrder: function($list) {
// 重新排序后,可以在这里添加额外的处理逻辑
console.log('章节顺序已更新');
},
// 时间字符串转秒数
timeToSeconds: function(timeStr) {
var parts = timeStr.split(':');
if (parts.length === 2) {
return parseInt(parts[0]) * 60 + parseInt(parts[1]);
}
return 0;
},
// 秒数转时间字符串
secondsToTime: function(seconds) {
var mins = Math.floor(seconds / 60);
var secs = Math.floor(seconds % 60);
return (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs;
}
};
// 初始化
$(document).ready(function() {
ChapterEditor.init();
});
})(jQuery);
// assets/js/admin-scripts.js
(function($) {
'use strict';
// 章节编辑器对象
var ChapterEditor = {
init: function() {
this.bindEvents();
this.initSortable();
},
bindEvents: function() {
// 添加章节按钮
$(document).on('click', '.vet-add-chapter', function() {
ChapterEditor.addNewChapter($(this).closest('.vet-chapter-editor'));
});
// 编辑章节按钮
$(document).on('click', '.vet-edit-chapter', function() {
ChapterEditor.editChapter($(this).closest('.vet-chapter-item'));
});
// 删除章节按钮
$(document).on('click', '.vet-remove-chapter', function() {
ChapterEditor.removeChapter($(this).closest('.vet-chapter-item'));
});
// 视频时间点击事件
$(document).on('click', '.vet-video-preview video', function() {
var $video = $(this);
var currentTime = $video[0].currentTime;
var $editor = $video.closest('.vet-video-section').find('.vet-chapter-editor');
// 在章节编辑器中显示当前时间
ChapterEditor.showCurrentTime($editor, currentTime);
});
},
// 初始化可排序
initSortable: function() {
$('.vet-chapters-list').sortable({
handle: '.vet-chapter-preview',
update: function() {
ChapterEditor.updateChapterOrder($(this));
}
});
},
// 添加新章节
addNewChapter: function($editor) {
var $template = $editor.find('.vet-chapter-template .vet-chapter-item').clone();
var $list = $editor.find('.vet-chapters-list');
// 移除"暂无章节"提示
$list.find('.vet-no-chapters').remove();
// 添加到列表
$list.append($template);
// 编辑新章节
ChapterEditor.editChapter($template);
},
// 编辑章节
editChapter: function($chapterItem) {
var $preview = $chapterItem.find('.vet-chapter-preview');
var $timeSpan = $preview.find('.vet-chapter-time');
var $titleSpan = $preview.find('.vet-chapter-title');
var currentTime = $timeSpan.text();
var currentTitle = $titleSpan.text();
// 创建编辑表单
var $form = $('<div class="vet-chapter-edit-form"></div>');
$form.html(`
<div class="vet-edit-fields">
<div class="vet-field">
<label>时间戳 (秒):</label>
<input type="number" class="vet-time-input" step="0.1" min="0" value="${ChapterEditor.timeToSeconds(currentTime)}">
</div>
<div class="vet-field">
<label>章节标题:</label>
<input type="text" class="vet-title-input" value="${currentTitle}">
</div>
<div class="vet-edit-actions">
<button type="button" class="button button-primary vet-save-chapter">保存</button>
<button type="button" class="button vet-cancel-edit">取消</button>
</div>
</div>
`);
// 替换预览为编辑表单
$preview.hide();
$chapterItem.find('.vet-chapter-actions').hide();
$chapterItem.append($form);
// 绑定保存事件
$form.find('.vet-save-chapter').on('click', function() {
var timeInput = $form.find('.vet-time-input').val();
var titleInput = $form.find('.vet-title-input').val();
if (timeInput && titleInput) {
// 更新时间戳和标题
$chapterItem.find('input[name*="timestamp"]').val(timeInput);
$chapterItem.find('input[name*="title"]').val(titleInput);
// 更新预览显示
$timeSpan.text(ChapterEditor.secondsToTime(timeInput));
$titleSpan.text(titleInput);
}
// 恢复显示
$form.remove();
$preview.show();
$chapterItem.find('.vet-chapter-actions').show();
});
// 绑定取消事件
$form.find('.vet-cancel-edit').on('click', function() {
$form.remove();
$preview.show();
$chapterItem.find('.vet-chapter-actions').show();
});
},
// 删除章节
removeChapter: function($chapterItem) {
if (confirm('确定要删除这个章节吗?')) {
$chapterItem.remove();
// 如果没有章节了,显示提示
var $list = $chapterItem.closest('.vet-chapters-list');
if ($list.children('.vet-chapter-item').length === 0) {
$list.html('<p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p>');
}
}
},
// 显示当前时间
showCurrentTime: function($editor, currentTime) {
// 创建或更新时间提示
var $timeHint = $editor.find('.vet-current-time-hint');
if ($timeHint.length === 0) {
$timeHint = $('<div class="vet-current-time-hint"></div>');
$editor.find('.vet-chapter-header').after($timeHint);
}
var timeStr = ChapterEditor.secondsToTime(currentTime);
$timeHint.html('当前视频时间: <strong>' + timeStr + '</strong> (点击视频可获取当前时间)');
// 3秒后淡出
$timeHint.show();
setTimeout(function() {
$timeHint.fadeOut();
}, 3000);
},
// 更新章节顺序
updateChapterOrder: function($list) {
// 重新排序后,可以在这里添加额外的处理逻辑
console.log('章节顺序已更新');
},
// 时间字符串转秒数
timeToSeconds: function(timeStr) {
var parts = timeStr.split(':');
if (parts.length === 2) {
return parseInt(parts[0]) * 60 + parseInt(parts[1]);
}
return 0;
},
// 秒数转时间字符串
secondsToTime: function(seconds) {
var mins = Math.floor(seconds / 60);
var secs = Math.floor(seconds % 60);
return (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs;
}
};
// 初始化
$(document).ready(function() {
ChapterEditor.init();
});
})(jQuery);
/* assets/css/admin-styles.css */
/* 章节管理器样式 */
.vet-chapter-manager {
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
}
.vet-video-section {
margin-bottom: 30px;
padding: 20px;
background: white;
border: 1px solid #ddd;
border-radius: 5px;
}
.vet-video-section h3 {
margin-top: 0;
color: #23282d;
border-bottom: 2px solid #0073aa;
padding-bottom: 10px;
}
.vet-video-preview {
margin: 15px 0;
text-align: center;
}
.vet-video-preview video {
/* assets/css/admin-styles.css */
/* 章节管理器样式 */
.vet-chapter-manager {
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
}
.vet-video-section {
margin-bottom: 30px;
padding: 20px;
background: white;
border: 1px solid #ddd;
border-radius: 5px;
}
.vet-video-section h3 {
margin-top: 0;
color: #23282d;
border-bottom: 2px solid #0073aa;
padding-bottom: 10px;
}
.vet-video-preview {
margin: 15px 0;
text-align: center;
}
.vet-video-preview video {


