NPlayer 支持任何流媒体和 B 站弹幕体验的视频播放器

2022-10-09 10:17:17 浏览数 (1)

NPlayer 是由 Typescript 加 Sass 编写,无任何第三方运行时依赖,Gzip 大小只有 21KB,兼容 IE11,支持 SSR。该播放器高度可定制,所有图标、按钮、色彩等都可以替换,并且提供了 内置组件 方便二次开发。它还拥有插件系统,弹幕功能 就是使用插件形式提供。该播放器可以接入任何 流媒体,如 hls、dash 和 flv 等。

  • 官网:https://nplayer.js.org
  • 源码:https://github.com/woopen/nplayer
  • 在线编辑 & 预览:https://codesandbox.io/s/ancient-sky-ujtms?file=/src/index.js

安装

使用如下命令快速安装 NPlayer。

代码语言:javascript复制
npm i -S nplayer

更多请查看 安装

开始使用

代码语言:javascript复制
import Player from 'nplayer'const player = new NPlayer({  src: 'https://v-cdn.zjol.com.cn/280443.mp4'})// player.mount('#app')player.mount(document.body)

上面是创建一个播放器最简单的方法,创建一个 player 对象,设置视频元素的 src,然后将它挂载到 document.body 中。

当然你也可以自己提供 video 元素。

代码语言:javascript复制
import Player from 'nplayer'const video = document.createElement('video')
video.src = 'https://v-cdn.zjol.com.cn/280443.mp4'const player = new Player({ video, videoAttrs: { autoplay: 'true' } })

player.mount(document.body)

你还可以使用 videoAttrs 参数,将视频元素的属性添加到这个 video 元素上,videoAttrs 有一些默认值,它会和你传入的合并再设置到视频元素上,详情请查看 参数章节

player.mount 方法可以将播放器挂载到页面上,它接收一个参数,可以是一个字符串或一个 dom 元素。当是字符串时,将会自动查找该 dom 元素。

预览缩略图

当鼠标放到进度条上时就会出现,一个小缩略图来预览这个时间点的截图,现在很多视频网站都有这个功能。NPlayer 也提供了这个功能。

NPlayer 的缩略图有 thumbnail 参数设置,它是一个缩略图配置对象,具体接口如下:

代码语言:javascript复制
interface ThumbnailOptions {
  startSecond?: number;
  gapSecond?: number;
  row?: number;
  col?: number;
  width?: number;
  height?: number;
  images?: string[];
}

它个各个属性默认值如下:

代码语言:javascript复制
{  startSecond: 0,  gapSecond: 10,  col: 5,  row: 5,  width: 160,  height: 90,  images: []
}

这个预览缩略图其实是由一堆分辨率较小的截图组成的一张图片,如下所示。

我们可以看到这个雪碧图由 5 x 5 的小缩略图组成,当然一个视频可能有一堆上面这种雪碧图,这就是上面 images 是一个数组字符串的原因。

了解了雪碧图,下面来详细了解各个参数分别是什么意思吧。

参数

描述

startSecond

缩略图制作的开始时间,比如缩略图是视频的第一秒开始制作的那么,这里就是 1

gapSecond

一张小缩略图时间跨度,如果小缩略图是每 5 秒截一张,那么这里就填 5

col

雪碧图的列数

row

雪碧图的行数

width

小缩略图的宽

height

小缩略图的高

images

雪碧图的链接地址数组

缩略图制作

有很多方式可以制作视频的预览缩略图,比如用 NodeJS node-fluent-ffmpeg 库中的 thumbnails 方法。当然大家可以去网上寻找更多方法。

这里介绍如何直接用 ffmpeg 命令行生成视频缩略图。

ffmpeg 是非常强大音视频工具,很多播放器都是它作为内核,更多详情请查看 官方文档

首先需要去 ffmpeg 官网下载并安装好 ffmpeg 。

安装好后可以在命令行执行下面命令。

代码语言:javascript复制
ffmpeg -i ./test.webm -vf 'fps=1/10:round=zero:start_time=-9,scale=160x90,tile=5x5' M%d.jpg

