React学习笔记(番外一)——video.js视频播放组件的入门及排坑经历

Posted IMplementist

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React学习笔记(番外一)——video.js视频播放组件的入门及排坑经历相关的知识,希望对你有一定的参考价值。

React学习笔记(番外一)——video.js视频播放组件的入门及排坑经历

前言

很久没有静下心写博客了。近段时间接到一个任务,前端页面要加上视频播放功能。实现加排坑前后花了三天时间(别问我问什么这么久😂),觉得还是有必要记录一下的。


video.js的支持的视频格式及编码方式

这一部分有必要写在最前面,避免你看了一长串安装、引入、代码,然后发现自己想要播放的视频格式或编码video.js不支持。

支持的扩展名(格式)

从另一篇博客[1]中了解到,它支持mp4、webm、ogv、m3u8、flv、rtmp等扩展名的视频文件。

支持的视频编码

mp4为例,虽然扩展名都可以是mp4,但不同视频文件的编码可以是MPEG4、H.264等,这里要注意,video.js只支持H.264编码的mp4文件, 如果你要播放的文件编码不是H.264,需要先转换编码,或者在录制的时候设置为H.264

查看视频文件编码方式的方法:

  • Mac
    文件右键菜单 -> 显示简介 -> 更多信息 -> 编解码器
  • Windows
    文件右键菜单 -> 属性

video.js的安装

现在比较知名的前端库都支持node.js,video.js也不例外,所以安装只需要:

    npm install video.js

将video.js引入React

起初按照一些博文抄代码到自己的项目,发现会有一些报错,后来耗时很久没有解决,找到了官方给的示例代码[2],简洁且跑起来没有报错。
我这里把注释翻译一下,略作改动:

自定义播放器控件

import React from 'react';
import videojs from 'video.js';
// 记得引用css文件!
import 'video.js/dist/video-js.css';

export const VideoJS = (props) => 
  // video标签的引用Hook
  const videoRef = React.useRef(null);
  // 播放器实例的引用Hook
  const playerRef = React.useRef(null);
  const options, onReady = props;

  React.useEffect(() => 
	  // 确保video.js的播放器实例player仅被初始化一次,否则会报错
	  if (!playerRef.current) 
	      const videoElement = videoRef.current;
	      if (!videoElement) 
	          return;
	      
	
	      const player = playerRef.current = videojs(videoElement, options, () => 
	          videojs.log('播放器准备就绪!');
	          onReady && onReady(player);
	      );
	  // 当props发生变化时,可以对已经存在的player实例作一些操作,如:
	   else 
	     // const player = playerRef.current;
	     // player.autoplay(options.autoplay);
	     // player.src(options.sources);
	  
  , [options, videoRef]);

  // 控件被unmount卸载的时候,记得要对player实例执行反初始化dispose
  React.useEffect(() => 
    const player = playerRef.current;

    return () => 
      if (player) 
        player.dispose();
        playerRef.current = null;
      
    ;
  , [playerRef]);

  return (
    <div data-vjs-player>
      <video ref=videoRef className='video-js vjs-big-play-centered'/>
    </div>
  );


export default VideoJS;

引用自定义视频播放器控件

有了上述的视频播放器控件,我们可以在任意需要播放视频的页面中引用它:

import React from 'react';

// 从控件的路径引用它,这里默认它和页面在同一目录下
import VideoJS from './VideoJS'

