如何实现 OPPO 平板官网视频自动播放的效果?前提是不使用 video 标签来实现

303 天前
 xyxc0673
最近在做一个需求,类似 OPPO 平板官网( https://www.oppo.com/cn/accessories/oppo-pad-2/ )的这个效果,我看它是使用 canvas 去实现的,然后在网络请求里也没有看到这个视频,我问 GPT 给到的回复都是在 video 标签播放时将视频帧画到 canvas 上,这样一来不就会下载这个视频?

要如何做到不下载视频的情况下去画视频帧呢?

至于为什么不使用 video 标签来自动播放是因为现代浏览器会对这样自动播放的行为进行拦截,报出这个错误:
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.
961 次点击
所在节点    问与答
15 条回复
Satelli
303 天前
/content/dam/oppo/product-asset-library/accessory/oppo-pad-2/cn/v1/assets/ksp.flv
Rache1
303 天前
看起来是用了 WebAssembly 对 flv 进行解码,然后画到 canvas 上的
xyxc0673
303 天前
@Satelli 看来还是读取了视频,然后画到 canvas 上面的
xyxc0673
303 天前
@Rache1 请教一下从哪里可以看出使用了 WebAssembly 呢
SummerGua
303 天前
网络请求中是有该视频的
名称:ksp.flv
请求 URL: https://www.oppo.com/content/dam/oppo/product-asset-library/accessory/oppo-pad-2/cn/v1/assets/ksp.flv
请求方法: GET
Rache1
303 天前
@xyxc0673

从 Network 看到 这个 flv 的请求记录,的发起者,然后看到这个 getPlayer



点进去往下看一点儿,就有加载了个 wasm 的 video decode 插件



---

🐶 我也不太确定,根据上下文猜的
xyxc0673
303 天前
@SummerGua
@Rache1 #6

赞,看到加载视频的这个代码了,确实是 wasm + canvas 实现,现在的问题变成了有没有类似的开源库可以用一用,不然就要从前端工程师变成 Rust 工程师了🐶
Rache1
303 天前
喂饭级教学 🐶

---









anguiao
303 天前
所以为什么不能用 video 标签🤔
LinePro
303 天前
> 至于为什么不使用 video 标签来自动播放是因为现代浏览器会对这样自动播放的行为进行拦截,报出这个错误:
> Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.

实际上目前版本的浏览器只会对有声音的视频自动播放按策略进行拦截。video 标签设置 muted 属性就不会拦截了。
xyxc0673
302 天前
@Rache1 #8 太感谢了,是我未曾设想的道路
xyxc0673
302 天前
@anguiao
@LinePro

我后来发现确实将视频静音之后就可以自动播放了,这个功能其实只是视频的预览,自动播放前 N 秒,点击自定义的播放按钮再弹窗播放,所以不希望用户能够控制这个预览,使用视频组件的话,可以通过右键控制视频。
okakuyang
302 天前
自己解码效率并不高,没有特殊需求应该用 video
Track13
302 天前
我是用 jsmpeg 做的,缺点就是 mp4 转 ts 格式后文件体积翻倍。
FreeEx
302 天前
看了一下源码,播放的并不是视频,而是一个类似 GIF 的东西。因为后缀是 mp4 的时候调用的是 remove ,反之将 canvas 传入了一个对象中。

让 AI 反混淆后的源码如下:
```
function getPlayer(element, container, options, autoplay = false, placeholder, startTime) {

return new Promise(resolve => {

let compatibilityLevel;

const canvas = container.querySelector('canvas');
const img = container.querySelector('img');
const video = container.querySelector('video');

if (!this.isSupported || placeholder || (compatibilityLevel = this.app.plgs.fps?.compatLevel) > 0) {
// 如果不支持或需要占位图,则删除 video 和 canvas 元素
canvas.remove();
video?.remove();

resolve(new Player(null));
return;
}

Promise.all([
new Promise(resolve => {
// 检查兼容性
const level = this.app.plgs.fps?.compatLevel;
if (level === undefined) {
resolve(0);
} else if (level > -1) {
resolve(level);
}

// 监听兼容性变化
window.addEventListener(COMPAT_EVENT, ({detail: {level}}) => {
resolve(level);
});
}),
new Promise(resolve => this.onReady(() => resolve()))
]).then(() => {

let src = this.app.isPc() ? element.dataSrcPc : element.dataSrcMo || element.dataSrc;

if (this.app.isPad() && element.dataSrcPad) {
src = element.dataSrcPad;
}

if (!src) {
throw new Error('Video source not specified');
}

if (src.endsWith('mp4')) {
// MP4 视频

if (条件 1 && 条件 2) {
// 不支持,删除元素
canvas.remove();
video?.remove();
resolve(new Player(null));
return;
}

// 删除图片占位符
img.remove();

// 初始化视频
resolve(new Player(video));
video && this.initVideoWithOptions(video, container, src, options);
return;

} else {
// GIF animation

video?.remove();

const player = new GifPlayer(src, this.manager, canvas, options);

if (!autoplay && !startTime) {
// 显示占位图像
img.remove();
} else if (autoplay) {
// 自动播放时删除占位图
player.onFirstFrame(() => {
img.remove();
});
} else if (startTime) {
// 指定 startTime 时删除占位图并 seek 到指定时间
img.remove();
player.seek(startTime);
}

this.players.push(player);
resolve(player);
}
});

});

}
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/956164

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX