添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 前端
NPlayer 支持任何流媒体和 B 站弹幕体验的视频播放器

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

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

安装

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

npm i -S nplayer

更多请查看 安装

开始使用

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 元素。

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 参数设置,它是一个缩略图配置对象,具体接口如下:

interface ThumbnailOptions {
  startSecond?: number;
  gapSecond?: number;
  row?: number;
  col?: number;
  width?: number;
  height?: number;
  images?: string[];

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

{
  startSecond: 0,
  gapSecond: 10,
  col: 5,
  row: 5,
  width: 160,
  height: 90,
  images: []

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

M1.jpg

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

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

参数 描述
startSecond 缩略图制作的开始时间,比如缩略图是视频的第一秒开始制作的那么,这里就是 1
gapSecond 一张小缩略图时间跨度,如果小缩略图是每 5 秒截一张,那么这里就填 5
col 雪碧图的列数
row 雪碧图的行数
width 小缩略图的宽
height 小缩略图的高
images 雪碧图的链接地址数组

缩略图制作

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

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

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

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

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

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 表示按数字递增。

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

new Player({
  thumbnail: {
    startSecond: 1,
    images: ['M1.jpg', 'M2.jpg', 'M3.jpg']

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

流媒体

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

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 来实现清晰度切换吧。(如果代码有困难,最好先阅读 控制条章节 )。

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 将销毁视频
image.png

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

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

弹幕

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

安装

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

npm i -S @nplayer/danmaku

使用

import Player from "nplayer";
import Danmaku from "@nplayer/danmaku";
import items from "./items";
const danmaku = new Danmaku({ items });
const player = new Player({