通过上面这个命令生成一堆 5 x 5 的雪碧图,每个雪碧图中小缩略图的尺寸是 160 x 90。雪碧图的文件名是 M1.jpg、M2.jp、M3.jpg... 这样递增。

  • -i 参数后面是视频文件。
  • -vf 参数后面跟着过滤器,多个过滤器用 , 分开,一个过滤器多个参数使用 : 分开。
  • fps=1/10 表示每 10 秒输出一张图片,round=zero 为时间戳向 0 取整。start_time=-9 是让它从第 1 秒开始截取,忽略掉 0 秒的黑屏帧,这里是 -9,而不是 1 的原因是,fps 我们设置的是 10 秒一张,所以想要从第 1 秒开始时,就用 1 - 10 等于 -9
  • scale=160x90 设置输出图像分辨率大小,tile=5x5 将小图用 5x5 的方式组合在一起。
  • 最后面的 M%d.jpg 就是文件名,%d 表示按数字递增。

那么用上面命令生成的缩略图,可以设置如下参数。

代码语言:javascript复制
new Player({  thumbnail: {    startSecond: 1,    images: ['M1.jpg', 'M2.jpg', 'M3.jpg']
  }
})

由于其他参数都可以使用默认值,所以这里就不填了。

流媒体

现在大家看的网络视频一般不会直接用 .mp4 文件了,而是使用 HLS,DASH 这些流媒体协议。NPlayer 支持接入任何流媒体协议。

代码语言:javascript复制
import Hls from 'hls'import Player from 'player'const hls = new Hls()const player = new Player()
hls.attachMedia(player.video)

hls.on(Hls.Events.MEDIA_ATTACHED, function () {
  hls.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8')
})

player.mount(document.body)

因为这些流媒体库其实只需要一个 video 元素,我们直接传入 player.video 属性就行,对于其他任何流媒体库都是这个套路。更多信息请查看 流媒体章节

清晰度切换

网上这些主流的视频网站应该都可以调节视频清晰度,有高清晰度还需要开通会员才能观看。而且默认情况下还会根据当前用户网速自动选择最佳清晰度。

其实使用上面提到的流媒体协议可以非常轻松的实现这些功能,下面就用 NPlayer 来实现清晰度切换吧。(如果代码有困难,最好先阅读 控制条章节)。

代码语言:javascript复制
import Player from 'nplayer'import Hls from 'hls'// 1. 首先创建一个控制条项const Quantity = {  element: document.createElement('div'),  init(player) {    this.btn = document.createElement('div')    this.btn.textContent = '画质'
    this.element.appendChild(this.btn)    this.popover = new player.Player.components.Popover(this.element)    this.btn.addEventListener('click', () =>  this.popover.show())    // 点击按钮的时候展示 popover
    this.element.style.display = 'none'
    // 默认隐藏
    this.element.classList.add('quantity')
  }
}// 2. 我们把它放到 spacer 后面const player = new Player({  controls: ['play', 'volume', 'time', 'spacer', Quantity, 'airplay', 'settings', 'web-fullscreen', 'fullscreen'],
})// 3. 创建 HLS 实例const hls = new Hls();
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
  hls.on(Hls.Events.MANIFEST_PARSED, function () {    // 4. 给清晰度排序,清晰度越高的排在最前面
    hls.levels.sort((a, b) => b.height - a.height)    const frag = document.createDocumentFragment()    // 5. 给与清晰度对应的元素添加,点击切换清晰度功能
    const listener = (i) => (init) => {      const last = Quantity.itemElements[Quantity.itemElements.length - 1]      const prev = Quantity.itemElements[Quantity.value] || last      const el = Quantity.itemElements[i] || last
      prev.classList.remove('quantity_item-active')
      el.classList.add('quantity_item-active')      Quantity.btn.textContent = el.textContent
      if (init !== true && !player.paused) setTimeout(() => player.play())      // 因为 HLS 切换清晰度会使正在播放的视频暂停,我们这里让它再自动恢复播放
      Quantity.value = hls.currentLevel = hls.loadLevel = i;      Quantity.popover.hide();
    }    // 6. 添加清晰度对应元素
    Quantity.itemElements = hls.levels.map((l, i) => {      const el = document.createElement('div')
      el.textContent = l.name   'P'
      if (l.height === 1080) el.textContent  = ' 超清'
      if (l.height === 720) el.textContent  = ' 高清'
      if (l.height === 480) el.textContent  = ' 清晰'
      el.classList.add('quantity_item')
      el.addEventListener('click', listener(i))
      frag.appendChild(el)      return el;
    })    const el = document.createElement('div')
    el.textContent = '自动'
    el.addEventListener('click', listener(-1))
    el.classList.add(styles.QuantityItem)
    frag.appendChild(el)    Quantity.itemElements.push(el)    // 这里再添加一个 `自动` 选项,HLS 默认是根据网速自动切换清晰度

    Quantity.popover.panelElement.appendChild(frag);    Quantity.element.style.display = 'block';    listener(hls.currentLevel)(true)    // 初始化当前清晰度
  })  // 绑定 video 元素成功的时候,去加载视频
  hls.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8')
})

