You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

页面刷新后嵌入式视频批量播放/暂停按钮失效及显示异常问题

问题描述

我开发了一个可展示多个YouTube视频的网站,想通过JavaScript实现批量播放、暂停所有视频的按钮功能。网站能正常运行,但存在两个问题:

  • 页面刷新后,点击「显示视频」按钮,前两次只会生成空div,第三次点击才会加载视频
  • 播放/暂停按钮只有在点击显示按钮后才会生效

原代码

$(document).ready(function() {
  let input = document.getElementById("video-url");
  let displayButton = document.getElementById("display-video");
  let containerDiv = document.getElementById("video-container");
  let stateList = [];
  let player;
  let players = [];


    // Load the YT library
    var tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    var firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

    displayButton.addEventListener("click", function() {
      let videoID = input.value.split("watch?v=")[1];
      let innerDiv = document.createElement("div");
      innerDiv.className = "inner";
      innerDiv.id = "video-" + players.length;
      let outerDiv = document.createElement("div");
      outerDiv.className = "window draggable resizable";
      outerDiv.style.transform = "translate3d(100px, 100px, 0)";
      outerDiv.appendChild(innerDiv);
      init();
      containerDiv.appendChild(outerDiv);
    
      player = new YT.Player("video-" + players.length, {
        height: "100%",
        width: "100%",
        videoId: videoID,
        events: {
          'onReady': onPlayerReady
        }
      });
      players.push(player);

      init();
      saveState();

    });
    
    function onPlayerReady(event) {
      let playButton = document.getElementById("play-button");
      playButton.addEventListener("click", function() {
        event.target.playVideo();
      });
      
      let pauseButton = document.getElementById("pause-button");
      pauseButton.addEventListener("click", function() {
        event.target.pauseVideo();
      });
  
      let stopButton = document.getElementById("stop-button");
      stopButton.addEventListener("click", function() {
        event.target.stopVideo();
      });
    }

  function init() {
    // the ui-resizable-handles are added here
    $('.resizable').resizable();
    // makes GSAP Draggable avoid clicks on the resize handles
    $('.ui-resizable-handle').attr('data-clickable', true);
    // make the element draggable
    Draggable.create('.draggable', {
      onPress: function () {
        $(this.target).addClass('ui-resizable-resizing');
      },
      onRelease: function() {
        $(this.target).removeClass('ui-resizable-resizing');
        saveState();
      }
    });
    $('.resizable').dblclick(function () {
      var win = $(this);
      toggleSize(win);
      saveState();
    });
  }
  
  function createHandleBar() {
    // create handlebar
    let handleBar = document.createElement('div');
    handleBar.className = 'handleBar';
    $(handleBar).css({
      position: 'absolute',
      top: '0px',
      width: '100%', height: '43px',
      padding: '4px',
      boxSizing: 'border-box'
    });
    saveState();
    return handleBar;
}

function saveState() {
  let state = {
    id: stateList.length,
    state: containerDiv.innerHTML
  };
  stateList.push(state);
  window.localStorage.setItem('videoState', JSON.stringify(stateList));
  window.history.pushState({}, '', '#' + btoa(JSON.stringify(stateList)));
}

if (window.location.hash) {
  let stateListFromStorage = JSON.parse(atob(window.location.hash.substring(1)));
  let stateToRestore = stateListFromStorage[stateListFromStorage.length - 1];
  containerDiv.innerHTML = stateToRestore.state;
  init();
}
});

问题原因

  1. YouTube API加载时机错误:
    在$(document).ready里直接插入API脚本,但脚本加载是异步的,刷新后第一次点击显示按钮时,YT对象可能还未初始化,导致创建播放器失败,视频无法渲染。

  2. 状态恢复逻辑缺失:
    页面刷新后从hash恢复DOM结构,但这些恢复的视频div没有重新创建播放器,players数组依然为空,新创建的视频ID用players.length作为后缀,会和恢复的div ID重复,导致播放器绑定失败。

  3. 批量按钮事件绑定时机错误:
    播放/暂停按钮的事件绑定写在onPlayerReady里,只有当第一个播放器初始化完成后,按钮才会有事件响应,且每次创建新播放器都会重复绑定事件,逻辑只能控制单个播放器,无法实现批量操作。


修复方案

核心修改点

1. 遵守YT API加载规范

使用API规定的全局回调onYouTubeIframeAPIReady,确保API加载完成后再执行播放器创建、事件绑定等逻辑。

2. 提前绑定批量控制按钮事件

直接在API加载完成后绑定全局播放/暂停/停止按钮的事件,遍历players数组实现批量操作,无需等待单个播放器ready。

3. 优化状态存储与恢复

修改saveState存储每个视频的ID、位置信息,刷新恢复时重新创建播放器并加入players数组,避免ID冲突。

4. 健壮的视频ID提取逻辑

支持标准链接和短链接(youtu.be),同时过滤链接中的额外参数。

修复后的完整代码

// 全局变量声明
let input, displayButton, containerDiv, stateList, players = [];

