video标签

遇到video相关的问题记录

  1. video占用内存在活动监视器里可以看到内存使用情况,Mac有一个进程是VTDecoderXPCServ可以注意下,就是视频编解码相关的进程,页面占用的内存不要太高了。
  2. 谷歌可以在perfomance monitor查看CPU占用率(我主要使用这个看)。或者timeline也会记录内存使用情况。javascript内存泄露及谷歌浏览器查看内存使用
  3. IOS和安卓的内存容量不同,有时候视频懒加载没写好,IOS会直接崩掉(闪退等等),还有视频的码率应该注意一下,4k的视频网页加载不动,1080P的视频才行,码率一般我们导出5M。

参考:
H5 video 开发问题及相关知识点
Video元素的使用和常见问题总结
video在安卓与ios实际应用中遇到的问题及解决

常用属性记录

  1. autoPlay
    自动播放,但是不同机型还需要其他属性配合,比如IOS必须加muted等。建议不使用自动的autoPlay属性,而采用懒加载的方式唤起播放。

  2. readyState属性
    只读属性。使用media.readyState返回媒介当前播放位置的就绪状态,共有5个可能值。
    HAVE_NOTHING(数字值为0):当前播放位置无有效媒介资源;
    HAVE_METADATA(数字值为1):加载中,媒介资源确认存在,但当前位置没有能够加载到有效媒介数据进行播放;
    HAVE_CURRENT_DATA(数字值为2):已获取到当前播放数据,但没有足够的数据进行播放;
    HAVE_FUTURE_DATA(数字值为3):已获取到后续播放数据,可以进行播放;
    HAVE_ENOUGH_DATA(数字值为4):可以进行播放,且浏览器确认媒体数据以某一种速度进行加载,可以保证有足够的后续数据进行播放,而不会使浏览器的播放进度赶上加载数据的末端。

  3. playsInline & webkit-playsinline
    标志视频播放时局域播放(不全屏播放),不脱离文档流 。不希望用户来拖动进度条,可以加上playsInline标签,IOS10以下的使用webkit-playsinline。部分机型没有设置playsInLine会自动全屏播放。遇到了再说。

  4. muted
    视频里的音频设置。设置后,音频会初始化为静音。默认值是false。IOS必须要加上muted才能自动播放。

  5. controls
    加上这个属性,Gecko 会提供用户控制,允许用户控制视频的播放,包括音量,跨帧,暂停/恢复播放。

  6. duration & currentTime
    视频总时长以及当前播放时长,可以根据这两个数据得知视频目前的播放进度,做动画设置。

一、视频懒加载。

  1. 懒加载一开始采用监听scroll的方案,但是其实scroll监听挺耗费内存的,可以采用IntersectionObserver的方式完成懒加载监听的优化。
  2. 只是video.pause()无法阻止加载进程,和占用内存的优化没有关系,只有停止后同时删掉src,再load()刷新一下才行噢。但是停止不在视口窗内的视频播放,一定程度上还是会减轻渲染消耗的吧。(不知道这样的理解有没有错误)
  3. 如果暂停播放太频繁会有报错:The play() request was interrupted by a call to pause(). 可以通过判断是否处于播放状态/直接把错误catch掉两个方式来解决。
  4. scroll监听时,最好做一个判断加载完后自动卸载监听的操作,并且使用throttle节流,需要把所有的视频都摆出来(需要切换的视频使用opacity),否则滑动完了自动把事件卸载之后新的视频就无法加载了,PS:不会使用pause()。
    useEffect(() => {
      const handleVideoLazyLoad = () => {
        const lazyVideos = [].slice.call(document.querySelectorAll('video'));
        if (typeof IntersectionObserver !== 'undefined') {
          const lazyVideoObserver = new IntersectionObserver((entries) => {
            entries.forEach((_video) => {
              const video = _video.target;
              const isPlaying =
                video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
              if (_video.isIntersecting && !isPlaying) {
                const videoSrc = video.getAttribute('src');
                const videoPoster = video.getAttribute('poster');
                const videoDataSrc = video.getAttribute('data-src');
                const videoDataPoster = video.getAttribute('data-poster');
                if (videoSrc) {
                  video.play().catch((e) => {});
                } else {
                  if (!videoPoster && videoDataPoster) video.setAttribute('poster', videoDataPoster);
                  if (!videoSrc && videoDataSrc) {
                    video.setAttribute('src', videoDataSrc);
                    video.play().catch((e) => {});
                  }
                }
              } else if (isPlaying) {
                video.pause();
                // const playtime = video.currentTime;
                // const videoDataSrc = video.getAttribute('data-src');
                // video.setAttribute('src', '');
                // video.load();
                // video.setAttribute('src', videoDataSrc);
                // video.currentTime = playtime;
              }
            });
          });
          lazyVideos.forEach((video) => lazyVideoObserver.observe(video));
        }
      };
      handleVideoLazyLoad();
    }, []);
    // scroll
    const videoPlay = useThrottle(() => {
      if (videos.current.every((d) => d.isPlay)) document.removeEventListener('scroll', videoPlay);

      const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

      videos.current.forEach((_v, i) => {
        const offsetTop = _v.dom.getBoundingClientRect().top; // video顶部距离视口窗的距离
        const top = offsetTop - viewPortHeight; // video顶部距离视窗底部的高度

        if (top <= (isMobile ? 150 : 300) && !_v.isPlay) {
          _v.dom.play();
          _v.isPlay = true;
          // _v.dom.style.height = _v.clientHeight !== 0 ? `${_v.clientHeight}px` : `${_v.height}px`;
        }

        // if(rect.bottom <= 0 && _v.isPlay) {
        //   _v.dom.pause();
        //   _v.isPlay = false;
        // } 
      });
    }, 300);

    const lazyLoadVideos = () => {
      const oVideos = document.querySelectorAll('video');
      const _videos = [];

      oVideos.forEach((dom, i) => {
        if (!isEmpty(dom.src)) {
          _videos.push({ dom, isPlay: false });
        }
      });
      videos.current = _videos;

      document.addEventListener('scroll', videoPlay);
    };

    useEffect(() => {
      lazyLoadVideos();
      return () => document.removeEventListener('scroll', videoPlay);
    }, []);