hls.attachMedia(player.video)
player.mount(container.current);const dispose = () => {
  hls.destroy()
  player.dispose()
}// 调用 dispose 将销毁视频

这里是使用 HLS 的多码率来实现多清晰度视频切换。只需稍微改下代码,你可以使用 NPlayer 对任何流媒体或普通 mp4 视频添加清晰度切换功能。

后面会发布如何制作多码率视频的文章,欢迎关注。

弹幕

NPlayer 的弹幕功能可以保持大量弹幕而不卡顿,弹幕系统体验和性能与 B 站弹幕十分相似,支持非常多的设置,弹幕防碰撞、弹幕速度、字体、速度、透明度、显示区域、无限弹幕等。

安装

执行下面命令使用 npm 包的形式安装。

代码语言:javascript复制
npm i -S @nplayer/danmaku

使用

代码语言:javascript复制
import Player from "nplayer";import Danmaku from "@nplayer/danmaku";import items from "./items";const danmaku = new Danmaku({ items });const player = new Player({  src: "https://v-cdn.zjol.com.cn/280443.mp4",  plugins: [danmaku]
});

player.mount(document.body);

点击这个链接进行预览和编辑:https://codesandbox.io/s/nplayer-demo-ujtms?file=/src/index.js

更多弹幕插件信息,请查看 弹幕插件章节

弹幕实现

NPlayer 的弹幕系统尝试了多种实现方案,最终选择了 CSS3 中的 transformtransition 方式,它也是 B 站弹幕默认选择的方案,当然 B 站还支持 canvas 的方式渲染,NPlayer 也有尝试,但是在 firefox 上测试大量弹幕时,会有一点小卡顿,所以最终选择了更优一点的 CSS3 的方案。

除了渲染方式,弹幕实现还有很多其他的难点,比如弹幕如何防碰撞,当视频倍速播放时弹幕的速度也如何改变,视频的播放暂停事件会有一个很小的延迟,即使很小的延迟也会让弹幕在暂停视频时有个卡顿位置跳跃问题。当然在用户自定义调节弹幕速度和视频播放速度同时疯狂的播放暂停,怎么保证弹幕位置不发生突然跳动等问题?NPlayer 解决了全部这些问题,和 B 站的弹幕体验非常相似。

这里篇幅有限,我打算后续再写一篇超详细的实现弹幕文章,欢迎关注。

总结

除了上述功能外 NPlayer 还有非常多的功能,欢迎点击下面链接查看详情。

如果有问题或者是想要新功能,欢迎提交 issue。也欢迎提交 PR。

  • 官网:https://nplayer.js.org
  • 源码:https://github.com/woopen/nplayer
  • 在线编辑 & 预览:https://codesandbox.io/s/ancient-sky-ujtms?file=/src/index.js

0 人点赞