如何一步步抽出双端通用Video组件(Taro小程序/h5)

1,261 阅读4分钟

0、前情提要

为了解决下面这些问题,我考虑到抽出一个公共的Video组件,使其能在多端的效果一致

  • 1.Video组件即使未点击也会预先拉取视频资源,小程序的内存非常容易溢出,当一个页面渲染多个Video在部分老旧机型上会直接爆内存,导致微信卡顿/闪退

  • 2.ios的浏览器播放video是直接全屏打开,安卓则是无论video的宽高多小都是在video内部播放,必须手动全屏。

  • 3.当一个页面需要渲染多个video(比如视频列表的时候),安卓&安卓h5端会出现多个video同时播放的问题。

  • 4.Video组件在Taro(小程序)和H5部分API不一致的问题,例如Taro.createVideoContext的到的Video实例,无法操控h5端的Video播放/暂停/全屏等行为,如下所示,仅在小程序端可用。

    image.png

  • 5.Video组件在Taro(小程序)和H5部分样式不一致

1、需求分析

综上所述,我们需要的Video需求如下

  • 能在单个页面渲染多个video,且在小程序能流畅运行。
  • 实现ios和安卓小程序端的点击播放效果统一。
  • 实现ios和安卓h5端的点击播放效果统一。

2、思考需求解决方案

2.1、解决小程序单个页面渲染多个Video内存溢出

  • 这个问题在寻找小程序官方社区以后有人给出了可行的方案,在接口中传递一张视频的缩略图,当点击以后再切换Image组件Video组件,经简单测试,思路可行,在老旧的手机只要视频不是很大上也不会出现爆内存的情况。

2.2、实现ios和安卓的点击播放效果统一

  • 这一点在思考后决定统一为ios的效果,如果Video都是点击就全屏播放,退出全屏就暂停播放的话,安卓端单个页面多个Video同时播放的问题也得到了解决,可以说是用一个方案解决了两个Issue

2.3、解决Taro.createVideoContext的实例方法无法控制h5的video

  • 这个目前思考到只能通过环境变量拿到当前的环境是weapp还是h5,对h5上使用useRef绑定Video的引用,来操控h5的video行为,期待后续Taro对Video组件的支持更好吧。

2.4、样式不一致的问题

  • css的问题,不会就问GPT。

3、思考组件的props需要什么

  • src:string,video组件播放的视频资源
  • 为了要更加通用,要控制Video的播放,我们需要传入一个play:Boolean,来控制当前Video是否播放
  • thumb:string,Video组件的缩略图,点击后切换视频
  • id:string,video组件的id,用于拿到Video实例(目前看来意义不大,可供后续拓展h5渲染多个video实现更流畅的效果)
    // props Type
    type Props = {
      src: string
      play: Boolean
      thumb: string
      id?: number
    }
    

4、编写CommonVideo组件

  • 为了通过Play控制Video的播放/同一个页面只渲染一个Video,未点击的用缩略图占位(解决2.1和2.3)
    // 我们根据props传入的play属性,判断是渲染video还是渲染image
    // uniqueId的传入是为了多个video和imgae之间切换全屏异常的问题
    return (
            <View className={styles.video_wrapper}>
              {play ? (
                <Video
                  id={`videoTaroPlayer${uniqueId}`}
                  ref={videoH5Ref}
                  showBottomProgress="false"
                  x5-video-player-type="h5"
                  style={{ width: '100%', height: '100%' }}
                  x5-video-player-fullscreen="true"
                  {...rest}
                ></Video>
              ) : (
                <Image src={thumb} className={styles.video_wrapper_thumb} mode="widthFix" />
              )}
            </View>
        )
    }
    
  • 统一ios和安卓在小程序端打开视频的效果一致,全部统一为ios打开视频的效果,既点击视频就打开全屏播放,退出全屏就暂停播放(解决2.2)
    • 先拿到video的小程序和h5的引用,在根据环境调用commonVideoFullScreen方法,点击就全屏并播放
    • 这里有一个问题:ios和安卓在h5上全屏的api是不一样的,所以只能各写一遍了
    // ref 控制 h5 的播放暂停
    const videoH5Ref = useRef(null)
    
    // videoTaroPlayer 控制 Taro 的播放暂停
    const videoTaroPlayer = Taro.createVideoContext(`videoTaroPlayer${id}`)
    
    
    const commonVideoFullScreen = (video: HTMLVideoElement, pageEnv: PageEnv) => {
        switch (pageEnv) {
          case 'weapp':
            videoTaroPlayer.requestFullScreen({ direction: 0 })
            // 因为play的方法要等待视频资源加载完成才能拿到,默认等待0.5秒,防止用户一进来立刻点击大视频导致卡顿
            delayOnVideoReady(videoTaroPlayer.play, 500)
            break
          case 'h5':
            if (video.requestFullscreen) {
              video.requestFullscreen()
              video.addEventListener('loadedmetadata', () => {
                video?.play()
              })
            }
            break
          default:
            // 如果 pageEnv 不是 'weapp' 或 'h5',可以在这里处理默认情况
            break
        }
    }
    
    useLayoutEffect(() => {
      const video: HTMLVideoElement = videoH5Ref.current!
      const pageEnv: PageEnv = isWeapp ? 'weapp' : 'h5'
      if (play) {
         commonVideoFullScreen(video, pageEnv)
      }
    }, [play])
    

5、遇到的疑难杂症

  • 直接调用video.play()报错:调试的时候发现在useEffect直接调用videoplay属性会报错:App.jsx:17 Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first., 查找stackoverflow发现是因为大多数现代浏览器都会阻止自动播放带有声音的视频,除非用户首先与页面进行了交互,以防止页面加载时自动播放声音可能会打扰用户的体验,但是使用muted属性让视频静音可以跳过这个浏览器限制,虽然不影响我们这次组件的编写(因为我们是点击后再全屏播放),但是可以扩展一下这方面的知识!
  • Taro Video在h5的效果和原生的Video不一致:在chorme中,默认使用const video = videoRef.current;video.requestFullscreen()就可以全屏并自动播放,但是在Taro的h5开发中,必须要等待loadedmetadata再调用play方法/这是因为视频资源还没加载完成调用play会报错。
  • 在切换image和video的过程中,ios浏览器(特指safari),在切换成video后无法在代码层面全屏,这是因为ios的video浏览器限制,不支持在播放时默认全屏,但是这也属于多端之间可以接受的差异