视频文件格式
视频文件格式是指视频文件的编码和压缩方式,
1.容器和编解码器是任何视频文件的两个组成部分。
- 视频格式是存储音频,视频,字幕和任何其他源数据的容器。
- 编解码器是对音频和视频之类的多媒体数据进行编码和解码。
2.创建视频时,视频编解码器对视频进行编码和压缩,而音频编解码器对声音也是如此。 之后,编码的视频和音频将同步并存储在媒体容器(文件格式)中。
常见在线流媒体格式:mp4、flv、f4v、webm
移动设备格式:m4v、mov、3gp、3g2
RealPlayer :rm、rmvb
微软格式 :wmv、avi、asf
MPEG 视频 :mpg、mpeg、mpe、ts
DV格式 :div、dv、divx
其他格式 :vob、dat、mkv、lavf、cpk、dirac、ram、qt、fli、flc、mod
选择视频格式之前,请考虑以下情况:
1.对于在线视频,请选择大多数网络浏览器支持的文件格式。 这样,您的视频将在浏览器上本地播放。 MP4和WEBM是与浏览器兼容的视频格式。
2.对于家庭视频录像,请选择高质量的视频格式,以备将来使用。 开源文件格式比企业控制的专有格式更具面向未来性。 MP4或AVI格式非常适合该类别。
3.对于Windows应用程序,请选择与Windows兼容的格式。 在这种情况下,WMV是一个不错的选择。
m3u8
m3u8是一种视频流媒体播放列表文件格式,常用于将视频内容分割成多个小文件并进行流式传输。
现在的视频网站采用的是流媒体传输协议,将一段视频切成无数个小段,这几个小段就是ts格式的视频文件,一段一段的网站上播放。
- 服务器采集编码传输视频到切片器
- 切片器对视频创建索引文件,并切割成 n 个 ts 文件
- 这两个文件传输到 http 服务器上
- 网站/客户端根据索引文件查找 http 服务器上的 ts 文件,连续播放这 n 个 ts 文件,就可以了。
索引文件里面存储着ts文件的网络url链接,网站需要拿到索引文件,去按照url链接下载在http服务器中的ts文件。拿到了ts文件之后,本身这些ts文件就是原视频中的一小段视频,所有ts文件下载、顺序播放,就完成了整个视频的播放。而索引文件就是 m3u8 文件。
m3u8是文件记录了一个索引纯文本文件,打开它时播放软件并不是播放它,而是根据它的索引找到对应的音视频文件的网络地址进行在线播放。这种格式通常用于在网络上实现实时视频直播和点播服务,可以通过多种设备和平台进行播放。
Hls
HLS是苹果公司开发的一种流媒体传输协议,全称为HTTP Live Streaming,用于实时流传输的协议。
它通过将整个视频分割成多个小的ts文件,每个文件都有自己的URL地址,然后通过HTTP协议进行传输,实现了在网络上的流式传输。
HLS协议支持多种码率和分辨率的视频,可以自适应地根据用户的网络环境和设备性能进行调整,提供更好的观看体验。
hls.js是一个JavaScript库,可实现HTTP Live Streaming客戶端。 它依靠HTML5视频和MediaSource扩展进行播放。
它通过将MPEG-2传输流和AAC / MP3流转换为ISO BMFF(MP4)片段来工作。 如果在浏览器中可用,可以使用Web Worker异步执行此转换。hls.js还支持HLS + fmp4,不需要任何播放器,它可以直接在标准HTML 元素上运行。
video.js 播放 m3u8
在项目中可以使用 video.js 播放m3u8格式的视频文件文件, 同时video.js也对vue3提供支持。
顺便感慨一句 video.js 的文档我真的看不懂😭
首先在项目中安装video.js
yarn add video.js videojs-contrib-hls
初始化video.js
<template>
<div>
<div>
<video id="my-video" class="video-js vjs-default-skin" preload="auto">
<source :src="vodeoUrl" type="application/x-mpegURL">
</video>
<div>
</div>
</template>
<script lang="ts" setup>
// 定义 videojs 实例
const myPlayer = ref()
const playVideo = reactive({
videoUrl: 'xxxx.m3u8',
})
onMounted(()=>{
const videoUrl ="xxx.example.m3u8"
initVideoSource(videoUrl)
})
function initVideoSource () {
myPlayer.value = videojs('my-video', {
controls: true, // 是否显示控制条
preload: 'auto',
autoplay: false,
loop: false, // 循环播放
// fluid: true, // 自适应宽高
language: 'zh-CN', // 设置语言
muted: false, // 是否静音
inactivityTimeout: false,
// 设置控制条组件
controlBar: { // 设置控制条组件
// 设置控制条里面组件的相关属性及显示与否
currentTimeDisplay: true,
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
volumePanel: {
inline: false,
},
pictureInPictureToggle: false,
// 或者使用 children 定义
/* 使用children的形式可以控制每一个控件的位置,以及显示与否 */
// children: [
// { name: 'playToggle' }, // 播放按钮
// { name: 'currentTimeDisplay' }, // 当前已播放时间
// { name: 'progressControl' }, // 播放进度条
// { name: 'durationDisplay' }, // 总时间
// {
// name: 'volumePanel', // 音量控制
// inline: false, // 不使用水平方式
// },
// { name: 'FullscreenToggle' }, // 全屏
// ],
},
sources: [
{
src: playVideo.videoUrl,
type: 'application/x-mpegURL',
},
],
userActions: {
seek: false,
},
})
}
</script>
video.js 语言设置
引入 zh-CN.json 语言包后在配置项中设置中文即可,亲测有效
import videojs from "video.js"
import "video.js/dist/video-js.css"
import video_zhCN from 'video.js/dist/lang/zh-CN.json'
videojs.addLanguage('zh-CN', video_zhCN)
在设置里配置成中文:language:“zh-CN”
<template>
<div>
<video
id="my-video" class="video-js vjs-default-skin video" preload="auto">
</video>
</div>
</template>
<script lang="ts" setup>
import videojs from 'video.js/dist/video.min'
import 'video.js/dist/video-js.min.css'
import videoLanguage from 'video.js/dist/lang/zh-CN.json'
// videojs 语言设置
videojs.addLanguage('zh-CN', videoLanguage)
const myPlayer = ref()
const playVideo = reactive<videoPlayType>({
videoUrl: 'xxxx.m3u8',
})
onMounted(() => {
initVideoSource()
})
function initVideoSource () {
videojs.addLanguage('zh-CN', videoLanguage)
const myPlayer = videojs('my-video', {
controls: true, // 是否显示控制条
preload: 'auto',
autoplay: false,
fluid: true, // 自适应宽高
language: 'zh-CN', // 设置语言
muted: false, // 是否静音
inactivityTimeout: false,
controlBar: { // 设置控制条组件
children: [
{ name: 'playToggle' }, // 播放按钮
{ name: 'currentTimeDisplay' }, // 当前已播放时间
{ name: 'progressControl' }, // 播放进度条
{ name: 'durationDisplay' }, // 总时间
{
name: 'volumePanel', // 音量控制
inline: false, // 不使用水平方式
},
{ name: 'FullscreenToggle' }, // 全屏
],
},
sources: [ // 视频播放资源
{
src: playVideo.videoUrl,
type: 'application/x-mpegURL',
},
],
})
// 自动播放
myPlayer.play()
}
</script>
video.js 触发事件汇总
在 video 上的监听事件
| 事件 | 描述 |
|---|---|
| loadstart | 浏览器开始在网上寻找媒体数据 |
| progress | 浏览器正在获取媒体数据 |
| loadedmetadata | 浏览器获取完毕媒体的时间长和字节数 |
| play | 即将开始播放,当执行了play方法时触发,或数据下载后元素被设为autoplay属性 |
| pause | 播放暂停,当执行了pause方式时触发 |
| waiting | 播放过程由于得不到下一帧而暂停播放(例如下一帧尚未加载完毕),但很快就能够得到下一帧 |
| timeupdate | 由于播放位置被改变,可能是播放过程中的自然改变,也可能是被人为的改变,或由于播放不能连续而发生的跳变 |
| ended | 播放结束后停止播放 |
| canplay | 浏览器能够播放媒体,但估计以当前的播放速率不能直接播放完毕,播放期间需要缓冲 |
| canplaythrough | 浏览器能够播放媒体,而且以当前播放速率能够将媒体播放完毕,不再需要进行缓冲 |
| seeking | seeking属性变为true,浏览器正在请求数据 |
| seeked | seeking属性变为false,浏览器停止请求数据 |
| suspend | 浏览器暂停获取媒体数据,但是下载过程并滑正常结束 |
| ratechange | defaultplaybackRate属性(默认播放速率)或playbackRate属性(当前播放速率)被改变 |
| druationchange | 播放时长被改变 |
| stalled | 浏览器尝试获取媒体数据失败 |
| abort | 浏览器在下载完全部媒体数据之前中止获取媒体数据,但是并不是由错误引起的 |
| error | 获取媒体数据过程中出错 |
| emptied | video元素或audio元素所在网络突然变为未初始化状态可能原因有两个:1.载入媒体过程中突然发生一个致命错误 |
| volumechange | volume属性(音量)被改变或muted属性(静音状态)被改变 |
| fullscreenchange | 监听全屏事件 |
设置监听的方式一:在video 的dom元素上绑定事件监听
<template>
<div>
<video
id="my-video" class="video-js vjs-default-skin video" preload="auto"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
@ended="onPlayerEnded($event)"
@loadeddata="onPlayerLoadeddata($event)"
@waiting="onPlayerWaiting($event)"
@playing="onPlayerPlaying($event)"
@timeupdate="onPlayerTimeupdate($event)"
@seeking="onSeek($event)">
</video>
</div>
</template>
<script>
function onPlayerPlay(player:any){
console.log('即将开始播放', player.target)
}
function onPlayerLoadeddata (player:any) {
// 获取视频总时长,只能在视频加载完成之后获取
const totalTime = player.target.duration
console.log('playVideo play!', playVideo)
}
</script>
监听方式二:
function initVideoSource () {
// videojs 语言设置
videojs.addLanguage('zh-CN', videoLanguage)
myPlayer.value = videojs('my-video', {
controls: true, // 是否显示控制条
preload: 'auto',
autoplay: false,
loop: false, // 循环播放
// fluid: true, // 自适应宽高
language: 'zh-CN', // 设置语言
muted: false, // 是否静音
inactivityTimeout: false,
// 设置控制条组件
controlBar: {
children: [
{ name: 'playToggle' }, // 播放按钮
{ name: 'currentTimeDisplay' }, // 当前已播放时间
{ name: 'progressControl' }, // 播放进度条
{ name: 'durationDisplay' }, // 总时间
{ name: 'remainingTimeDisplay' }, // 剩余时间
{
name: 'volumePanel', // 音量控制
inline: false, // 不使用水平方式
},
{ name: 'FullscreenToggle' }, // 全屏
],
},
sources: [
{
src: url,
type: 'application/x-mpegURL',
},
],
userActions: {
seek: false,
},
})
myPlayer.value.on('loadedmetadata', function () {
// 视频播放定位到上次播放时间
myPlayer.value.currentTime(playVideo.currentSecond)
})
// 视频播放结束
myPlayer.value.on('ended', async () => {
// 提交视频记录 更新视频信息
setCurrentVideo()
getCurrentCourse()
})
// 暂停时提交
myPlayer.value.on('pause', () => {
setCurrentVideo()
})
// 监听全屏按钮事件
myPlayer.value.on('fullscreenchange', () => {
console.log('全屏')
})
暂停播放时展示按钮
通过样式控制,vjs-paused 下的按钮设置为 display:block
// 播放暂停时 展示暂停按钮
::v-deep .vjs-paused > .vjs-big-play-button {
display: block;
}
获取视频总时长
只能在视频加载完之后获取
function onPlayerLoadeddata (player:any) {
// 获取视频总时长,只能在视频加载完成之后获取
const totalTime = player.target.duration
console.log('playVideo play!', playVideo)
}
即时记录播放时间
监听播放进度,获取当前播放事件,通过取余的方式判断当前经过的时间,每过 n 秒向服务器提交一次时间记录用户当前播放的进度
//播放时长(秒)
var totalTime = 0;
myPlayer.on('timeupdate', function() {
//当前播放时长(秒)
var currentTime = Math.floor(myPlayer.currentTime());
if (currentTime > 0 && currentTime > totalTime && (currentTime % 5 == 0)) {
//每隔5秒,向服务器提交播放时间(秒)
}
totalTime = currentTime;
});
禁止视频拖拽、阻止用户快进
只允许用户在当前已经播放的时间内调整观看进度,不允许用户快进。
通过 timeupdate 获取当前视频播放的时间差,当时间小于一秒的属于视频的正常播放,否则为人为的拖拽快进
const myPlayer = ref()
function initVideoSource () {
// 在video实例初始化时监听视频播放时长更新
myPlayer.value.on('timeupdate', async () => {
// videojs 播放的当前位置
const currentTime = myPlayer.value.currentTime()
if (currentTime > 0 && Math.ceil(currentTime) - 1 === playVideo.currentSecond && (Math.ceil(currentTime) % 15 === 0)) {
// 每隔15秒,向服务器提交播放时间(秒)
await setCurrentVideo()
}
// 禁止用户拖拽
if (currentTime > playVideo.maxSecond) {
const num = Math.ceil(currentTime) - Math.ceil(playVideo.maxSecond)
if (num <= 1) {
// 正常播放时,记录当前播放时间
playVideo.maxSecond = Math.ceil(currentTime)
playVideo.currentSecond = Math.ceil(currentTime)
} else {
// 差值大于1s时,视为拖动,将上一次记录的播放时间赋值给当前播放器的播放时间
myPlayer.value.currentTime(playVideo.currentSecond)
}
}
})
}
}
播放视频添加水印
通过获取视频播放的 dom 元素,在 video 组件中手动插入水印图层的 div 方式实现添加水印。
局部添加水印
// 设置局部水印
export function setSingleWaterMark (documentRef:any, str: any) {
// 获取 video 外层 dom 元素 在其中设置水印
const videoElement = documentRef.value
const videoChild = videoElement.firstChild as HTMLElement
const waterMark = document.createElement('div')
waterMark.id = 'userName'
waterMark.setAttribute('class', 'resize-drag')
// 从父组件传过来的水印内容
waterMark.innerText = str
waterMark.style.cssText = 'position:absolute;top:24px;right:24px; color:#c4c6cb; font-size:20px; cursor: default;'
videoChild.appendChild(waterMark)
}
全局添加水印
设置全局水印的方式与局部水印相似,基于 canvas 实现
- 通过 canvas 创建画布,绘制出一张文字填充的图片
- 设置好样式后将图片导出作为水印层的背景图平铺
- 最后将背景图插入准备好的dom元素中
// 设置全局水印
export const setFullWaterMark = (documentRef:any, str: any) => {
const id = 'waterMark'
const videoElement = documentRef.value
const videoChild = videoElement.firstChild as HTMLElement
const can = document.createElement('canvas')
// 设置画布的长宽
can.width = 300
can.height = 300
const cans = can.getContext('2d')!
// 旋转角度
cans.rotate((-20 * Math.PI) / 180)
cans.font = '16px Vedana'
// 设置填充绘画的颜色、渐变或者模式
cans.fillStyle = 'rgba(196, 198, 203,.4)'
// 设置文本内容的当前对齐方式
cans.textAlign = 'left'
// 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
cans.fillText(str, can.width / 8, can.height / 2)
const div = document.createElement('div')
div.id = id
div.style.pointerEvents = 'none'
div.style.top = '0px'
div.style.left = '0px'
div.style.position = 'absolute'
div.style.zIndex = '100000'
div.style.width = document.documentElement.clientWidth + 'px'
div.style.height = screen.height + 'px' // 全屏时的屏幕高度
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'
videoChild.appendChild(div)
}
由于视频当前水印的大小获取的是浏览器的宽高,能够适用于播放器全屏的情况下,
在浏览器未全屏的情况下可以用 overflow: hidden 控制水印的范围
在初始化 video 组件时添加水印
function initVideoSource () {
....
// 设置视频全屏水印
setFullWaterMark('水印名称xxx')
//设置局部水印
setWaterMark()
}
视频不能自动播放
在使用videojs播放视频的过程可能会存在的问题。
报错 DOMException: play() failedbecause the user didn’t interact with the document first. (用户还没有交互,不能调用play)
在最新版的Chrome浏览器(以及所有以Chromium为内核的浏览器)中,已不再允许自动播放音频和视频。就算你为video或audio标签设置了autoplay属性也一样不能自动播放, 所以浏览器在页面没有产生用户交互时时不能进行自动播放
解决方案 : 设置muted属性为true
<video id="videoPlayer" class="video-js" muted></video>
关于muted属性:
muted属性,设置或返回音频是否应该被静音(关闭声音);属性的值是true和false;
muted="false"表示视频不用静音(视频播放便有声音),但设置muted="fasle"的情况下,视频无法实现自动播放。
video标签中muted的作用: 允许视频自动播放;(Chrome66版本开始,禁止视频和音频的自动播放)