mp4 实现前端动画

754 阅读6分钟

一. 背景

[医生端-2024年度执医成就]需要使用复杂的转场动画,单纯使用 css 方式开发成本高。经调研,使用 mp4 作为背景动画可以低成本实现业务需求。 线上地址,大家可以感受一下:muzhi.baidu.com/f2c/twentyF…

但存在问题如下:

  1. 视觉侧交付的视频体积过大,难以保证用户体验
  2. 考虑到网络情况等不可控因素,视频加载 / 播放可能会出现失败

二. 资源体积优化

2.1 名词解释

分辨率:

分辨率是指图像或视频中的像素数量。常见的分辨率有720p(1280×720)、1080p(1920×1080)和4K(3840×2160)等。分辨率越高,图像或视频的细节和清晰度就越高。

码率:

码率是指视频或音频数据传输速率,通常以每秒传输的比特数来表示,单位为kbps(千比特每秒)或Mbps(兆比特每秒)。码率越高,视频或音频的质量就越高,但同时占用的带宽也会更大。

帧率:

帧率是指每秒显示的图像帧数。通常以“帧每秒”来表示。常见的帧率有24、30、60等。帧率越高,视频播放就越流畅,尤其在快速运动的场景中能够更好地展现细节。

编码格式:

编码的目的是压缩数据量,采用编码算法压缩冗余数据。常用的编码格式有如下这两种

视频元数据(Metadata)

技术性元数据 包括:

  • 文件大小
  • 文件格式(如MP4, AVI, MOV等)
  • 编码格式(如H.264, H.265)
  • 创建和修改日期
  • ......

内容相关元数据 包括:

  • 创建日期
  • 内容描述
  • ......

2.2 视频格式处理

2.2.1 编码格式

判断设备是否支持 H265 格式,支持则采用 H265 编码格式视频,否则采用 H264 编码格式

  • H265 视频压缩率高 & 对硬件设备要求较低
  • 内嵌 webveiw H5 页面可能会不兼容 H265 格式,则采用 H264 视频格式

各编码格式具体对比如下:

  • MPEG(MPEG-2、MPEG-4)
    • 压缩效率:较低,主要用于早期数字电视和DVD等应用。
    • 兼容性:历史最久,兼容性好
  • H.26X(H.263、H.264/AVC、H.265/HEVC)
    • H264
      • 压缩效率:显著提升,比MPEG-4高50%以上。适合高清(HD)和全高清(FHD)内容。
      • 兼容性:广泛支持,几乎所有现代设备(手机、PC、电视等)均提供硬件解码。
    • H265
      • 压缩效率:比H.264高约50%,适合4K和HDR视频。
      • 兼容性:逐渐普及,支持的设备较新(2015年后发布的设备多支持)。
  • AV1
    • 压缩效率:比H.265和VP9高约20-30%,目前压缩效率最高的主流标准,适合8K、HDR视频。
    • 兼容性:较新,2020年后发布的高端设备逐渐支持(如新款GPU、旗舰手机)。
  • VP9
    • 压缩效率:与H.265相近,比H.264高约50%。适合4K和高动态范围(HDR)。
    • 兼容性:现代设备(如Android手机、Chrome浏览器支持良好),但普及度略低于H.265。
编码标准压缩效率硬解码兼容性软解码开销硬解码开销
MPEG-2全面
MPEG-4部分支持
H.263较低几乎无较低较低
H.264非常全面中高
H.265更高新设备支持较好较低
VP9类似H.265Android/Chrome友好较低
AV1最高新设备逐渐普及极高较高

2.2.2 原视频格式分析

视觉侧交付的文件中,视频编码格式是 H264,2s 的视频体积为 3.3M,不符合预期

使用 ffmpeg 查看具体视频信息

ffprobe -show_streams 登榜次数-264.mp4

[STREAM]
index=0
codec_name=h264
profile=Main
codec_type=video
width=1242
height=2688
coded_width=1242
coded_height=2688
level=60    !!!
duration=2.000000
TAG:encoder=AVC Coding
......
[/STREAM]

当使用H.264编码时,profilelevel是指视频编码的配置参数,它们决定了视频的质量、兼容性和性能。具体来说,这些参数包括:

Profile(配置文件):

指定了编码器可以使用的特定功能和算法,影响了视频的压缩效率和质量。常见的profile包括Baseline、Main和High。Baseline适用于较低质量的视频,Main适用于一般质量的视频,而High适用于高质量的视频。

Level(级别):

指定了视频的参数,如分辨率、帧率和比特率的限制。不同的level对应不同的视频参数限制,例如Level 3.0适用于标清视频,Level 4.1适用于高清视频,Level 5.1适用于超高清视频。

简单来说:Profile 和 Level 的等级越高,文件压缩得越小,传输越快,但cpu消耗越多。

压缩级别越高不仅在压缩时cpu的消耗越高,视频在播放时也需要消耗更多的cpu进行解压,各类型手机的硬件参数不一样,所以支持的压缩级别也不同。

ios 设备中最高支持的 profile 为 High,Level 为 4.1

在兼顾视频质量 & 视频体积后,选择 Profile 为 Main,Level 为 4.1 等级的视频

2.2.3 视频格式处理

H265的视频体积会比H264 体积更小,但webview 内嵌 h5 页面可能对H265 的兼容性不好

通过运行时判断是否支持H265 格式,采用不同的视频格式

#!/bin/bash

