V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
xyxc0673
V2EX  ›  问与答

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

  •  
  •   xyxc0673 · 2023-07-12 14:49:12 +08:00 · 1181 次点击
    这是一个创建于 498 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在做一个需求,类似 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.
    15 条回复    2023-07-12 19:33:51 +08:00
    Satelli
        1
    Satelli  
       2023-07-12 14:56:01 +08:00
    /content/dam/oppo/product-asset-library/accessory/oppo-pad-2/cn/v1/assets/ksp.flv
    Rache1
        2
    Rache1  
       2023-07-12 15:01:40 +08:00
    看起来是用了 WebAssembly 对 flv 进行解码,然后画到 canvas 上的
    xyxc0673
        3
    xyxc0673  
    OP
       2023-07-12 15:03:32 +08:00
    @Satelli 看来还是读取了视频,然后画到 canvas 上面的
    xyxc0673
        4
    xyxc0673  
    OP
       2023-07-12 15:04:36 +08:00
    @Rache1 请教一下从哪里可以看出使用了 WebAssembly 呢
    SummerGua
        5
    SummerGua  
       2023-07-12 15:05:57 +08:00
    网络请求中是有该视频的
    名称:ksp.flv
    请求 URL: https://www.oppo.com/content/dam/oppo/product-asset-library/accessory/oppo-pad-2/cn/v1/assets/ksp.flv
    请求方法: GET
    Rache1
        6
    Rache1  
       2023-07-12 15:10:30 +08:00   ❤️ 3
    @xyxc0673

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



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



    ---

    🐶 我也不太确定,根据上下文猜的
    xyxc0673
        7
    xyxc0673  
    OP
       2023-07-12 15:23:44 +08:00
    @SummerGua
    @Rache1 #6

    赞,看到加载视频的这个代码了,确实是 wasm + canvas 实现,现在的问题变成了有没有类似的开源库可以用一用,不然就要从前端工程师变成 Rust 工程师了🐶
    Rache1
        8
    Rache1  
       2023-07-12 15:38:56 +08:00   ❤️ 1
    喂饭级教学 🐶

    ---









    anguiao
        9
    anguiao  
       2023-07-12 15:48:06 +08:00
    所以为什么不能用 video 标签🤔
    LinePro
        10
    LinePro  
       2023-07-12 16:27:22 +08:00   ❤️ 1
    > 至于为什么不使用 video 标签来自动播放是因为现代浏览器会对这样自动播放的行为进行拦截,报出这个错误:
    > Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.

    实际上目前版本的浏览器只会对有声音的视频自动播放按策略进行拦截。video 标签设置 muted 属性就不会拦截了。
    xyxc0673
        11
    xyxc0673  
    OP
       2023-07-12 18:23:09 +08:00
    @Rache1 #8 太感谢了,是我未曾设想的道路
    xyxc0673
        12
    xyxc0673  
    OP
       2023-07-12 18:25:10 +08:00
    @anguiao
    @LinePro

    我后来发现确实将视频静音之后就可以自动播放了,这个功能其实只是视频的预览,自动播放前 N 秒,点击自定义的播放按钮再弹窗播放,所以不希望用户能够控制这个预览,使用视频组件的话,可以通过右键控制视频。
    okakuyang
        13
    okakuyang  
       2023-07-12 18:49:07 +08:00 via iPhone
    自己解码效率并不高,没有特殊需求应该用 video
    Track13
        14
    Track13  
       2023-07-12 19:16:07 +08:00 via Android
    我是用 jsmpeg 做的,缺点就是 mp4 转 ts 格式后文件体积翻倍。
    FreeEx
        15
    FreeEx  
       2023-07-12 19:33:51 +08:00
    看了一下源码,播放的并不是视频,而是一个类似 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);
    }
    });

    });

    }
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3445 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 00:16 · PVG 08:16 · LAX 16:16 · JFK 19:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.