简介:本资源是一份面向前端初学者与HTML5进阶开发者的教学课件,聚焦HTML5中video和audio元素的核心交互能力,系统讲解多媒体控制方法与事件机制,解决网页中音视频动态控制、状态监听与兼容性处理等实际问题。课件以PPTX格式呈现,共1个文件,大小195KB,内容结构清晰,涵盖load()、play()、pause()、canPlayType()等关键方法的用途与调用时机,以及play、playing、ended、timeupdate、canplaythrough等20余个常用事件的触发条件与典型应用场景,并辅以表格对比与使用说明,便于理解记忆与代码实践。目前已有377人学习下载,适合用于课堂讲授、自学梳理或项目开发中快速查阅音视频API细节,帮助开发者构建响应及时、体验流畅的多媒体网页功能。

1. 不再依赖 Flash:HTML5 原生音视频控制已成网页标配,但多数人只写了 <video> 标签就交作业

你写过 <video src="demo.mp4"></video> ,页面能播,作业得了分——但这只是 HTML5 音视频能力的冰山一角。真正决定用户体验的是:如何让视频支持倍速播放、如何监听播放暂停状态变化、怎样在音频加载失败时优雅降级、为什么 timeupdate 事件每秒触发几十次却总拿不到精确时间戳、CSS3 如何配合实现鼠标悬停即显示控制条的交互效果。这些不是“高级技巧”,而是现代网页音视频功能的基线要求。本文面向刚学完 HTML5 基础标签、正准备做课程设计或企业前端实习的开发者,不讲理论推导,只拆解真实项目中必须落地的 5 类核心操作:媒体资源加载控制、播放状态监听、时间轴精准响应、CSS3 驱动的视觉反馈、以及跨浏览器兼容性兜底策略。所有代码均可直接粘贴运行,参数含义逐项说明,错误日志对应到具体 DOM API 行为。

2. 用 <video> 和 <audio> 标签加载媒体资源:不只是加 src ,还要处理加载失败与格式兼容

HTML5 原生音视频能力的核心载体是 <video> 和 <audio> 元素。但仅设置 src 属性远不足以应对实际场景:不同浏览器对编码格式支持差异显著(如 Safari 不支持 WebM VP9,Firefox 对 HEVC 支持有限),网络波动会导致加载中断,移动端自动播放策略更严格。因此,必须主动控制加载流程并提供 fallback 路径。

2.1 使用 source 元素声明多格式备选,避免单 src 失败即黑屏

直接写 <video src="movie.webm"> 在 Chrome 可能正常,但在 iOS Safari 上必然失败——因为 Safari 仅原生支持 H.264 编码的 MP4 容器。正确做法是用 <source> 显式声明多种格式,浏览器按顺序尝试加载首个支持的格式:

<video controls width="640" height="360" poster="preview.jpg">
  <source src="movie.mp4" type="video/mp4">
  <source src="movie.webm" type="video/webm">
  <source src="movie.ogv" type="video/ogg">
  您的浏览器不支持 video 标签,请升级或更换浏览器。
</video>

提示 : type 属性必须准确填写 MIME 类型,不能省略。常见类型包括 video/mp4 (H.264)、 video/webm (VP8/VP9)、 video/ogg (Theora);音频对应 audio/mpeg 、 audio/ogg 、 audio/wav 。错误的 type 值会导致浏览器跳过该 source。

2.2 监听 error 事件捕获加载失败,并动态插入降级方案

即使提供了多格式,仍可能因网络中断、文件损坏或 CORS 限制导致加载失败。此时需监听 error 事件,在控制台记录错误详情并触发 UI 提示:

const videoEl = document.querySelector('video');
videoEl.addEventListener('error', (e) => {
  console.error('视频加载失败,错误类型:', e.target.error.code);
  // error.code 值说明:
  // 1 → MEDIA_ERR_ABORTED(用户中止)
  // 2 → MEDIA_ERR_NETWORK(网络错误)
  // 3 → MEDIA_ERR_DECODE(解码失败)
  // 4 → MEDIA_ERR_SRC_NOT_SUPPORTED(格式不支持)
  
  if (e.target.error.code === 4) {
    // 格式不支持:可切换为 GIF 动画或静态图
    const posterImg = videoEl.getAttribute('poster');
    if (posterImg) {
      videoEl.outerHTML = `<img src="${posterImg}" alt="视频封面图" style="max-width:100%;">`;
    }
  } else if (e.target.error.code === 2) {
    // 网络错误:显示重试按钮
    const retryBtn = document.createElement('button');
    retryBtn.textContent = '重新加载';
    retryBtn.onclick = () => videoEl.load();
    videoEl.parentNode.insertBefore(retryBtn, videoEl.nextSibling);
  }
});
2.2.1 关键参数说明: error.code 与 networkState

HTMLMediaElement.error 对象包含 code (错误类型编号)和 message (可选描述)。但更可靠的判断依据是 networkState 属性值:

  • 0 (NETWORK_EMPTY):尚未初始化
  • 1 (NETWORK_IDLE):空闲,未加载
  • 2 (NETWORK_LOADING):正在加载
  • 3 (NETWORK_NO_SOURCE):无可用源(即所有 <source> 均失败)

可在 loadedmetadata 事件后检查 networkState 是否为 2 或 3 ,提前预判失败风险。

2.3 强制触发加载与预加载策略: preload 属性的实际效果

preload 属性控制浏览器是否预先加载媒体数据,取值有三个:

  • none :不预加载(节省流量,适合大视频或用户可能不播放的场景)
  • metadata :仅加载元数据(时长、尺寸、封面图),默认值
  • auto :尽可能预加载全部内容(但浏览器可忽略此建议)

实测发现:在移动 Safari 中, preload="auto" 会被强制降级为 metadata ;Chrome 桌面版对 auto 执行较积极,但若页面可见区域外则延迟加载。因此,若需确保首帧快速呈现,应在 JS 中显式调用 load() 方法:

// 页面加载完成后立即触发加载(绕过 preload 限制)
window.addEventListener('DOMContentLoaded', () => {
  const videoEl = document.querySelector('video');
  // 检查是否已处于加载中状态
  if (videoEl.networkState === 0 || videoEl.networkState === 1) {
    videoEl.load(); // 触发加载流程
  }
});

3. 监听播放状态变化事件:从 play 到 waiting ,掌握 7 个关键事件的触发时机与用途

HTML5 媒体元素提供 12 个标准事件,但日常开发中高频使用的是其中 7 个。它们并非孤立存在,而是构成一条清晰的状态流转链: loadstart → loadedmetadata → loadeddata → canplay → canplaythrough → play → pause → waiting → playing → ended → error 。理解每个事件的精确触发条件,才能写出稳定可靠的控制逻辑。

3.1 loadedmetadata 与 canplay 的本质区别:何时能安全读取时长?

初学者常混淆这两个事件: loadedmetadata 表示元数据(时长、宽高、帧率)已加载完成,此时 duration 属性可读; canplay 表示当前缓冲区足以开始播放(但不一定能播完)。关键差异在于:

事件 触发条件 duration 是否可用 currentTime 是否可设
loadedmetadata 视频头信息解析完毕 ✅ 是(值为 NaN 表示未知) ✅ 是(可设为任意值,但可能无效)
canplay 缓冲区有足够数据解码首帧 ✅ 是(已确定) ✅ 是(设后立即生效)

因此,若需在播放前显示总时长,应在 loadedmetadata 中读取;若需在用户点击播放按钮后立即跳转到某时间点,则应在 canplay 后执行 currentTime = x 。

const videoEl = document.querySelector('video');

videoEl.addEventListener('loadedmetadata', () => {
  console.log('视频元数据加载完成,总时长:', videoEl.duration); // 单位:秒
  // 此处可初始化进度条最大值
  const durationEl = document.getElementById('duration');
  if (!isNaN(videoEl.duration)) {
    durationEl.textContent = formatTime(videoEl.duration);
  }
});

