V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
KuAoaoaoao
V2EX  ›  Java

给女朋友做的视频播放平台,播放视频很卡,有几张方案解决

  •  2
     
  •   KuAoaoaoao · 219 天前 · 4219 次点击
    这是一个创建于 219 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1.首先,介绍场景:视频播放平台,系统使用 Springboot 和 Vue 编写。拥有前台和后台,前台进行视频播放,后台进行视频的上传,也可以上传音乐和图片,功能顺手都做了。

    2.问题:浏览器播放视频卡顿。视频卡顿问题需要攻克 2 关。

    3.解决过程: 1 )第一关:视频文件太大,浏览器加载时间长。采用后端对文件分块读取。 场景:编写完成文件的上传与下载接口,在浏览器使用<video>组件绑定 url 进行观看视频。按照以上步骤,浏览器是能正常播放视频的,但是我把项目发布到服务器后,就出现另一个情况了。我发现浏览器会一直加载视频,浏览器中心呈现转圈动画,进度条会一点一点增长,但是没有画面出现。直到进度条加载完,才会出现画面。 原因:后端的下载接口是把整个视频文件一下子发送到浏览器,所以浏览器一直在接收文件,接收完文件后,浏览器的 video 组件才能进行播放。 解决:后端接口使用 randomAccessFile 类读取文件,这个类取到 file 的信息后,你便可以设置从文件的哪个位置开始读取,读取多少字节,然后把数据响应到浏览器。 这样就解决了浏览器一直加载视频的问题。例子代码在末尾。 2 )第二关:服务器带宽太低,视频下载赶不上视频播放,造成视频播放卡顿。采用 ffmpeg 组件进行画质压缩。 场景:假如有一个 30 秒 90M 的视频(我手机录的),上传到服务器了,然后在浏览器进行播放,那么播放视频就会卡顿了。 原因:因为服务器的带宽是 1M/s ,每秒能传送 1M 文件到浏览器,但是浏览器要想流畅播放那个视频,浏览器需要每秒接收 3M 文件,90M 的视频,30 秒,每秒需要播放 3M ,所以这就造成视频播放卡顿了。 解决:使用 ffmepg 组件,把 90M 的视频压缩到 30M ,可以压缩视频的比率和分辨率,最好进行相同比例的压缩,不然画质会变糊。

    代码: 1 )第一关: /** * 斷點播放 * @param request * @param response * @throws IOException */ public void play(HttpServletRequest request, HttpServletResponse response) throws IOException{ response.reset(); File file = new File("本地的一個視頻地址"); long fileLength = file.length(); // 随机读文件 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");

        //获取从那个字节开始读取文件
        String rangeString = request.getHeader("Range");
        long range=0;
        if (StrUtil.isNotBlank(rangeString)) {
            range = Long.valueOf(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-")));
        }
        //获取响应的输出流
        OutputStream outputStream = response.getOutputStream();
        //设置内容类型
        response.setHeader("Content-Type", "video/mp4");
        //返回码需要为 206 ,代表只处理了部分请求,响应了部分数据
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        // 移动访问指针到指定位置
        randomAccessFile.seek(range);
        // 每次请求只返回 1MB 的视频流
        byte[] bytes = new byte[1024 * 1024];
        int len = randomAccessFile.read(bytes);
        //设置此次相应返回的数据长度
        response.setContentLength(len);
        //设置此次相应返回的数据范围
        response.setHeader("Content-Range", "bytes "+range+"-"+(fileLength-1)+"/"+fileLength);
        // 将这 1MB 的视频流响应给客户端
        outputStream.write(bytes, 0, len);
        outputStream.close();
        randomAccessFile.close();
    

    // System.out.println("返回数据区间: ["+range+"-"+(range+len)+"] "); }

    2 )第二关: ffmepg 依赖 <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.3</version> </dependency> 代码: /**

    • 修改视频分辨率
    • @param imagePath 原视频地址
    • @param outputDir 输出目录
    • @param width 宽度
    • @param height 高度
    • @param bitRate 码率
    • @return 视频地址
    • @throws Exception 异常 / public static String modifyResolution( String imagePath, String outputDir, Integer width, Integer height, Integer bitRate) throws Exception { if (bitRate == null) { bitRate = 2000; } List<String> paths = Splitter.on(".").splitToList(imagePath); String ext = paths.get(paths.size() - 1); if (!Arrays.asList("mp4").contains(ext)) { throw new Exception("format error"); } String resultPath = Joiner.on(File.separator).join(Arrays.asList(outputDir, IdUtil.simpleUUID() + "." + ext)); String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class); ProcessBuilder builder = new ProcessBuilder( ffmpeg, "-i", imagePath, "-s", MessageFormat.format("{0}{1}", String.valueOf(width), String.valueOf(height)), "-b", MessageFormat.format("{0}k", String.valueOf(bitRate)), resultPath); builder.inheritIO().start().waitFor(); return resultPath; }
    48 条回复    2023-09-23 22:00:30 +08:00
    fengci
        1
    fengci  
       219 天前   ❤️ 1
    视频切片。m3u8 ,然后你就不需要做其他任何处理了。
    flyqie
        2
    flyqie  
       219 天前 via Android   ❤️ 1
    @fengci #1

    是的,楼主这个需求可以转画质后通过 m3u8 实现。。。
    kiduu
        3
    kiduu  
       219 天前   ❤️ 1
    判断一下视频时长,1 分钟以内的 MP4 直链,中长视频直接无脑切片就行。
    Elissa
        4
    Elissa  
       219 天前
    要不试试用 cdn 顶上?感觉是服务器带宽不够
    chaoschick
        5
    chaoschick  
       219 天前 via Android   ❤️ 1
    如果使用 video 标签绑定 url 播放可以试试 videojs 这个库,但是这个有点问题就是视频不能过大 谷歌浏览器好像是不能超过 150MB
    JensenQian
        6
    JensenQian  
       219 天前   ❤️ 2
    直接 emby ,plex
    chaoschick
        7
    chaoschick  
       219 天前 via Android
    前端流式播放视频的技术 感觉还不太成熟,还没有统一,PC 端与安卓端现在大部分都是才用的不同技术,安卓那边一般是用 m3u8 ,PC 这边一般是 flv
    leaflxh
        8
    leaflxh  
       219 天前   ❤️ 1
    b 站是转成 dash ,腾讯视频是 hls

    不切片 1M 的小水管估计够呛,因为比如拖动进度条到第 60 秒,但上个关键帧是在第 30 秒,结果就是浏览器要从 30 秒的地方开始下载,这 30 秒的视频大小可能超过 10M ,这样就导致要等待 10 秒的时间才能开始播放第 60 秒的视频
    KuAoaoaoao
        9
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @Elissa cdn 加速应该可以 但是需要付费哦,商业项目可以加
    KuAoaoaoao
        10
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @chaoschick 如果只上传项目定位是只播放 150M 以下大小的视频 有机会试试
    KuAoaoaoao
        11
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @fengci 知识盲区了 我去学习一下
    KuAoaoaoao
        12
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @flyqie 我瞅瞅
    KuAoaoaoao
        13
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @JensenQian 知识盲区了 俺学习下
    KuAoaoaoao
        14
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @chaoschick 原来简简单单的文件下载 涉及到视频文件时便成了流式传输领域
    KuAoaoaoao
        15
    KuAoaoaoao  
    OP
       219 天前 via iPhone
    @leaflxh 醍醐灌顶
    ltyj2003
        16
    ltyj2003  
       219 天前 via Android
    硬盘寄过去不好吗?
    Maerd
        17
    Maerd  
       219 天前   ❤️ 1
    你和我当时犯的错误一样,就是把视频通过后端处理然后返回给前端,这个对服务的影响其实蛮大的。楼上的几个老哥也说复杂了,实际上视频无论是 mp4 还是 flv ,套一层 nginx ,把静态链接丢给前端就可以流式播放了,不需要手动处理。另外就是,视频这种的大静态文件最好不要直接丢在服务器上
    Bingchunmoli
        18
    Bingchunmoli  
       219 天前 via Android   ❤️ 1
    直接对象存储 mp4 默认支持流式传输
    herozzm
        19
    herozzm  
       219 天前
    文件太大,不应该要压缩一下吗?光切片没啥用
    KuAoaoaoao
        20
    KuAoaoaoao  
    OP
       219 天前
    @Maerd 需要考虑一下项目的定位,小项目小视频就放自己服务器。大项目可以采用 oss 提高用户体验。
    KuAoaoaoao
        21
    KuAoaoaoao  
    OP
       219 天前
    @herozzm 需要压缩
    chaoschick
        22
    chaoschick  
       219 天前 via Android
    @KuAoaoaoao 不止视频, 音频 图像 等都可以这样做,流只是个思想
    chaoschick
        23
    chaoschick  
       219 天前 via Android
    chaoschick
        24
    chaoschick  
       219 天前 via Android
    这个是用 MSE 制作的
    liuguang
        25
    liuguang  
       219 天前   ❤️ 1
    用 hls 切片
    putyy
        26
    putyy  
       219 天前   ❤️ 1
    视频处理成 m3u8 ,在找个播放器
    ikas
        27
    ikas  
       219 天前   ❤️ 1
    第一关可以 controller 返回 FileSystemResource 就可支持"Range"了

    上传后队列里调用 ffmpeg 切片生成 m3u8..后续就用 m3u8 了
    hefish
        28
    hefish  
       219 天前   ❤️ 1
    用 flv.js 也行的。我觉着比 video.js 好用。
    转码肯定是要转的。可以直接调用 shell
    切片也肯定是要切的。也可以调用 shell ,好像能转码切片一起干了。
    MFWT
        29
    MFWT  
       219 天前   ❤️ 1
    HLS ( m3u8+ts )的方案可以考虑下
    mightybruce
        30
    mightybruce  
       219 天前   ❤️ 1
    ts, flv 压缩率太低,
    建议采用 DASH 或 HLS(m3u8 + fmp4) 来播放视频分片。
    m3u8 采用多级 m3u8 索引, 将视频转成多个不同分辨率的片段,每个子索引 m3u8 对应相应的分辨率片段,保证视频的播放平滑。
    服务器带宽要高点。

    使用 mp4 将视频压缩率提高, 并选用合适的分辨率。 移动 moov box 到 MP4 文件头部,播放器获取到 moov box 才能开始播放视频。视频传输采用 http 渐进式下载,

    使用云服务的对象存储保存这些视频
    winglight2016
        31
    winglight2016  
       218 天前
    我记得 v2 里有个老哥发了个开源前端播放器,挺炫酷的,可能可以解决 lz 的问题
    cslive
        32
    cslive  
       218 天前   ❤️ 1
    放过自己,搭建个 emby 私服吧
    jifengg
        33
    jifengg  
       218 天前   ❤️ 2
    楼上关于切片的都很对。但是这不是卡顿的原因,最大的原因是,视频码率大于服务器出口带宽。
    码率和带宽正好都是用 bit/s 为单位,所以,你要流畅播放,“理论上”视频码率就不能大于出口带宽,实际上码率还得降 20%。比如服务器是 10mb/s 的,“理论上”视频码率=10mb/s 是可以流畅播放的,实际上就看网络稳定性了。
    尽量扩展带宽,在此基础上,尽量降低视频码率,继续在这个基础上,提供多码率供前端选择(这要转多个码率的视频,耗转码时间)。
    至于不同分辨率用什么码率,可以去 B 站参考( youtu 的参考意义不大,因为他码率给得足)
    EricInBj
        34
    EricInBj  
       218 天前
    mp4 元数据前移一下。

    弄个 jellyfin 不香吗?
    chf007
        35
    chf007  
       218 天前
    @KuAoaoaoao 花点 cdn 的流量费绝对比你这 1M 小水管体验好,如果你只是为了学习如何在有限条件下把事做好又不怕女友那另当别论
    MrKrabs
        36
    MrKrabs  
       218 天前   ❤️ 1
    -movflags +faststart
    n1cogrv
        37
    n1cogrv  
       218 天前   ❤️ 1
    @EricInBj #34 > 弄个 jellyfin 不香吗?
    @KuAoaoaoao 看下来同感,别自己造轮子了。如果你女朋友又不是懂技术的人,你自己写出来的东西她用来用去都不顺手,那就不要再当沸羊羊了。
    喜羊羊都是滚个开源的 jellyfin 轮子再带个 tailscale/zeroconf 或者是 openvpn ,再不行 nginx 反代 https 出去得了。美羊羊用的舒心最重要。
    如果 jellyfin 没上传功能(我也不太清楚,没用过),你就自己用 go/java 写个 upload api 再 jellyfin 监听文件夹。
    flexbug
        38
    flexbug  
       218 天前   ❤️ 1
    可以试试 Cloudflare Stream
    Granado
        39
    Granado  
       218 天前
    给她充个会员吧
    chronos
        40
    chronos  
       218 天前   ❤️ 1
    将视频的元数据帧放到第一帧就能在未加载完成前拖动。ffmpeg 可以使用 -movflags +faststart ,或者 Handbrake 里面勾上[网页优化]。转换成 m3u8 + 切片的方式也行。
    mensa23
        41
    mensa23  
       218 天前   ❤️ 1
    这怎么说呢,可能就是一片心意,但是实际点说,写得再好,体验也比成熟产品差。。。也许到最后就是花了一大堆时间写了一个女朋友只用了几分钟的东西。。。
    反正个人觉得,时间成本也是成本。。。
    trzzzz
        42
    trzzzz  
       218 天前 via iPhone
    @ltyj2003 好好好,这么玩是吧
    KuAoaoaoao
        43
    KuAoaoaoao  
    OP
       217 天前
    @ikas 有这个类就很方便了,感谢
    KuAoaoaoao
        44
    KuAoaoaoao  
    OP
       217 天前
    @MFWT 需要看下 ts 技术
    KuAoaoaoao
        45
    KuAoaoaoao  
    OP
       217 天前
    @cslive 自己用,这种方法很棒
    KuAoaoaoao
        46
    KuAoaoaoao  
    OP
       217 天前
    @n1cogrv 写出来的东西,好用才是对的。不能只感动自己
    KuAoaoaoao
        47
    KuAoaoaoao  
    OP
       217 天前
    @mensa23 量力而行,尽力而为,兴趣使然嘛
    KuAoaoaoao
        48
    KuAoaoaoao  
    OP
       217 天前
    @chf007 买!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2689 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:28 · PVG 23:28 · LAX 08:28 · JFK 11:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.