# 指定你的视频文件夹路径
VIDEO_FOLDER="/Users/chenwenliang/Desktop/24"

OUTPUT_DIR="${VIDEO_FOLDER}/h264"

# 进入视频文件夹
cd "${VIDEO_FOLDER}"

# 循环处理文件夹内的每个MP4文件
for input in *.mp4; do
    # 跳过不存在的文件(例如如果没有找到任何匹配的.mp4文件)
    [ -f "$input" ] || continu

    # 使用ffmpeg转换视频到H.264编码
    ffmpeg -i "${input}" -c:v libx264 -profile:v main -level 4.1 "${OUTPUT_DIR}/${input%.mp4}_h264.mp4"
    # 使用ffmpeg转换视频到H.265编码
    ffmpeg -i "${input}" -c:v libx265 -vtag hvc1 "${OUTPUT_DIR}/${input%.mp4}_h265.mp4"
done

echo "转换完成!"

2.3 图片格式处理

  • SVG 是基于XML的矢量图片格式,不失真无限放大。支持动画。
  • JPEG 是有损压缩,不支持透明度或者动画。
  • PNG 是无损压缩,支持透明度。APNG 是 PNG 的扩展,支持动胡奥。
  • WebP 是无损和有损压缩,支持动画或者透明度。
  • GIF 是位图图片格式,支持动画。

结论:

    * 小图标或 logo 可以使用 SVG
    * 背景图片不需要透明度,使用 JPEG 格式
    * 对于超过特定大小的 GIF动图,可使用CSS动效或者视频替代。

2.4 处理后总体积

H264 视频18个 体积12.96 MB

H265 视频18个 体积3.93 MB

图片资源(含 gif) 42个 体积 5.48 MB

音频资源 1 个 体积 938.106KB

共计:23.29MB 最优情况只需加载 H265 视频 + 图片 + 音频 ≈ 11MB

三. 兜底处理

3.1 视频加载失败

**原因:**由于网络原因等不可控因素,视频加载可能会出现失败

**目的:**视频获取失败时能正常展示背景图片,保证用户体验

**核心逻辑:**在每个页面组件外层使用 backgroundImage 作为兜底处理

return pages.map((item, index) => {
    const match = index === currentPage;
    return (
        <div
            key={index}
            style={{
                zIndex,
                pointerEvents: match ? 'auto' : 'none',
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%',
                backgroundSize: 'cover',
                backgroundPosition: 'center center',
                backgroundRepeat: 'no-repeat',
                backgroundImage: match ? `url(${item.bg})` : '',
            }}
        >
            <CSSTransition
                in={match}
                timeout={100}
                classNames={
                    enter: styles.reportEnter,
                    enterDone: styles.reportEnterDone,
                    exit: styles.reportExit,
                    exitDone: styles.reportExitDone
                }
                appear
                unmountOnExit={!!isApp}
            >
                <div
                    style={{
                        width: '100%', height: '100%',
                        visibility: currentPage === index ? 'visible' : 'hidden'
                    }}
                >
                    <item.component
                        order={index}
                        src={videoSrc}
                        bg={bg}
                        videoRef={videoRefArr.current[index]}
                    />
                </div>
            </CSSTransition>
        </div>
    );
});

3.2 播放视频失败

原因:

  1. 视频加载失败导致用户上滑时播放视频失败
  2. 兼容性问题,某些浏览器视频播放时机必须和用户交互强绑定。但通过视频转场时必须监听上一页的视频播放完毕,触发下一页视频的播放,无法和用户操作强绑定。

目的: 播放转场视频失败时也能正常翻页

核心逻辑: 子组件内监听用户手势操作**,并重写 video.play 方法,**播放成功后 / 失败都需翻页

import { useCallback, useEffect } from 'react';
import { ON_PAGE_NEXT } from '@/constants';
import { useTwentyFourContext } from './useTwentyFourContext';

const useHandleVideoEnd = (secondVideoRef, setShowFirstVideo) => {
    const { dcBill: { currentPage }, eventBus, updateDcBillData } = useTwentyFourContext();

    const goNext = useCallback(() => {
        updateDcBillData({
            key: 'currentPage',
            value: currentPage + 1
        });
    }, [currentPage, updateDcBillData]);

    const onNextPage = useCallback(() => {
        setShowFirstVideo(false);
        secondVideoRef.current?.play(goNext);
        secondVideoRef.current?.onVideoEnd(oNext);
    }, [goNext, secondVideoRef, setShowFirstVideo]);

    useEffect(() => {
        eventBus?.on(ON_PAGE_NEXT, onNextPage);

        return () => {
            eventBus?.off(ON_PAGE_NEXT, onNextPage);
        };
    }, [eventBus, onNextPage]);

    return onNextPage;
};

export default useHandleVideoEnd;

四. 兼容性问题处理

4.1 微信导航栏问题

问题: 微信路由跳转后会出现底部前进/后退导航栏,造成空间压缩影响视觉效果,且没有暴露 api 进行屏蔽

解决方案: 改造成单路由页面

4.2 safari 100vh问题

问题: safari 浏览器中 100vh 并不是指的可视区中的高度,会包括下面工具栏

使用 vh 进行布局时,就会可能造成功能列遮挡文字

解决方案: 通过 innerHeight 获取可视区高度,top / bottom 采用百分比布局

const width = window.innerWidth;
const height = window.innerHeight;

 <div
    style={{ maxHeight: height, maxWidth: width }}
    ref={contentRef}
>
    {buildPages}
</div>