当前位置: 首页 > article >正文

springboot-ffmpeg-m3u8-convertor nplayer视频播放弹幕效果

学习链接

ffmpeg-cli-wrapper - 内部封装了操作ffmpeg命令的java类库,它提供了一些类和方法,可以方便地构建和执行 ffmpeg 命令,而不需要直接操作字符串或进程。并且支持异步执行和进度监听

springboot-ffmpeg-m3u8-convertor - gitee代码 - springboot+ffmpeg,将视频转换为 m3u8 格式。支持 .mp4 | .flv | .avi | .mov | .wmv | .wav 格式视频转换。转换方式有:指定文件路径 、文件上传转换两种转换方式。

java-ffmpeg-convert-wav-to-mp3-demo

在 java 中使用 ffmpeg 的四个阶段

nplayer官网文档

artplayer官网文档

nplayer播放效果

在这里插入图片描述

代码

App.vue

<template>
  <div id="app">
    <div class="video-area">
      <div class="video-wrapper" ref="videoWrapperRef">
      </div>
      <div class="video-select-wrapper">
        <ul>
          <li v-for="(m3u8, index) in m3u8List" :key="index" @click="switchVideo(m3u8)">{{ m3u8.videoName }}</li>
        </ul>
      </div>
    </div>
    <div>
      <button @click="sendADanmu">发送1个弹幕</button>
      <button @click="pauseDanmu">暂停弹幕</button>
      <button @click="resumeDanmu">恢复弹幕</button>
      <button @click="getDanmu">获取弹幕列表</button>
      <!-- 添加一个弹幕到弹幕列表,并返回该弹幕插入下标。(大量弹幕请不要循环调用该方法,请使用其他批量方法) -->
      <button @click="addDanmuToDanmuList">添加一个弹幕到弹幕列表</button>
      <!-- 在现有弹幕列表末尾添加弹幕列表。需要保证添加的弹幕列表是有序的,而且其第一个弹幕的时间比现有的最后一个时间大。 -->
      <button @click="addDanmuListToDanmuList">在现有弹幕列表末尾添加弹幕列表</button>
      <!-- 重置弹幕列表。如果你又有一堆无序弹幕列表需要加入。可以通过 getItems() 获取现有弹幕,然后拼接两个列表,做排序,再调用该方法。 -->
      <button @click="resetDanmuList">重置弹幕列表</button>
    </div>
    <div>
      <button @click="startVideo">播放视频</button>
      <button @click="pauseVideo">暂停视频</button>
      <button @click="toggleVideo">切换视频播放状态</button>
      <button @click="jumpTo">跳到指定时间开始播放</button>
      <button @click="updatePlayerOptions">更新播放器参数</button>
      <button @click="destroyPlayer">销毁</button>

    </div>
  </div>
</template>

<script>
import Hls from 'hls.js'
import Player, { EVENT } from 'nplayer';
import Danmaku from '@nplayer/danmaku'
console.log(EVENT)