videoEl.addEventListener('canplay', () => {
  console.log('已缓冲足够数据,可开始播放');
  // 此处可执行自动播放(需用户手势触发后才有效)
  // videoEl.play().catch(e => console.warn('自动播放被阻止'));
});

3.2 timeupdate 事件的性能陷阱:为什么它每秒触发 25~50 次?

timeupdate 是最常用也最容易误用的事件——它在播放过程中持续触发,频率由浏览器决定(通常每秒 25~50 次),目的是反映当前播放位置。但直接在此事件中更新 DOM 会导致严重性能问题:

// ❌ 错误示范:每次触发都更新 DOM
videoEl.addEventListener('timeupdate', () => {
  currentTimeEl.textContent = formatTime(videoEl.currentTime);
  progressBar.value = videoEl.currentTime;
});

正确做法是使用 requestAnimationFrame 节流,或利用 setTimeout 控制更新频率:

// ✅ 正确:限制每秒最多更新 4 次(250ms 间隔)
let lastUpdateTime = 0;
videoEl.addEventListener('timeupdate', () => {
  const now = Date.now();
  if (now - lastUpdateTime > 250) {
    currentTimeEl.textContent = formatTime(videoEl.currentTime);
    progressBar.value = videoEl.currentTime;
    lastUpdateTime = now;
  }
});
3.2.1 timeupdate 的替代方案: seeking 与 seeked 事件精准捕获跳转

当用户拖动进度条时, timeupdate 会密集触发,但无法区分是自然播放还是手动跳转。此时应监听 seeking (开始跳转)和 seeked (跳转完成)事件:

videoEl.addEventListener('seeking', () => {
  console.log('用户正在拖动进度条,当前 seeking 状态:', videoEl.seeking);
  // 可在此隐藏播放按钮,显示加载指示器
  loadingIndicator.style.display = 'block';
});

videoEl.addEventListener('seeked', () => {
  console.log('跳转完成,当前时间:', videoEl.currentTime);
  loadingIndicator.style.display = 'none';
  // 此时可安全更新 UI,因 currentTime 已稳定
});

3.3 waiting 与 playing 的协同:识别卡顿并提示用户

waiting 事件在缓冲不足时触发(如网速下降), playing 在恢复播放时触发。二者组合可用于检测播放卡顿:

let waitingStart = 0;
videoEl.addEventListener('waiting', () => {
  waitingStart = Date.now();
  console.log('开始等待缓冲...');
});

videoEl.addEventListener('playing', () => {
  if (waitingStart > 0) {
    const waitDuration = Date.now() - waitingStart;
    console.log(`本次等待耗时:${waitDuration}ms`);
    if (waitDuration > 2000) {
      // 超过 2 秒,提示用户网络不佳
      showNetworkWarning();
    }
    waitingStart = 0;
  }
});

4. CSS3 驱动的交互反馈:用 :hover 、 transition 和 @keyframes 实现专业级控制条动画

HTML5 媒体控件默认样式简陋且不可定制。通过 CSS3 的伪类、过渡与动画,可构建符合现代设计规范的悬浮控制条:鼠标移入时平滑展开,播放中保持可见,暂停时淡出。这不仅提升体验,更是前端工程师展示 CSS 功底的关键场景。

4.1 用 :hover + transition 实现控制条渐显,避免突兀弹出

默认隐藏控制条,仅在用户交互时显示。关键在于利用 :hover 触发 opacity 和 transform 变化,并通过 transition 添加缓动效果:

.video-container {
  position: relative;
  display: inline-block;
}

.video-controls {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.7);
  padding: 12px;
  opacity: 0;
  transform: translateY(20px);
  transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  /* 使用贝塞尔曲线使动画更自然 */
}

.video-container:hover .video-controls,
.video-container:focus-within .video-controls {
  opacity: 1;
  transform: translateY(0);
}

