xgplayer-hls
xgplayer-hls 可用于播放 HLS 的点播和直播视频流。它可以作为 xgplayer 插件集成到 xgplayer 使用,或以内核形式单独使用。
支持的功能
- master m3u8 和 media m3u8 描述文件
- 点播和直播
- TS 格式视频文件
- CMAF/FMP4 格式视频文件
- H264 和 H265 编码格式(H265支持解析,能否播放取决于浏览器是否支持H265编码格式)
- TS 格式的 SEI 解析
- AAC 音频编码
- AES-128 CBC 加密
安装
你可以通过 NPM 和 CDN 两种方式安装使用 xgplayer-hls。
- NPM 方式安装使用(推荐)
运行下面这个命令进行安装。
npm i -S xgplayer-hls
然后就可以使用了。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
new Player({
plugins: [HlsPlugin]
})
- CDN 方式安装使用
在 HTML 文件中加入如下代码。
<script src="//unpkg.com/xgplayer-hls@latest/dist/index.min.js"></script>
然后就可以直接在 window 上访问了window.HlsPlayer
。
<script>
new Player({
plugins: [window.HlsPlayer]
})
</script>
快速开始
xgplayer-hls 作为 xgplayer 插件使用,需要引入 xgplayer。如果不了解 xgplayer,可以先去查看相关文档,这里不再重复。
作为 xgplayer 插件,播放 HLS 流只需两步。
- 检测当前环境是否支持。
- 将 HLS 插件传给 xgplayer 的 plugins 参数。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
let player
if (document.createElement('video').canPlayType('application/vnd.apple.mpegurl')) {
// 原生支持 hls 播放
player = new Player({
el: document.querySelector('.player'),
url: 'test.m3u8'
})
} else if (HlsPlugin.isSupported()) { // 第一步
player = new Player({
el: document.querySelector('.player'),
isLive: false,
url: 'test.m3u8', // hls 流地址
plugins: [HlsPlugin] // 第二步
})
}
上面代码中首先会判断当前环境是否原生支持 hls 播放(如 IOS 系统)如果支持则直接播放 HLS 流而不使用 HLS 插件, 否则检测当前环境是否支持 xgplayer-hls,支持的话通过插件播放
指南
参数
HLS 插件还可以接收一些参数,这些参数需要通过 hls
参数对象传递给 HLS 插件。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
if (HlsPlugin.isSupported()) {
const player = new Player({
plugins: [HlsPlugin],
hls: {
// hls 插件参数
}
})
}
配置网络请求行为
一共有 4 个参数可以配置网络请求的行为。你可以通过下面 4 个参数控制请求重试次数、重试间隔、请求超时时间和 fetch 参数。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
if (HlsPlugin.isSupported()) {
const player = new Player({
plugins: [HlsPlugin],
hls: {
retryCount: 3, // 重试 3 次,默认值
retryDelay: 1000, // 每次重试间隔 1 秒,默认值
loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
fetchOptions: {
// 该参数会透传给 fetch,默认值为 undefined
mode: 'cors'
}
}
})
}
配置直播行为
一共有 3 个参数为直播特有参数,它们分别控制直播的目标延迟、最大延迟和自动断流。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
if (HlsPlugin.isSupported()) {
const player = new Player({
url: 'live.m3u8',
isLive: true,
plugins: [HlsPlugin],
hls: {
targetLatency: 10, // 直播目标延迟,默认 10 秒
maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
disconnectTime: 0, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
}
})
}
targetLatency
和 maxLatency
关系是,当直播延迟超过 maxLatency
时,hls 就会将当前播放时间点跳转到 targetLatency
位置。所以配置这两个值的时候需要确保 maxLatency
大于 targetLatency
,并且应该大很多,默认值是大两倍。
disconnectTime
是当直播暂停后,直播延迟超过该值就会断流,作为插件使用时该值是 0
秒,用户暂停直播后就会断流,点击播放会重新拉流。但是用户频繁的暂停播放,就会导致频繁的断流和拉流,设置该值可以让短时间的停止播放不断流。
针对点播场景,preloadTime
指定允许的预加载buffer的最大长度(单位s)
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
if (HlsPlugin.isSupported()) {
const player = new Player({
url: 'vod.m3u8',
isLive: false,
plugins: [HlsPlugin],
hls: {
preloadTime: 30 // 默认值
}
})
}
更多 HLS 参数,请查看 API 参数章节。
监听事件
HLS 插件会对外抛出一些事件,HLS 插件会通过 xgplayer 对外抛出事件,要监听 HLS 事件,需要监听 xgplayer 上的 core_event
事件。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
const player = new Player({
plugins: [HlsPlugin]
})
player.on('core_event', ({ eventName, ...rest }) => { // eventName: hls 事件名; rest: hls 事件回调函数参数
...
})
HLS 会抛出的事件及回调函数参数,请查看 API 事件章节。
处理错误
如果发生错误,HLS 插件会通过 xgplayer 的 error
事件,抛出对应错误。错误对象是 xgplayer 中的 Errors
对象。只要 HLS 抛出 error
事件,HLS 内部就会断流并结束视频(可能会触发 ended
事件)。错误类型细化见API 错误描述。
import Player, { Events } from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
const player = new Player({
plugins: [HlsPlugin]
})
player.on(Events.ERROR, (error) => {
error // xgplayer 中的 Errors 对象
})
独立使用
在实际的应用场景中,我们会希望使用视频播放器的能力,但不希望与播放器UI库绑定。因此这里我们提供独立使用的方式,仅包含功能,不引入UI。
独立使用方式。
import { Hls } from 'xgplayer-hls'
if (Hls.isSupported()) { // 检测是否支持,和 HlsPlugin 是一个方法
const video = document.createElement('video')
document.body.append(video)
const hls = new Hls({
media: video
// hls 参数
})
hls.load('m3u8 url') // 进行拉流
}
// ----- 自动创建 video 元素
const hls = new Hls()
hls.load('m3u8 url')
document.body.append(hls.media) // hls.media 就是通过参数传入或自动创建的 video 元素
你可以直接通过 Hls 创建一个实例,从而脱离 xgplayer 独立使用。你只需要提供一个 video
元素,如果不提供内部会自动创建一个 video
元素。
值得一提的是,在以插件方式使用 HLS 播放器时,你可以通过 player.plugins.hls.core
访问到hls内核实例,它就是上文中我们独立使用场景下创建的 hls 实例。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
if (HlsPlugin.isSupported()) {
const player = new Player({
el: document.querySelector('.player'),
url: 'test.m3u8',
isLive: true,
plugins: [HlsPlugin]
})
const hls = player.plugins.hls.core
console.log(hls) // hls 上有很多有用的方法和属性
}
你可以通过 HlsPlugin.Hls
或直接导入 Hls 访问到 hls 对象的构造类。
import HlsPlugin, { Hls } from 'xgplayer-hls'
console.log(HlsPlugin.Hls === Hls) // true
hls 对象会抛出很多事件和错误,其实就是作为插件使用时抛出的事件和错误。HlsPlugin 只是将这个对象抛出的事件和错误代理到了 xgplayer。
import { Hls, EVENT } from 'xgplayer-hls'
const hls = new Hls()
const onError = (error) => {}
hls.on(EVENT.ERROR, onError) // 监听错误事件
hls.off(EVENT.ERROR, onError) // 取消监听错误事件
hls.once(EVENT.LOAD_START, (event) => { // 只监听一次开始加载事件
// ...
})
API
参数
在播放器初始化时,我们可以给播放器传入插件参数以开启不同的播放行为、能力。
import Player from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
const player = new Player({
plugins: [HlsPlugin],
hls: {} // hls播放参数
})
具体参数如下。
参数名 | 默认值 | 描述 |
---|---|---|
bufferBehind | 10 | 允许当前时间点之前的缓存时长,时间点小于该值的缓存将被清除。 |
preloadTime | 30 | [点播]预加载时长,单位秒 |
maxJumpDistance | 3 | 最大跳洞距离,单位秒 |
retryCount | 3 | 网络请求重试次数 |
retryDelay | 1000 | 网络请求重试间隔,单位毫秒 |
loadTimeout | 10000 | 网络请求超时时间,单位毫秒 |
fetchOptions | window.fetch 方法的参数 | |
startTime | 0 | [点播] 开始播放时间,单位秒 |
targetLatency | 10 | [直播] 目标延迟,单位秒 |
maxLatency | 20 | [直播] 最大延迟,超过该延迟会直接 seek 到 targetLatency 位置,单位秒 |
disconnectTime | 0 | [直播] 当暂停停止播放时,多久断流。作为 xgplayer 插件使用时默认值为 0,单位秒 |
独立使用场景
独立使用场景下,上述的hls 参数是全部支持的,另外加入了几个独立使用场景的参数。
import { Hls } from 'xgplayer-hls'
if (Hls.isSupported()) { // 检测是否支持
const video = document.createElement('video')
document.body.append(video)
const hls = new Hls({
media: video
// hls参数在这里传入
})
hls.load('{hls_url}')
}
独立使用场景下需要额外传入的参数
参数名 | 默认值 | 描述 |
---|---|---|
media | HTMLMediaElement。如果没有该参数将会自动创建 video 元素,可以通过 hls.media 访问到该值 | |
url | hls url 地址,如果不在创建时,则必须在调用 load 方法时提供 | |
isLive | false | 是否是直播 |
属性
可以通过 player.plugins.hls.core.属性名
的方式访问当前播放器的属性。
属性名 | 参数 |
---|---|
media | 传入的 media 参数 (HTMLMediaElement | null ) |
isLive | 是否是直播 |
version | 当前 xgplayer-hls 库的版本号 |
当我们独立使用时,访问属性的方式为 hls.属性名
,直接从独立使用生成的 hls 实例获取属性即可。
方法
下面是 hls 播放器提供的方法,我们可以通过如下方式进行方法的调用。
import Player, { Events } from 'xgplayer'
import HlsPlugin from 'xgplayer-hls'
const player = new Player({
plugins: [HlsPlugin]
})
// 开始拉流或者后续播放阶段时获取
player.on(Events.LOAD_START, () => {
console.log(player.plugins.hls.core.speedInfo()) // 调用方法
})
speedInfo
speedInfo(): {speed: number, avgSpeed: number}
获取当前下载速度信息,单位是 bit/s。speed
是最新的速度,avgSpeed
是近几次速度统计的平均值。
这里统计的速度信息可能不准确,因为在有缓存的情况下可能获取一个非常快的速度。如果需要获取最原始的速度,自己处理的话可以不使用该 api,而是监听 EVENT.SPEED
,该事件详情请查看 API 事件章节。
getStats
getStats(): StatsInfo
StatsInfo
对象属性如下。
属性 | 类型 | 描述 |
---|---|---|
encodeType | string | 视频编码格式 |
audioCodec | string | 音频codec字符串描述,类似mp4.40.2 |
videoCodec | string | 视频codec字符串描述,类似avc1.640033 |
domain | string | 拉流域名 |
fps | number | 视频帧率 |
bitrate | number | 视频实时码率 |
width | number | 视频分辨率宽度 |
height | number | 视频分辨率高度 |
samplerate | number | 音频采样率 |
channelCount | number | 音频channel数量 |
downloadSpeed | number | 实时下载速度 |
avgSpeed | number | 平均速度 |
currentTime | number | 当前播放时间点 |
bufferEnd | number | 当前buffer缓存长度 |
bufferInfo
bufferInfo(): BufferInfo
获取当前缓存信息,单位是秒。
BufferInfo
对象属性如下。
属性 | 类型 | 必须存在 | 描述 |
---|---|---|---|
currentTime | number | 否 | 当前视频播放时间 |
buffers | [[number, number]] | 是 | 当前 buffer 数组,二维结构,[[开始时间,结束时间]] |
index | number | 否 | 当前视频播放时间命中缓存,在 buffers 中的下标 |
start | number | 否 | 当前视频播放时间命中缓存的开始时间 |
end | number | 否 | 当前视频播放时间命中缓存的结束时间 |
nextStart | number | 否 | 下一个缓存的开始时间 |
nextEnd | number | 否 | 下一个缓存的结束时间 |
prevStart | number | 否 | 上一个缓存的开始时间 |
prevEnd | number | 否 | 上一个缓存的开始时间 |
behind | number | 否 | 当前视频时间点直接缓存的长度 = currentTime - start |
remaining | number | 否 | 当前视频时间点后方缓存长度 = end - currentTime |
length | number | 否 | 当前是缓存总时长 = behind + remaining |
playbackQuality
playbackQuality(): PlaybackQuality
获取当前渲染帧信息(根据平台不同可能会没有值)。
PlaybackQuality
对象属性如下。
属性 | 类型 | 必须存在 | 描述 |
---|---|---|---|
droppedVideoFrames | number | 否 | 掉帧数量 |
totalVideoFrames | number | 否 | 总渲染帧 |
creationTime | number | 否 | 当前获取帧信息的时间点,毫秒 |
通过 creationTime
和 totalVideoFrames
可以计算出当前的渲染 fps。
let last = hls.playbackQuality()
fps = 0
setInterval(() => {
const cur = hls.playbackQuality()
if (last) {
fps = Math.round((cur.totalVideoFrames - last.totalVideoFrames) / (cur.creationTime - last.creationTime) * 1000)
}
last = cur
}, 1000)
load
load(url?: string): Promise<void>
加载或重新加载数据。如果没有提供构造函数 url
参数时,这个 url
为必传参数。
const fn = async () => {
await hls.load() // 第一个视频数据加载好后会 resolve
// 第一个视频数据已经加载完毕
}
replay
replay(): Promise<void>
重新播放或重新拉流。调用该方法内部会销毁从头开始拉流播放。
switchURL
switchURL(url: string, startTime = 0): Promise<void>
该方法用于切换视频,第一个参数为新视频地址,第二个参数是新的视频开始播放时间(只在点播生效)。
该方法会在切换视频结束后触发两个事件 SWITCH_URL_SUCCESS
和 SWITCH_URL_FAILED
,切换成功和切换失败。
destroy
destroy(): Promise<void>
销毁 hls 实例。
[静态] isSupported
static isSupported(): boolean
用于判断当前环境是否支持 hls 播放
事件
下面是 hls 对象对外抛出的事件。作为插件使用时,需要监听 xgplayer 的 core_event
事件。
import Player from 'xplgayer'
import HlsPlugin, { Hls, EVENT } from 'xgplayer-hls'
// 作为插件使用
const player = new Player({
plugins: [HlsPlugin]
})
player.on('core_event', ({ eventName, ...rest }) => { // eventName: hls 事件名; rest: hls 事件回调函数参数
// 通过判断eventName来区分内核事件
if (eventName === EVENT.LOAD_START) {
// ...
}
})
hls 对外暴露 EVENT
常量,它是事件名常量,可以使用它来监听事件。
import { Hls, EVENT } from 'xgplayer-hls'
const hls = new Hls()
hls.on(EVENT.TTFB, () => {})
TTFB
分片请求开始到接收到请求响应。回调函数参数对象属性如下。
属性 | 类型 | 必须存在 | 描述 |
---|---|---|---|
url | string | 是 | 请求 url |
responseUrl | string | 是 | 响应 url |
elapsed | number | 是 | 耗时,毫秒 |
LOAD_START
分片在发送请求之前触发,参数为 { url: string },url 为请求 url 。
LOAD_RESPONSE_HEADERS
接收到请求响应头时触发,参数为 { headers: Headers | Recored<string, string>}
。如果当前环境支持 fetch 则 headers 为 Response.headers,否则是普通对象。
LOAD_COMPLETE
在请求完成后触发,参数为 { url: string }
,url 为请求 url 。
HLS_MANIFEST_LOADED
主从m3u8格式时,master m3u8文件加载并解析完成后, 抛出master m3u8解析后的结构, 参数为 { playlist: MasterPlaylist }
。
type MasterPlaylist = {
isMaster: boolean,
streams: Array<MasterStream>
}
type MasterStream = {
id: number
bitrate: number
width: number
height: number
name: string
url: string
audioCodec: string
videoCodec: string
textCodec: string
audioGroup: string
audioStreams: Array<AudioStream>
}
type AudioStream = {
id: number
url: string
group: string
name: string
lang: string
channels: number
segments: Array<MediaSegment>
}
type MediaSegment = {
sn: boolean // Media Sequence Number
cc: boolean // discontinue number index
url: string
title: string
start: string // segment start time(/s)
duration: string // segment duration(/s)
isInitSegment: boolean
initSegment: MedaiSegment
isLast: boolean
}
HLS_LEVEL_LOADED
二级m3u8加载并解析完成后,抛出解析后的结构, 参数为 { playlist: MediaPlaylist }
。
type MediaPlaylist = {
version: number
url: string
type: string
startCC: number
endCC: number
startSN: number
endSN: number
totalDuration: number
targetDuration: number
live: boolean
segments: Array<MediaSegment>
}
type MediaSegment = {
sn: boolean // Media Sequence Number
cc: boolean // discontinue number index
url: string
title: string
start: string // segment start time(/s)
duration: string // segment duration(/s)
isInitSegment: boolean
initSegment: MedaiSegment
isLast: boolean
}
LOAD_RETRY
请求发生重试时触发。参数如下。
属性 | 类型 | 必须存在 | 描述 |
---|---|---|---|
error | StreamingError | 是 | 网络错误对象,请查看 API 错误章节 |
retryTime | number | 是 | 第几次重试 |
SPEED
当收集到网络速度统计时触发,参数如下。
属性 | 类型 | 必须存在 | 描述 |
---|---|---|---|
time | number | 是 | 请求耗时,毫秒 |
byteLength | number | 是 | 下载的字节数 |
KEYFRAME
解析到关键帧触发,参数为 { pts: numer }
,pts 为修正后的 pts。
METADATA_PARSED
视频元数据被第一次解析到时触发参数被分为两种类型 video
和 audio
,它们的具体属性也不同。
interface EventVideo {
type: 'video';
meta: {
codec: string; // 视频编码字符串
timescale: number; // 视频原始时间尺度
width: number; // 视频宽度
height: number; // 视频高度
baseDts?: number; // 视频轨道到的 base dts,会在元数据变化的时候可能不同
sarRatio?: [number, number]; // 视频宽高比
}
}
interface EventAudio {
type: 'audio';
meta: {
codec: string; // 音频编码字符串
channelCount: number; // 音频通道数
sampleRate: number; // 音频采样率
}
}
SEI
当解析到视频 sei 时触发,参数如下:
interface Event {
originPts: number; // 原始 pts
pts: number; // 修正后的 pts, 以毫秒为单位,如果需要以TS格式timescale(90000)为单位的时间戳信息,使用时此值 * 90
time: number; // 该 sei 在当前视频中的时间点,单位秒
data: {
payload: Uint8Array; // sei 数据
type: number; // sei 类型
size: number; // sei 大小
uuid: string; // sei uuid,不存在则为空字符串
},
sei: { // 弃用,不建议使用该属性
code: number; sei 类型
content: Uint8Array; // sei 数据
dts: number; // sei 修正后的 pts
}
}
SEI_IN_TIME
根据当前视频播放时间抛出 sei,触发该事件表示该 sei 将在当前时间点展示。
它的回调函数参数同 SEI 事件,但是它不包含 sei 属性。
SWITCH_URL_SUCCESS
switchURL
方法调用后,切换 url 成功后触发。
SWITCH_URL_FAILED
switchURL
方法调用后,切换 url 失败后触发。
STREAM_EXCEPTION
当解析音视频时,发生错误时触发。一共有 6 种类型的解析异常,它们通过参数的 type
属性进行区别。
hls.on(EVENT.STREAM_EXCEPTION, ({ type, ...info }) => {
type // 异常类型
info // 异常详情
})
type
可以是全大写下划线分隔的字符串,也可以使用 EVENT
常量找到对应字符串。
import { EVENT } from 'xgplayer-hls'
EVENT.LARGE_AV_FIRST_FRAME_GAP_DETECT === 'LARGE_AV_FIRST_FRAME_GAP_DETECT'
// true
LARGE_AV_FIRST_FRAME_GAP_DETECT
音视频偏移过大。详情对象属性如下。
videoBaseDts
视频第一帧的 dtsaudioBasePts
音频第一帧的 ptsbaseDts
等于Math.min(audioBasePts, videoBaseDts)
delta
等于videoBaseDts - audioBasePts
LARGE_VIDEO_DTS_GAP_DETECT
视频两帧之间间隔过大。详情对象属性如下。
time
当前帧的解码时间(秒),与 video 元素时间轴一致dts
修改后的 dtsoriginDts
原始 dtssampleDuration
该帧的时长refSampleDuration
参考帧时长
LARGE_AUDIO_DTS_GAP_DETECT
两帧音频间隙过大,不会进行补针。详情对象属性如下。
time
当前帧的播放时间(秒),与 video 元素时间轴一致pts
修改后的 ptsoriginPts
原始 PtssampleDuration
该帧的时长refSampleDuration
参考帧时长
AUDIO_GAP_DETECT
两帧音频间隙大,进行补帧。详情对象属性如下。
pts
修改后的 ptsoriginPts
原始 Ptscount
补了多少帧nextPts
补的第一帧的 ptsrefSampleDuration
参考帧时长
AUDIO_OVERLAP_DETECT
两帧音频重叠,进行丢帧。详情对象属性如下。
pts
修改后的 ptsoriginPts
原始 Ptscount
补了多少帧nextPts
期望的 ptsrefSampleDuration
参考帧时长
MAX_DTS_DELTA_WITH_NEXT_SEGMENT_DETECT
两个视频 Chunk 之间间隙过大,详情对象属性如下。
nextDts
期望的下一帧 dtsfirstSampleDts
该 Chunk 第一帧的 dtssampleDuration
等于nextDts-firstSampleDts
独立使用场景
在独立使用场景下,上述事件是全部支持的,监听事件的方式与作为插件使用时有所区别
import { Hls, EVENT } from 'xgplayer-hls'
if (Hls.isSupported()) { // 使用静态方法
const hls = new Hls({
media: document.createElement('video')
})
hls.on(EVENT.SEI, (event) => { // 监听事件
// event定义与上述定义一致,不包括eventName属性
})
}