const App = () => 
	const playerRef = React.useRef(null);

	const videoJsOptions = 
		// 自动播放:为true时,加载完毕自动播放
		autoplay: true,
		// 播放器子控件是否显示:为true时显示暂停、播放速率等按钮
		controls: true,
		// 响应性:为true时,播放视频进度条会自动移动
		responsive: true,
		// 流式布局:为true时尽可能大得填满父标签的剩余空间
		fluid: true,
		// 视频源
		sources: [
			// 视频文件的路径,可以是一个前端相对路径、后台视频文件URL、直播源等
			src: '/path/to/video.mp4',
			// 视频源类型
			type: 'video/mp4'
		]
	;

	// 播放器实例化完成后的事件动作,注意!不是视频加载成功
	const handlePlayerReady = (player) => 
		playerRef.current = player;
		// 播放器的子事件在这里定义

		player.on("canplaythrough", () => 
	        console.log("视频加载完成!")
        );
        
		player.on("error", () => 
            console.log(`视频文件加载失败,请稍后重试!`);
        );
        
        player.on("stalled", () => 
            console.log(`网络异常,请稍后重试!`);
        );
	;

	return (
		<>
			<!-- ... -->
			<VideoJS options=videoJsOptions onReady=handlePlayerReady/>
			<!-- ... -->
		</>
	);

更多的播放器事件定义可以参考另一篇博文[3]


排坑记录

报错VIDEOJS ERROR (CODE4 MEDIA_ERR_SRC_NOT_SUPPORTED) No compatible source was found for this media

我自己遇到两种情况引起的这个报错:

  1. 视频源的格式或编码方式video.js真的不支持 - 这种情况只能调整视频源,转码或者替换其他前端视频播放控件。

    我当时遇到的情况是: 起初不太懂同样是MP4文件,还有编码方式的区别。幸而有多个视频源,有的可以播放,有的不可以播放。点开文件属性进行对比,发现了编码方式的区别,即上述不支持MPEG4编码的问题。
  2. HTML DOM树中没有指定的video标签 - 从上述代码中可以看到,我们需要一个<video></video>标签去初始化video.js。如果因为一些代码逻辑问题,DOM树中没有video标签,此时初始化video.js对象也会报这个错。

    我当时遇到的情况是: 一个高阶组件通过props里的参数控制渲染一个<img></img>图片标签还是<video></video>视频标签。由于js代码逻辑有漏洞,渲染<img></img>图片标签的同时通过videojs(...)初始化了一个播放器对象。

重复初始化报错

严格意义上说是一个警告,原文不记得了,大致意思就是说player对象已经初始化过一次,所以不再接受options里的新参数。

这个是因为在组件上次unmount卸载的时候没有对player对象进行销毁dispose(),按上述的代码去写,就不会有这个问题。

React底层代码报错:要删除的标签video不存在

由于dispose()函数会删除<video></video>标签,之前借鉴其他博文里通过id初始化video.js对象,dispose()的时候就报了这个错。

原本的预期应该是React底层先unmount,这个时候就删除了<video></video>标签,dispose()在其后执行就不会报错。但是借鉴了那些代码之后不知道为什么dispose()执行在unmount之前了,React底层找不到要删除的<video></video>标签,就抛错了,并且导致了页面白屏。

解决方法同上,使用官方给出的示例代码去实现就好了,不会报这样的错。


后记

首先,感谢video.js视频播放组件的开发者及贡献者。这个组件很简单易用,为音视频的门外汉节省了很多时间和代码量。

实现过程中,一开始参考一些个人博文的代码,大多数是基于class组件实现的,和我的函数式组件及React Hook框架有些水土不服。所以花了些时间在踩坑和排坑上。如果一开始就看了官方的示例,就能省些时间。另外遇到及处理视频编码的问题,也额外学习到了一些相应的知识。


  1. video 、video.js、playease.js支持的视频格式 ↩︎

  2. React and Video.js ↩︎

  3. videojs的一些监听事件汇总 ↩︎

从零开始的跨平台渲染引擎(番外一)——光和相机

前言

基于物理渲染中使用的光和相机也需要符合物理性质,那么首先需要确定在物理世界中,光和相机是如何描述的。而其中最核心的,就是如何划定参与计算的数值类型和范围。
本文主要阐述了物理世界中,光如何量定,相机参数含义,以及它们如何参与到着色计算,以及实际应用。


光的单位