export default {
  name: 'App',
  components: {
  },
  data() {
    return {
      hls: null,
      player: null,
      video: null,
      danmaku: null,
      m3u8List: [
        { videoId: '1', videoUrl: 'http://127.0.0.1/test3.m3u8', poster: 'http://127.0.0.1/20250219/13/41/poster.jpg', videoName: '第1集' },
        { videoId: '2', videoUrl: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', poster: '', videoName: '联网视频' },
        { videoId: '3', videoUrl: 'http://127.0.0.1/test1.m3u8', poster: 'http://127.0.0.1/20250219/13/37/poster.jpg', videoName: '第2集' },
        { videoId: '4', videoUrl: 'http://127.0.0.1/test5.m3u8', poster: '', videoName: '第3集' },
        { videoId: '5', videoUrl: 'http://127.0.0.1/test6.m3u8', poster: '', videoName: '第4集' },
        // {videoUrl: 'http://127.0.0.1/20250219/13/49/test7.m3u8',poster:'http://127.0.0.1/poster.jpg',videoName: '第5集'},
      ]
    }
  },
  mounted() {
    this.initPlayer(/* this.m3u8List[0] */)
  },
  methods: {
    isNativeHlsSupported() {
      const video = document.createElement("video");
      return video.canPlayType("application/vnd.apple.mpegurl") !== "";
    },
    destroyPlayer() {
      if (this.player) {
        if (this.player.playing) {
          this.player.pause()
        }
        this.player.danmaku.resetItems([])
        this.player.dispose();
      }
      if (this.hls) {
        this.hls.destroy()
      }
    },
    switchVideo(m3u8) {
      console.log('播放: ', m3u8);
      this.destroyPlayer()
      this.initPlayer(m3u8)
    },
    sendADanmu() {
      /* 这里不需要设置time时间,默认直接取视频时间 */
      this.danmaku.send({ text: 'sendADanmu~~~' })
    },
    pauseDanmu() {
      console.log(this.danmaku.paused);
      this.danmaku.pause()
      console.log(this.danmaku.paused);
    },
    resumeDanmu() {
      console.log(this.danmaku.paused);
      this.danmaku.resume()
      console.log(this.danmaku.paused);
    },
    getDanmu() {
      console.log(this.danmaku.getItems());
    },
    addDanmuToDanmuList() {
      // 添加一个弹幕到弹幕列表,并返回该弹幕插入下标。(大量弹幕请不要循环调用该方法,请使用其他批量方法)
      // 这里需要设置time时间,否则不会添加到弹幕列表
      console.log(this.danmaku.addItem({ text: 'addDanmuToDanmuList~~~', time: 8 }));
    },
    addDanmuListToDanmuList() {
      /* 它会在末尾添加大量弹幕,这里可以不指定time */
      console.log(this.danmaku.appendItems(
        [
          { text: 'addDanmuListToDanmuList~~~' },
          { text: 'addDanmuListToDanmuList~~~' },
          { text: 'addDanmuListToDanmuList~~~' },
          { text: 'addDanmuListToDanmuList~~~' },
          { text: 'addDanmuListToDanmuList~~~' },
        ]
      ));
    },
    resetDanmuList() {
      const oldItems = this.player.danmaku.getItems()
      const newUnsortItems = [
        { time: 3, text: '重置弹幕1' },
        { time: 4, text: '重置弹幕2' },
        { time: 10, text: '重置弹幕3' },
      ]
      const sortedItems = oldItems.concat(newUnsortItems).sort((a, b) => a.time - b.time)
      this.player.danmaku.resetItems(sortedItems)
    },
    startVideo() {
      this.player.play()
    },
    pauseVideo() {
      this.player.pause()
    },
    toggleVideo() {
      this.player.toggle()
    },
    jumpTo() {
      this.player.seek(30)
    },
    updatePlayerOptions() {
      this.player.updateOptions({})
    },
    initPlayer(m3u8) {
      console.log('initPlayer' + m3u8);
      this.video = document.createElement('video')
      const videoWrapper = this.$refs.videoWrapperRef
      let _this = this
      const danmakuOptions = {
        // 是否开启无限弹幕模式。
        unlimited: true,
        // 1、弹幕列表必须按照 time 从小到大排序。
        //    如果获取的弹幕是无序的,那么在传入之前需要自己 .sort((a, b) => a.time - b.time) 一下。
        // 2、你还可以通过 danmaku 对象的 appendItems 和 resetItems 等方法,添加和重置弹幕。
        items: [],
        // 发送弹幕之前会调用该回调,用来判断是否丢弃当前弹幕。
        discard(bullet) {
          console.log('discard bullet? 内容是:', bullet.text, bullet);
          if (bullet.text.indexOf('2B') > -1) {
            // 不显示该弹幕
            return true
          } else {
            return false
          }
        }
      }

      this.player = new Player({
        isTouch: false, // 默认会自动检测
        poster: m3u8?.poster,
        // 开启快捷键功能
        shortcut: true,
        // volumeStep 参数控制
        volumeStep: 0.2,
        // 前进或后退 时长参数控制
        seekStep: 2,
        // videoProps: { autoplay: false },
        volumeVertical: true,
        i18n: 'zh',
        video: this.video,
        plugins: [
          new Danmaku(danmakuOptions)
        ]
      })
      window.player = this.player
      // 弹幕插件还会在 player 对象上注册一个 danmaku 对象。可以通过 player.danmaku 访问该对象。
      // 可以通过 danmaku 对象的 appendItems 和 resetItems 等方法,添加和重置弹幕
      console.log('danmaku 对象', this.player.danmaku)
      this.danmaku = this.player.danmaku
      window.danmaku = this.player.danmaku
      // 用户发送弹幕之前触发。
      this.player.on('DanmakuSend', (opts) => {
        console.log('DanmakuSend', opts);
      })
      this.player.on("DanmakuUpdateOptions", (opts) => {
        console.log('DanmakuUpdateOptions', opts);
      })
      this.player.mount(videoWrapper)

      if (this.isNativeHlsSupported()) {
        this.player.src = url; // 直接使用原生播放
      } else {
        if (m3u8 && m3u8.videoUrl) {
          this.hls = new Hls()
          this.hls.attachMedia(this.player.video)
          this.hls.on(Hls.Events.MEDIA_ATTACHED, function () {
            console.log('Hls.Events.MEDIA_ATTACHED监听');
            // _this.hls.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8')
            // _this.hls.loadSource('http://127.0.0.1/20250219/13/41/test3.m3u8')
            // _this.hls.loadSource('http://127.0.0.1/test/test.m3u8')
          })
          this.hls.on(Hls.Events.MANIFEST_PARSED, function () {
            console.log('Hls.Events.MANIFEST_PARSED监听');
          })
          this.hls.loadSource(m3u8.videoUrl)
          // 模拟加载弹幕
          new Promise((resolve, reject) => {
            console.log('加载弹幕');
            this.player.danmaku.appendItems([
              { time: 1, text: m3u8.videoId + '- 弹幕1~' },
              { time: 2, text: m3u8.videoId + '- 弹幕2~' },
              { time: 2.5, text: m3u8.videoId + '- 2B~' },
              { time: 3, text: m3u8.videoId + '- 弹幕3~' },
              { time: 4, text: m3u8.videoId + '- 弹幕4~' },
              { time: 5, text: m3u8.videoId + '- 弹幕5~' },
              { time: 6, text: m3u8.videoId + '- 自定义弹幕哦~', color: '#f00', type: 'scroll', isMe: false, force: true }
            ])
          })
        }
      }

      window.hls = this.hls
    },

  },


}
</script>

<style lang="scss">
body {
  margin: 0;
}
.nplayer_video {
  object-fit: cover;
}
.video-area {
  width: 1200px; // 80 * 45   16 9 
  margin: 20px auto;
  display: flex;
  height: 448px;
}

.video-wrapper {
  width: 800px;
  border: 1px solid #ccc;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 5px 10px 2px rgba(0,0,0,.08);
}

ul,
li {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

.video-select-wrapper {
  border: 1px solid #ccc;
  border-radius: 4px;
  margin-left: 15px;
  flex: 1;

  ul {
    width: 100%;
    height: 100%;

    li {
      border: 1px solid #eee;
      margin: 5px;
      height: 30px;
      text-align: center;
      line-height: 30px;
      color: #333;

      &:hover {
        background-color: #8cc6f2;
        border-radius: 4px;
        cursor: pointer;
        color: #fff;
      }
    }
  }
}

button {
  margin: 5px;
}
</style>


package.json

{
  "name": "nplayer-demo",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "@nplayer/danmaku": "^1.0.12",
    "core-js": "^3.8.3",
    "hls.js": "^1.5.20",
    "nplayer": "^1.0.15",
    "vue": "^2.6.14"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "sass": "^1.32.7",
    "sass-loader": "^12.0.0",
    "vue-template-compiler": "^2.6.14"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}


http://www.kler.cn/a/553310.html

相关文章:

  • WIN系统服务器如何修改远程端口?
  • 人工智能学习环境配置
  • qt for android release apk 手动签名方式
  • 如何使用Spark SQL进行复杂的数据查询和分析
  • TPU(Tensor Processing Unit)详解
  • 使用DeepSeek+本地知识库,尝试从0到1搭建高度定制化工作流(自动化篇)
  • 新品发布:即插即用,8寸Type-C接口电脑副屏显示器发布!
  • 6.4 k8s的informer机制
  • 什么是掉期(Swap)?——金融衍生品的关键工具(中英双语)
  • 第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式
  • 【PLL】应用:时钟生成
  • Nacos Derby 远程命令执行漏洞修复建议
  • LearnOpenGL——高级OpenGL(下)
  • Linux 多Python版本统一和 PySpark 依赖 python 包方案
  • 如何在自定义组件中使用v-model实现双向绑定
  • JupyterNotebook高级使用:常用魔法命令
  • MyBatis-Plus之通用枚举
  • 懒人精灵本地离线卡密验证系统教程(不联网、安全稳定、省钱、永久免费、无任何限制)
  • 15.1 Process(进程)类
  • 智信BI:解决Power BI全面兼容问题的新选择