/* 播放中强制显示(避免鼠标移开后控制条消失) */
.video-playing .video-controls {
  opacity: 1 !important;
  transform: translateY(0) !important;
}

注意 : focus-within 用于键盘操作场景(如 Tab 切换到视频后按空格播放),确保无障碍访问。 .video-playing 类需通过 JS 动态添加/移除。

4.2 @keyframes 实现播放按钮脉冲动画,强化视觉反馈

当用户点击播放按钮时,添加微小缩放动画可提升操作确认感。使用 @keyframes 定义动画序列,并绑定到按钮的 :active 状态:

@keyframes pulsePlay {
  0% { transform: scale(1); }
  50% { transform: scale(0.95); }
  100% { transform: scale(1); }
}

.play-btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #ff5252;
  border: none;
  cursor: pointer;
  transition: background 0.2s;
}

.play-btn:active {
  animation: pulsePlay 0.2s ease-out;
}

.play-btn:hover {
  background: #ff3333;
}
4.2.1 CSS3 transform 与 transition 的性能优化要点
  • 优先使用 transform ( translate / scale / rotate )和 opacity 进行动画,它们触发 GPU 加速,避免重排(reflow)
  • 避免对 width / height / top / left 等属性做 transition ,会强制浏览器计算布局
  • 设置 will-change: transform 可提前告知浏览器该元素将动画,但仅在必要时使用(过度使用反而降低性能)
.video-controls {
  will-change: transform; /* 仅对频繁动画的元素启用 */
}

4.3 响应式控制条布局:用 Flexbox 实现不同屏幕下的自适应排列

控制条内包含播放/暂停、进度条、音量、全屏等按钮,在小屏设备上需折叠次要功能。Flexbox 是最佳选择:

.video-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.video-controls .main-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