光学术语标记单位定义
光通量 Luminous flux / Luminous power Φ \\mathrm\\Phi Φ流明 Lumen ( l m = c d ⋅ s r \\mathrmlm=cd\\cdot sr lm=cdsr)单位时间内由光源所发出或由被照物所吸收的总光能
发光强度、光度 Luminous Intensity I \\mathrmI I坎德拉 Candela ( c d = l m / s r \\mathrmcd=lm/sr cd=lm/sr)光源在给定方向上,每单位立体角内所发出的光通量
照度 Illuminance E \\mathrmE E勒克斯 Lux ( l x = l m / m 2 \\mathrmlx=lm/m^2 lx=lm/m2)物体表面每单位面积所吸收或发出可见光的光通量
亮度、辉度 Luminance L \\mathrmL L尼特、坎德拉每平米 ( n i t = c d / m 2 \\mathrmnit=cd/m^2 nit=cd/m2)单位面积光源在给定方向上,在每单位面积内所发出的总光通量

通过上表中,各种单位的定义可以确定各种类型光源的单位;

光源类型单位原因
方向光Illuminance ( l x \\mathrmlx lx)方向统一(不能使用 c d \\mathrmcd cd),且没有固定的发光物,无法统计总光能(不能使用 l m \\mathrmlm lm)
点光源Luminous power ( l m \\mathrmlm lm)有固定发光物,可以统计总光能
聚光灯Luminous power ( l m \\mathrmlm lm)原因同上
IBL基于图像的光源Luminance ( c d / m 2 \\mathrmcd/m^2 cd/m2)对于引擎捕获光探头使用亮度单位非常直观

光的使用

方向光

L o u t = f ( v , l ) E ⊥ ⟨ n ⋅ l ⟩ \\mathrmL_out=f(v,l)\\mathrmE_\\perp\\langle n\\cdot l\\rangle Lout=f(v,l)Enl
输出值的单位是亮度单位,尼特( n i t \\mathrmnit nit)。

一般用方向光模拟日光、月光,下表是实际测量晴天时的照度:

光源10:00 am12:00 pm5:30 pm
S k y ⊥ + S u n ⊥ Sky_\\perp+Sun_\\perp Sky+Sun 120 , 000 120,000 120,000 130 , 000 130,000 130,000 90 , 000 90,000 90,000
S k y ⊥ Sky_\\perp Sky 20 , 000 20,000 20,000 25 , 000 25,000 25,000 9 , 000 9,000 9,000
S u n ⊥ Sun_\\perp Sun 100 , 000 100,000 100,000 105 , 000 105,000 105,000 81 , 000 81,000 81,000

满月则近似为 1 l x 1\\mathrmlx 1lx

精确光

精确光包括点光源和聚光灯。

为了模拟平方衰减定律,其照度计算公式为:
E = I d 2 ⟨ n , l ⟩ \\mathrmE=\\cfrac\\mathrmId^2\\langle n, l\\rangle E=d2In,l
其中点光源和聚光灯的光度 I \\mathrmI I的计算方式有所区别; d d d为被照射点到光源点的距离。
此时的计算结果单位是照度( l x \\mathrmlx lx),与方向光的单位相同,为照度 l x \\mathrmlx lx(注:光度仅用于描述光源信息,所以有立体角概念,但是对于受照射物体,则只考虑接收到的光通量,所以此处的单位忽略的每立体角)。

最终计算亮度的公式则和方向光类似: L o u t = f ( v , l ) I d 2 ⟨ n , l ⟩ \\mathrmL_out=f(v,l)\\cfrac\\mathrmId^2\\langle n, l\\rangle Lout=f(v,l)d2In,l

IBL

在现实世界中,光线会从四面八方照射到物体表面,并且还有一些经过弹射后,间接照射到物体表面的光;一般情况下,渲染引擎会用立方体贴图来描述这类光,称为基于图像光照(Image Based Lighting, IBL)或间接光照(Indirect Lighting)。