$(document).ready(function() {
  input = document.getElementById("video-url");
  displayButton = document.getElementById("display-video");
  containerDiv = document.getElementById("video-container");
  stateList = [];

  // 加载YouTube IFrame API
  const tag = document.createElement('script');
  tag.src = "https://www.youtube.com/iframe_api";
  const firstScriptTag = document.getElementsByTagName('script')[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

  // 页面刷新后恢复状态
  if (window.location.hash) {
    try {
      const stateListFromStorage = JSON.parse(atob(window.location.hash.substring(1)));
      const latestState = stateListFromStorage[stateListFromStorage.length - 1];
      containerDiv.innerHTML = latestState.containerHTML;
      stateList = stateListFromStorage;

      // 重新创建播放器
      latestState.videos.forEach((videoInfo, index) => {
        const player = new YT.Player(videoInfo.id, {
          height: "100%",
          width: "100%",
          videoId: videoInfo.videoId,
          events: {
            'onReady': onPlayerReady
          }
        });
        players.push(player);
        // 恢复窗口位置
        const windowEl = containerDiv.querySelectorAll('.window')[index];
        windowEl.style.transform = videoInfo.transform;
      });
      init();
    } catch (e) {
      console.error('状态恢复失败:', e);
    }
  }
});

// YouTube API加载完成后自动触发的全局回调
function onYouTubeIframeAPIReady() {
  // 绑定显示视频按钮事件
  displayButton.addEventListener("click", function() {
    const videoURL = input.value.trim();
    if (!videoURL) {
      alert('请输入有效的YouTube视频链接');
      return;
    }

    // 提取视频ID(兼容标准链接和短链接)
    let videoID;
    if (videoURL.includes("watch?v=")) {
      videoID = videoURL.split("watch?v=")[1].split('&')[0];
    } else if (videoURL.includes("youtu.be/")) {
      videoID = videoURL.split("youtu.be/")[1].split('?')[0];
    } else {
      alert('请输入有效的YouTube视频链接');
      return;
    }

    // 创建视频容器DOM
    const innerDiv = document.createElement("div");
    innerDiv.className = "inner";
    innerDiv.id = `video-${players.length}`;
    
    const outerDiv = document.createElement("div");
    outerDiv.className = "window draggable resizable";
    outerDiv.style.transform = "translate3d(100px, 100px, 0)";
    outerDiv.appendChild(innerDiv);
    containerDiv.appendChild(outerDiv);

    // 创建YouTube播放器
    const player = new YT.Player(`video-${players.length}`, {
      height: "100%",
      width: "100%",
      videoId: videoID,
      events: {
        'onReady': onPlayerReady
      }
    });
    players.push(player);

    init();
    saveState();
  });

  // 绑定批量控制按钮事件
  const playButton = document.getElementById("play-button");
  playButton.addEventListener("click", () => {
    players.forEach(player => {
      if (player?.playVideo) player.playVideo();
    });
  });

  const pauseButton = document.getElementById("pause-button");
  pauseButton.addEventListener("click", () => {
    players.forEach(player => {
      if (player?.pauseVideo) player.pauseVideo();
    });
  });

  const stopButton = document.getElementById("stop-button");
  stopButton.addEventListener("click", () => {
    players.forEach(player => {
      if (player?.stopVideo) player.stopVideo();
    });
  });
}

// 播放器就绪回调(可用于单个播放器的状态监听)
function onPlayerReady(event) {
  // 这里可以添加单个播放器的逻辑,比如自动播放等
}

// 初始化拖拽、缩放功能
function init() {
  $('.resizable').resizable();
  $('.ui-resizable-handle').attr('data-clickable', true);
  
  Draggable.create('.draggable', {
    onPress: function () {
      $(this.target).addClass('ui-resizable-resizing');
    },
    onRelease: function() {
      $(this.target).removeClass('ui-resizable-resizing');
      saveState();
    }
  });
  
  $('.resizable').dblclick(function () {
    const win = $(this);
    toggleSize(win);
    saveState();
  });
}

// 创建标题栏(原逻辑保留)
function createHandleBar() {
  const handleBar = document.createElement('div');
  handleBar.className = 'handleBar';
  $(handleBar).css({
    position: 'absolute',
    top: '0px',
    width: '100%', 
    height: '43px',
    padding: '4px',
    boxSizing: 'border-box'
  });
  saveState();
  return handleBar;
}

// 保存状态(新增视频信息存储)
function saveState() {
  const videosInfo = [];
  containerDiv.querySelectorAll('.window').forEach((win, index) => {
    const innerDiv = win.querySelector('.inner');
    videosInfo.push({
      videoId: players[index].getVideoData().video_id,
      id: innerDiv.id,
      transform: win.style.transform
    });
  });

  const state = {
    id: stateList.length,
    videos: videosInfo,
    containerHTML: containerDiv.innerHTML
  };
  stateList.push(state);
  
  window.localStorage.setItem('videoState', JSON.stringify(stateList));
  window.history.pushState({}, '', `#${btoa(JSON.stringify(stateList))}`);
}

内容的提问来源于stack exchange,提问作者Ryan Vaughan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:10:41