.video-controls .secondary-group {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* 小屏下隐藏音量和全屏按钮,仅保留核心控件 */
@media (max-width: 768px) {
  .video-controls .secondary-group {
    display: none;
  }
  .video-controls .main-group {
    gap: 8px;
  }
}

5. 实战:构建一个支持倍速播放、静音切换与进度跳转的自定义播放器

将前述知识点整合为一个可运行的最小自定义播放器。它包含:倍速选择下拉框、静音按钮、可拖拽进度条、实时时间显示。所有功能均基于原生 HTML5 API,无需第三方库。

5.1 HTML 结构:语义化标签与 ARIA 属性保障可访问性

<div class="video-player" tabindex="0">
  <video id="main-video" controls preload="metadata" aria-label="教学视频播放器">
    <source src="lesson.mp4" type="video/mp4">
    <source src="lesson.webm" type="video/webm">
  </video>
  
  <div class="video-controls">
    <div class="control-group">
      <button id="play-btn" aria-label="播放/暂停" title="播放/暂停">
        <svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
      </button>
      <div class="progress-container">
        <input type="range" id="progress-bar" min="0" max="100" value="0" aria-label="播放进度">
        <div class="progress-buffered"></div>
        <div class="progress-played"></div>
      </div>
      <span id="current-time">00:00</span>
      <span>/</span>
      <span id="duration">--:--</span>
    </div>
    
    <div class="control-group">
      <button id="mute-btn" aria-label="静音/取消静音" title="静音/取消静音">
        <svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.77s-2.11 5.91-5 6.77v2.06c3.39-.79 6-3.57 6-6.77s-2.61-5.98-6-6.77z"/></svg>
      </button>
      <select id="speed-select" aria-label="播放速度">
        <option value="0.5">0.5x</option>
        <option value="0.75">0.75x</option>
        <option value="1" selected>1x</option>
        <option value="1.25">1.25x</option>
        <option value="1.5">1.5x</option>
        <option value="2">2x</option>
      </select>
      <button id="fullscreen-btn" aria-label="全屏/退出全屏" title="全屏/退出全屏">
        <svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-2zm-2-4h2v2h3v2H5v-4zm12 7h-3v2h3v-2zm-3-5v2h3v2h-3z"/></svg>
      </button>
    </div>
  </div>
</div>

5.2 JavaScript 核心逻辑:事件绑定与状态同步

const video = document.getElementById('main-video');
const playBtn = document.getElementById('play-btn');
const muteBtn = document.getElementById('mute-btn');
const speedSelect = document.getElementById('speed-select');
const progressBar = document.getElementById('progress-bar');
const currentTimeEl = document.getElementById('current-time');
const durationEl = document.getElementById('duration');
const videoPlayer = document.querySelector('.video-player');

// 初始化:设置默认倍速
video.playbackRate = 1;

// 播放/暂停控制
playBtn.addEventListener('click', () => {
  if (video.paused) {
    video.play().catch(e => {
      console.warn('播放被阻止,请确保用户已与页面交互', e);
    });
  } else {
    video.pause();
  }
});

// 倍速切换
speedSelect.addEventListener('change', (e) => {
  video.playbackRate = parseFloat(e.target.value);
});

// 静音切换
muteBtn.addEventListener('click', () => {
  video.muted = !video.muted;
  muteBtn.innerHTML = video.muted 
    ? '<svg viewBox="0 0 24 24"><path d="M16.5 6c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.77s-2.11 5.91-5 6.77v2.06c3.39-.79 6-3.57 6-6.77s-2.61-5.98-6-6.77z"/></svg>'
    : '<svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.77s-2.11 5.91-5 6.77v2.06c3.39-.79 6-3.57 6-6.77s-2.61-5.98-6-6.77z"/></svg>';
});

// 进度条拖拽
progressBar.addEventListener('input', () => {
  const newTime = (progressBar.value / 100) * video.duration;
  video.currentTime = newTime;
});

// 时间更新(节流版)
let lastTimeUpdate = 0;
video.addEventListener('timeupdate', () => {
  const now = Date.now();
  if (now - lastTimeUpdate > 250) {
    currentTimeEl.textContent = formatTime(video.currentTime);
    progressBar.value = (video.currentTime / video.duration) * 100;
    lastTimeUpdate = now;
  }
});

// 缓冲进度更新
video.addEventListener('progress', () => {
  const buffered = video.buffered;
  if (buffered.length > 0) {
    const endBuffered = buffered.end(buffered.length - 1);
    const bufferedPercent = (endBuffered / video.duration) * 100;
    document.querySelector('.progress-buffered').style.width = `${bufferedPercent}%`;
  }
});

// 全屏切换
fullscreenBtn.addEventListener('click', () => {
  if (!document.fullscreenElement) {
    videoPlayer.requestFullscreen().catch(e => {
      console.error('全屏请求失败', e);
    });
  } else {
    document.exitFullscreen();
  }
});

// 播放状态同步 UI
video.addEventListener('play', () => {
  videoPlayer.classList.add('video-playing');
  playBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8 0h4V5h-4z"/></svg>';
});

video.addEventListener('pause', () => {
  videoPlayer.classList.remove('video-playing');
  playBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>';
});

// 格式化时间(秒 → mm:ss)
function formatTime(seconds) {
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
}

5.3 关键参数表:HTML5 媒体 API 核心属性与方法速查

属性/方法 类型 说明 常见取值/用法
playbackRate number 播放速率 0.5 , 1 , 1.25 , 2
muted boolean 是否静音 true / false
volume number 音量(0~1) 0.8
currentTime number 当前播放时间(秒) video.currentTime = 30
duration number 总时长(秒), NaN 表示未知 if (!isNaN(video.duration)) {...}
paused boolean 是否暂停 if (video.paused) {...}
ended boolean 是否播放结束 video.ended
play() method 播放 video.play().catch(...)
pause() method 暂停 video.pause()
load() method 重新加载 video.load()
canPlayType(type) method 检查格式支持 video.canPlayType('video/mp4')

6. 排查常见故障:从 MEDIA_ERR_DECODE 到 NotAllowedError ,定位 5 类典型错误

实际部署中,音视频功能失效往往不是代码错误,而是环境或配置问题。以下是最常遇到的 5 类错误及其定位路径,每类均附带终端命令验证与修复方案。

6.1 MEDIA_ERR_DECODE (3) :解码失败的 3 种根源与验证命令

该错误表示浏览器成功下载了文件,但无法解码。常见原因及验证方式:

原因 验证命令 修复方案
编码格式不匹配 (如 MP4 使用 HEVC 编码,但浏览器不支持) ffprobe -v quiet -show_entries stream=codec_name,width,height -of default video.mp4 用 FFmpeg 转码为 H.264: ffmpeg -i input.mp4 -c:v libx264 -c:a aac output.mp4
容器损坏 (文件头异常) file video.mp4 (检查是否显示 ISO Media, MP4 v2 ) 重新导出或用 ffmpeg -i broken.mp4 -c copy -movflags +faststart fixed.mp4 修复
CORS 限制 (跨域资源未开启 Access-Control-Allow-Origin ) curl -I https://example.com/video.mp4 | grep "Access-Control" 在服务器配置响应头: Access-Control-Allow-Origin: * 或指定域名

提示 :Chrome 开发者工具 Network 标签页中,右键媒体文件 → “Open in new tab”,若直接下载或显示乱码,说明服务端未正确设置 Content-Type 响应头(应为 video/mp4 )。

6.2 NotAllowedError :自动播放被阻止的 2 种绕过策略

现代浏览器禁止无用户交互的自动播放(尤其含音频)。错误信息类似 play() failed because the user didn't interact with the document first 。

策略一:延迟触发(推荐)
在 DOMContentLoaded 后等待用户首次点击,再启用自动播放:

let userInteracted = false;
document.addEventListener('click', () => { userInteracted = true; }, { once: true });
document.addEventListener('touchstart', () => { userInteracted = true; }, { once: true });

// 用户交互后,可安全调用 play()
if (userInteracted) {
  video.play();
}

策略二:静音自动播放(兼容性更好)
设置 muted 属性后,大多数浏览器允许自动播放:

<video muted autoplay playsinline>
  <source src="intro.mp4" type="video/mp4">
</video>

注意 : playsinline 属性对 iOS Safari 必需,否则视频会在全屏播放器中打开。

6.3 timeupdate 不触发:检查 preload 与 networkState 的组合状态

若 timeupdate 事件完全不触发,先检查 video.networkState :

  • 若为 0 (NETWORK_EMPTY): src 未设置或为空字符串
  • 若为 1 (NETWORK_IDLE): preload="none" 且未手动调用 load()
  • 若为 3 (NETWORK_NO_SOURCE):所有 <source> 均不被支持

验证步骤:

  1. 打开控制台,输入 video.networkState 查看值
  2. 输入 video.error?.code 确认是否有隐藏错误
  3. 输入 video.canPlayType('video/mp4') 检查格式支持

6.4 CSS 控制条不显示:排查 pointer-events 与 z-index 层级

当自定义控制条不可见或无法点击:

  • 检查父容器是否设置了 pointer-events: none (常见于轮播图组件)
  • 检查 z-index 是否被其他元素覆盖(用开发者工具的 Layers 面板查看渲染层)
  • 检查 video 元素是否设置了 controls 属性——它会覆盖自定义控件,必须移除

6.5 倍速播放无效:确认 playbackRate 的浏览器支持范围

playbackRate 在部分旧版 Android 浏览器中受限。验证支持性:

if ('playbackRate' in HTMLMediaElement.prototype) {
  console.log('支持倍速播放');
} else {
  console.log('不支持倍速播放,需降级为普通播放');
}

各浏览器支持情况:

  • Chrome ≥ 12:支持 0.01~16.0
  • Firefox ≥ 15:支持 0.25~4.0
  • Safari ≥ 10:支持 0.5~2.0(iOS Safari 仅支持 0.5~2.0)

若需超出范围,必须使用 WebAssembly 解码器(如 FFmpeg.wasm),但会显著增加包体积与内存占用。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

邀请您加入社区

更多推荐