整个环境光对给定物体表面点的整体贡献的光照,称为辐照度(Irradiance),用 E \\mathrmE E来表示,单位是 l x \\mathrmlx lx;而从物体表面弹射出的光称为辐亮度(Radiance),用 L o u t \\mathrmL_out Lout来表示,单位是 n i t \\mathrmnit nit。计算公式如下:
L o u t ( n , v ) = ∫ Ω f ( l , v , Θ ) L ⊥ ( l ) ⟨ n , l ⟩ d l \\mathrmL_out(n,v)=\\int_\\Omegaf(l,v,\\Theta)\\mathrmL_\\perp(l)\\langle n,l\\rangle dl Lout(n,v)=Ωf(l,v,Θ)L(l)n,ldl

其中,IBL所使用的物理单位是 n i t \\mathrmnit nit,即 c d / m 2 \\mathrmcd/m^2 cd/m2
对于HDR图像,情况会有些复杂。相机不能记录辐亮度,但是可以记录依赖于设备的,仅和场景辐亮度想过的值,所以需要提供一个乘数用于重建原本的绝对辐亮度。


相机

使用基于物理相机来对场景输出亮度正确的曝光,是图像处理的第一步。
光照管线输出的结果,到达相机时,使用亮度(Luminace)表示,单位是 c d / m 2 \\mathrmcd/m^2 cd/m2。场景亮度值会有一个极大的范围,比如星空的亮度近似为 1 0 − 5 c d / m 2 10^-5\\mathrmcd/m^2 105cd/m2;而太阳能够达到 1 0 9 c d / m 2 10^9\\mathrmcd/m^2 109cd/m2。所以需要对其进行重映射。

为了最大化的利用相机记录范围,将光集中到“中灰”附近;可通过手动或自动地改变三个参数来实现:光圈、快门速度、感光度。

  • 感光度

ISO感光度表示了胶片的感光速度,即银元素与光线的光化学反应速度;数码相机套用了这个感光度标准,代表了感光元器件的感光速度。感光度每提高一倍,感光速度也相应提高一倍。
低ISO值适合清晰、柔和的图片;而高的ISO值,则可以补偿灯光不足的场景。
感光度用 S \\mathrmS S表示。

  • 光圈

光圈决定了同一时间通过的光线多少,用 N \\mathrmN N来标记,以f-stop单位表示。越高的值表示光圈越小,而越低的值则表示更大的光圈,例如 f / 16 f/16 f/16 f / 1.4 f/1.4 f/1.4

  • 快门速度

快门速度控制光圈快门开启持续的时间。

曝光度

曝光度是通过以2为底的对数表示的:
E V = log ⁡ 2 ( N 2 t ) EV=\\log_2(\\cfracN^2t) EV=log2(tN2)
1 E V 1 EV 1EV的差异表示1 stop, + 1 E V +1EV +1EV表示亮度翻倍,相应的 − 1 E V -1EV 1EV则表示亮度减半。
业界惯例是使用感光度为100ISO的基础上进行度量,以100ISO为基准的曝光度可以记为 E V 100 EV_100 EV100。在改变感光度的情况下,可以得到以下公式:
E V S = E V 100 − log ⁡ 2 ( S 100 ) EV_S=EV_100 -\\log_2(\\cfracS100) EVS=EV100log2(100S)
可转换为:
E V 100 = E V S − log ⁡ 2 ( S 100 ) = log ⁡ 2 ( N 2 t ) − log ⁡ 2 ( S 100 ) EV_100=EV_S-\\log_2(\\cfracS100)=\\log_2(\\cfracN^2t)-\\log_2(\\cfracS100) EV100=EVS以上是关于React学习笔记(番外一)——video.js视频播放组件的入门及排坑经历的主要内容,如果未能解决你的问题,请参考以下文章

从零开始的跨平台渲染引擎(番外一)——光和相机

从零开始的跨平台渲染引擎(番外一)——光和相机

从零开始的跨平台渲染引擎(番外一)——光和相机

闭关学pwn番外——入门gdb

video.js学习笔记

Building Worlds In Unreal 学习笔记——番外1:自制交互水面