二、视频自动播放的属性

只添加autoPlay在IOS无法自动播放。因为IOS有一个策略,静音的视频才能自动播放,也就是muted属性。

 // 常用的video标签
<video
  data-poster={poster}
  data-src={src}
  loop
  muted
  playsInline
  controls={false}
/>

三、视频宽度一定时IOS上高度莫名拉长

某次开发中突然出现的,高度不知为何特别长,后来想了两种解决方案:

  1. 根据已知的视频比例直接设定高度

width: calc(100% - 48px);
height: calc((100% - 48px) * 16 / 9);

  1. 在懒加载的时候同步设定视频高度

_v.dom.style.height = _v.clientHeight !== 0 ? ${_v.clientHeight}px : ${_v.height}px;

四、video相关常用事件的应用

object.onXXX=function(){myScript};
object.addEventListener(“XXX”, myScript);

  1. play
    开始播放音频/视频,返回一个promise

document.querySelector(‘video’).pause()

  1. pause
    暂停当前播放的音频/视频

document.querySelector(‘video’).pause()

  1. load
    重新加载音频/视频元素

document.querySelector(‘video’).load()

  1. canplay
    在媒体数据已经有足够的数据(至少播放数帧)可供播放时触发。
    loop循环播放时每一次循环都会触发一次canplay
  2. ended
    播放结束时触发。
  3. progress
    当浏览器正在下载指定的音频/视频时,会发生 progress 事件。告知媒体相关部分的下载进度时周期性地触发。(也就是说和播放的progress无关,仅与下载进度有关)
  4. suspend
    在媒体资源加载终止时触发,这可能是因为下载已完成或因为其他原因暂停。

当音频/视频处于加载过程中时,会依次发生以下事件:

loadstart
durationchange
loadedmetadata
loadeddata
progress
canplay
canplaythrough

    useEffect(() => {
      querySelector('#testVideo').then((video) => {
        video.addEventListener('canplay', (e) => {
          console.log('canplay', e);
        });

        video.addEventListener('pause', (e) => {
          console.log('pause', e);
        });

        video.addEventListener('ended', (e) => {
          console.log('ended', e);
        });

        video.addEventListener('play', (e) => {
          console.log('play', e);
        });

        video.addEventListener('load', (e) => {
          console.log('load', e);
        });

        video.addEventListener('progress', (e) => {
          console.log('progress', e);
        });

        video.addEventListener('suspend', (e) => {
          console.log('suspend', e);
        });
      });
    }, []);

五、想要实现动画进度跟随视频进度,如何实现?

  1. 添加视频定时器监听,为了性能着想不能用setTimeout,采用requestAnimationFrame方式。
  2. 视频监听函数中,获取当前的播放进度currentTime / duration,然后根据progress进度处理动画。就算视频不播放/暂停等状态,也可以从progress上精确知道。
  useEffect(() => {
    cancelAnimationFrame(timer.current);
    timer.current = requestAnimationFrame(animationVideo);
    return () => cancelAnimationFrame(timer.current);
  }, [index, animationVideo]);

  // 动画跟随进度处理
  // videoDomMap[index]是当前活跃元素
  const animationVideo = useCallback(() => {
    let percentage = 0;
    if (videoDomMap[index]?.duration) {
      if (lineWidth[index - 2]?.style) {
        percentage = videoDomMap[index].currentTime / videoDomMap[index].duration;
        lineWidth[index - 2].style.width = `${percentage * 100}%`;
      }
    }
    if (percentage >= 1) {
      // 播放完毕,切换下一个
      changeVideo(index+1)
    }
    cancelAnimationFrame(timer.current);
    timer.current = requestAnimationFrame(animationVideo);
  });

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值