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
我自己遇到两种情况引起的这个报错:
- 视频源的格式或编码方式video.js真的不支持 - 这种情况只能调整视频源,转码或者替换其他前端视频播放控件。
我当时遇到的情况是: 起初不太懂同样是MP4文件,还有编码方式的区别。幸而有多个视频源,有的可以播放,有的不可以播放。点开文件属性进行对比,发现了编码方式的区别,即上述不支持MPEG4
编码的问题。 - 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框架有些水土不服。所以花了些时间在踩坑和排坑上。如果一开始就看了官方的示例,就能省些时间。另外遇到及处理视频编码的问题,也额外学习到了一些相应的知识。
从零开始的跨平台渲染引擎(番外一)——光和相机
前言
基于物理渲染中使用的光和相机也需要符合物理性质,那么首先需要确定在物理世界中,光和相机是如何描述的。而其中最核心的,就是如何划定参与计算的数值类型和范围。
本文主要阐述了物理世界中,光如何量定,相机参数含义,以及它们如何参与到着色计算,以及实际应用。
光
光的单位
光学术语 | 标记 | 单位 | 定义 |
---|---|---|---|
光通量 Luminous flux / Luminous power | Φ \\mathrm\\Phi Φ | 流明 Lumen ( l m = c d ⋅ s r \\mathrmlm=cd\\cdot sr lm=cd⋅sr) | 单位时间内由光源所发出或由被照物所吸收的总光能 |
发光强度、光度 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)E⊥⟨n⋅l⟩
输出值的单位是亮度单位,尼特(
n
i
t
\\mathrmnit
nit)。
一般用方向光模拟日光、月光,下表是实际测量晴天时的照度:
光源 | 10:00 am | 12:00 pm | 5: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=d2I⟨n,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)d2I⟨n,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,l⟩dl
其中,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
10−5cd/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=EV100−log2(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视频播放组件的入门及排坑经历的主要内容,如果未能解决你的问题,请参考以下文章