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

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

学习链接

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
在这里插入图片描述
ArtPlayer
在这里插入图片描述

代码

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",
    "artplayer": "^5.2.2",
    "artplayer-plugin-danmuku": "^5.1.5",
    "core-js": "^3.8.3",
    "hls.js": "^1.5.20",
    "nplayer": "^1.0.15",
    "vue": "^2.6.14",
    "vue-router": "^3.6.5"
  },
  "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"
  ]
}

NPlayer.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/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/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/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/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>


ArtPlauer

<template>
    <div id="app">
        <div class="video-area">
            <div class="art-wrapper">
                <div ref="artRef" class="artplayer-app"></div>
            </div>
            <div class="video-select-wrapper">
                <ul>
                    <li :class="{active: currVideoId == videoItem.videoId}" v-for="(videoItem, index) in videoList" :key="index" @click="switchVideo(videoItem)">{{
                        videoItem.videoName }}</li>
                </ul>
            </div>
        </div>
        <div class="btn-wrapper">
            <button @click="playVideo()">播放视频</button>
            <button @click="pauseVideo()">暂停视频</button>
            <button @click="jumpTo()">跳转到10s</button>
            <button @click="showDanmu()">显示弹幕</button>
            <button @click="hideDanmu()">隐藏弹幕</button>
            <button @click="emitDanmu()">发送1条实时弹幕</button>
            <!-- 通过方法 config 实时改变弹幕配置 -->
            <button @click="configDanmu()">修改弹幕配置</button>
            <button @click="reloadDanmu()">重新加载当前弹幕库</button>
            <button @click="toggleDanmu()">切换当前弹幕库</button>
            <button @click="appendDanmu()">追加弹幕</button>
            <button @click="resetDanmu()">用于清空当前显示的弹幕</button>
        </div>
    </div>
</template>

<script>
import Hls from 'hls.js'
import Artplayer from "artplayer";
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku';

export default {
    name: 'App',
    data() {
        return {
            instance: null,
            currVideoId: null,
            videoList: [
                { videoId: 1, videoUrl: 'http://127.0.0.1/test1.mp4', videoName: 'jvm', poster: 'http://127.0.0.1/test1.jpg' },
                { videoId: 2, videoUrl: 'http://127.0.0.1/test2.mp4', videoName: 'seata', poster: 'http://127.0.0.1/test2.jpg' },
                { videoId: 3, videoUrl: 'http://127.0.0.1/unknown.mp4', videoName: 'unknown', poster: 'http://127.0.0.1/test3.jpg' },
                
            ]
        }
    },
    mounted() {
        this.initPlayer(null)
        /* setTimeout(()=>{
            this.switchVideo(this.videoList[0])
        }, 2000) */
    },
    methods: {
        playVideo() {
            this.instance.play()
        },
        pauseVideo() {
            this.instance.pause()
        },
        jumpTo() {
            this.instance.seek = 10
        },
        emitDanmu() {
            this.instance.plugins.artplayerPluginDanmuku.emit({
                text: '发送1条实时弹幕',
                color: '#ff0',
                border: true,
            });
        },
        showDanmu() {
            console.log(art.plugins.artplayerPluginDanmuku.isHide);
            this.instance.plugins.artplayerPluginDanmuku.show()
        },
        hideDanmu() {
            console.log(art.plugins.artplayerPluginDanmuku.isHide);
            this.instance.plugins.artplayerPluginDanmuku.hide()
        },
        configDanmu() {
            this.instance.plugins.artplayerPluginDanmuku.config({
                fontSize: Number(40),
            });
        },
        reloadDanmu() {
            // 当danmuku的配置是路径的时候,会重新发起请求
            this.instance.plugins.artplayerPluginDanmuku.load();
        },
        toggleDanmu() {
            art.plugins.artplayerPluginDanmuku.config({
                danmuku: [
                    {
                        text: '1-toggleDanmu(⊙o⊙)?', // 弹幕文本
                        time: 10, // 弹幕时间, 默认为当前播放器时间
                        mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                        color: '#FFFFFF', // 弹幕颜色,默认为白色
                        border: false, // 弹幕是否有描边, 默认为 false
                        style: {}, // 弹幕自定义样式, 默认为空对象
                    },
                    {
                        text: '3-toggleDanmu(⊙o⊙)?', // 弹幕文本
                        time: 11, // 弹幕时间, 默认为当前播放器时间
                        mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                        color: '#FFFFFF', // 弹幕颜色,默认为白色
                        border: false, // 弹幕是否有描边, 默认为 false
                        style: {}, // 弹幕自定义样式, 默认为空对象
                    }
                ],
            });
            this.instance.plugins.artplayerPluginDanmuku.load();
        },
        appendDanmu() {
            // 追加新的弹幕库,参数类型和option.danmuku相同
            this.instance.plugins.artplayerPluginDanmuku.load([
                {
                    text: '666-appendDanmu(⊙o⊙)?', // 弹幕文本
                    time: 12, // 弹幕时间, 默认为当前播放器时间
                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                    border: false, // 弹幕是否有描边, 默认为 false
                    style: {}, // 弹幕自定义样式, 默认为空对象
                },
                {
                    text: '666-appendDanmu(⊙o⊙)?', // 弹幕文本
                    time: 12, // 弹幕时间, 默认为当前播放器时间
                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                    border: false, // 弹幕是否有描边, 默认为 false
                    style: {}, // 弹幕自定义样式, 默认为空对象
                }
            ])
        },
        resetDanmu() {
            // 并不是真的删除弹幕,只是当前显示的弹幕变成不显示了,再次拖动进度条,仍然会显示
            this.instance.plugins.artplayerPluginDanmuku.reset();
        },
        switchVideo(videoItem) {
            /* // 可以直接修改url,但是比如一些设置无法动态修改,比如highlight 
            this.currVideoId = videoItem.videoId
            this.instance.poster = videoItem.poster
            this.instance.switchUrl(videoItem.videoUrl) */
            if(this.instance) {
                this.instance.destroy()
            }
            this.initPlayer(videoItem)
            this.currVideoId = videoItem.videoId
            this.instance.poster = videoItem.poster
            this.instance.switchUrl(videoItem.videoUrl)
        },
        initPlayer(videoItem) {
            let _this = this
            this.instance = new Artplayer(
                {
                    // 语言
                    lang: 'zh-cn', // or 'en'
                    // 播放器的唯一标识,目前只用于记忆播放 autoplayback
                    id: 'your-url-id',
                    // container: this.$refs.artRef,
                    container: '.artplayer-app',
                    // 默认支持三种视频文件格式:.mp4, .ogg, .webm
                    // url: 'http://127.0.0.1/test1.mp4',
                    // 视频的海报,只会出现在播放器初始化且未播放的状态下
                    // poster: 'http://127.0.0.1/test2.jpg',
                    // 播放器主题颜色,目前用于 进度条 和 高亮元素 上
                    theme: '#00aeec',
                    // 播放器的默认音量 Default: 0.7
                    volume: 0.5,
                    // 使用直播模式,会隐藏进度条和播放时间
                    // isLive: true,
                    // 是否默认静音
                    // muted: true,
                    // 是否自动播放(假如希望默认进入页面就能自动播放视频,muted 必需为 true)
                    // autoplay: true,
                    // 播放器的尺寸默认会填充整个 container 容器尺寸,所以经常出现黑边,该值能自动调整播放器尺寸以隐藏黑边,类似 css 的 object-fit: cover;
                    autoSize: true,
                    // 当播放器滚动到浏览器视口以外时,自动进入 迷你播放 模式
                    autoMini: true,
                    // 是否显示视频翻转功能 (会出现在 设置面板 和 右键菜单 里)
                    // flip: true,
                    // 是否显示视频播放速度功能,会出现在 设置面板 和 右键菜单 里
                    playbackRate: true,
                    // 是否显示视频长宽比功能,会出现在 设置面板 和 右键菜单 里
                    aspectRatio: true,
                    // 是否在底部控制栏里显示 视频截图 功能(由于浏览器安全机制,假如视频源地址和网站是跨域的,可能会出现截图失败)
                    // screenshot: true,
                    // 是否在底部控制栏里显示 设置面板 的开关按钮
                    setting: true,
                    // 是否使用快捷键(只在播放器获得焦点后(如点击了播放器后),这些快捷键才会生效)
                    hotkey: true,
                    // 是否在底部控制栏里显示 画中画 的开关按钮
                    pip: true,
                    // 假如页面里同时存在多个播放器,是否只能让一个播放器播放
                    mutex: true,
                    // 是否在底部控制栏里显示播放器 窗口全屏 按钮
                    fullscreen: true,
                    // 是否在底部控制栏里显示播放器 网页全屏 按钮
                    fullscreenWeb: true,
                    // 迷你进度条,只在播放器失去焦点后且正在播放时出现
                    miniProgressBar: true,
                    // 在移动端是否使用 playsInline 模式
                    playsInline: true,
                    // 是否在移动端显示一个 锁定按钮 ,用于隐藏底部 控制栏
                    lock: true,
                    // 是否在移动端添加长按视频快进功能
                    fastForward: true,
                    // 初始化自定义的 层
                    /* layers: [
                        {
                            name: 'potser',
                            html: `<img style="width: 50px" src="http://127.0.0.1/naughty.gif">`,
                            style: {
                                position: 'absolute',
                                top: '20px',
                                right: '20px',
                                opacity: '.9',
                            },
                            click: function (...args) {
                                console.info('click', args);
                                // 点击后,该层消时
                                _this.instance.layers.show = false;
                            },
                            mounted: function (...args) {
                                console.info('mounted', args);
                            },
                        },
                    ], */
                    // 初始化自定义的 设置面板
                    /* settings: [
                        {
                            html: 'setting01',
                            selector: [
                                {
                                    html: 'setting01-01',
                                },
                                {
                                    html: 'setting01-02',
                                },
                            ],
                            onSelect: function (...args) {
                                console.info(args);
                            },
                        },
                        {
                            html: 'setting02',
                            selector: [
                                {
                                    html: 'setting02-01',
                                },
                                {
                                    html: 'setting02-02',
                                },
                            ],
                            onSelect: function (...args) {
                                console.info(args);
                            },
                        },
                    ], */
                    // 初始化自定义的 右键菜单
                    contextmenu: [
                        {
                            html: 'zzhua的个人主页',
                            click: function (...args) {
                                console.info('click', args);
                                _this.instance.contextmenu.show = false;
                            },
                        },
                    ],
                    // 初始化自定义的底部 控制栏
                    /* controls: [
                        {
                            position: 'right',
                            html: '测试',
                            tooltip: 'Your Control',
                            style: {
                                color: 'red',
                            },
                            click: function (...args) {
                                console.info('click', args);
                            },
                        },
                    ], */
                    /* controls: [
                        {
                            position: 'right',
                            html: '隐藏弹幕',
                            click: function () {
                                _this.instance.plugins.artplayerPluginDanmuku.hide();
                            },
                        },
                        {
                            position: 'right',
                            html: '显示弹幕',
                            click: function () {
                                _this.instance.plugins.artplayerPluginDanmuku.show();
                            },
                        },
                    ], */
                    // 是否在底部控制栏里显示 画质选择 列表
                    /* quality: [
                        {
                            default: true,
                            html: '480P',
                            url: '/assets/sample/video.mp4',
                        },
                        {
                            html: '720P',
                            url: '/assets/sample/video.mp4',
                        },
                    ], */
                    highlight: [
                        {
                            time: 60,
                            text: 'One more chance',
                        },
                        {
                            time: 120,
                            text: '谁でもいいはずなのに',
                        }
                    ],
                    // 在进度条上设置 预览图
                    /* thumbnails: {
                        url: '/assets/sample/thumbnails.png',
                        number: 60,
                        column: 10,
                    }, */
                    // 设置视频的字幕,支持字幕格式:vtt, srt, ass
                    /* subtitle: {
                        url: '/assets/sample/subtitle.srt',
                        type: 'srt',
                        encoding: 'utf-8',
                        escape: true,
                        style: {
                            color: '#03A9F4',
                            'font-size': '30px',
                        },
                    }, */
                    // 用于替换默认图标,支持 Html 字符串和 HTMLElement
                    /* icons: {
                        loading: '<img src="/assets/img/ploading.gif">',
                        state: '<img src="/assets/img/state.png">',
                    }, */
                    plugins: [
                        artplayerPluginDanmuku({
                            // 使用弹幕数组
                            /* danmuku: [
                                {
                                    text: '1-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                    time: 1, // 弹幕时间, 默认为当前播放器时间
                                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                                    border: false, // 弹幕是否有描边, 默认为 false
                                    style: {}, // 弹幕自定义样式, 默认为空对象
                                },
                                {
                                    text: '3-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                    time: 3, // 弹幕时间, 默认为当前播放器时间
                                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                                    border: false, // 弹幕是否有描边, 默认为 false
                                    style: {}, // 弹幕自定义样式, 默认为空对象
                                }
                            ], */
                            // 使用异步返回
                            danmuku: function () {
                                return new Promise((resovle) => {
                                    return resovle([
                                        {
                                            text: '1-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                            time: 3, // 弹幕时间, 默认为当前播放器时间
                                            mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                            color: '#FFFFFF', // 弹幕颜色,默认为白色
                                            border: false, // 弹幕是否有描边, 默认为 false
                                            style: {}, // 弹幕自定义样式, 默认为空对象
                                        },
                                        {
                                            text: '3-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                            time: 4, // 弹幕时间, 默认为当前播放器时间
                                            mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                            color: '#FFFFFF', // 弹幕颜色,默认为白色
                                            border: false, // 弹幕是否有描边, 默认为 false
                                            style: {}, // 弹幕自定义样式, 默认为空对象
                                        }
                                    ]);
                                });
                            },
                            // danmuku: '/assets/sample/danmuku.xml',

                            // 以下为非必填
                            speed: 5, // 弹幕持续时间,范围在[1 ~ 10]
                            margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比
                            opacity: 1, // 弹幕透明度,范围在[0 ~ 1]
                            color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖
                            mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部
                            modes: [0, 1, 2], // 弹幕可见的模式
                            fontSize: 25, // 弹幕字体大小,支持像素数字和百分比
                            antiOverlap: true, // 弹幕是否防重叠
                            synchronousPlayback: false, // 是否同步播放速度
                            mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部
                            heatmap: true, // 是否开启热力图
                            width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部
                            points: [], // 热力图数据
                            
                            visible: true, // 弹幕层是否可见
                            emitter: true, // 是否开启弹幕发射器
                            maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000]
                            lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60]
                            theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效
                            OPACITY: {}, // 不透明度配置项
                            FONT_SIZE: {}, // 弹幕字号配置项
                            MARGIN: {}, // 显示区域配置项
                            SPEED: {}, // 弹幕速度配置项
                            COLOR: [], // 颜色列表配置项
                            
                            // 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理
                            beforeEmit(danmu) {
                                return new Promise((resolve) => {
                                    console.log('beforeEmit弹幕',danmu);
                                    setTimeout(() => {
                                        resolve(true);
                                    }, 1000);
                                });
                            },
                            filter: (danmu) => {
                                console.log('filter弹幕',danmu);
                                return danmu.text.length <= 100
                            }, // 弹幕载入前的过滤器
                            beforeVisible: (e) => {
                                console.log('beforeVisible弹幕',e);
                                return true
                            }, // 弹幕显示前的过滤器,返回 true 则可以发送
                        }),
                    ],
                },
                // 构造函数接受一个函数作为第二个参数,播放器初始化成功且视频可以播放时触发,和ready事件一样
                // 等同于:this.instance.on('ready', function() {}
                function onReady(art) {
                    console.log('播放器初始化成功且视频可以播放时触发1', art == _this.instance);
                },
            );
            // 有时候 url 地址没那么快知道,这时候你可以异步设置 url
            /* setTimeout(() => {
                this.instance.url = 'http://127.0.0.1/test1.mp4';
            }, 5000); */
            // 播放器初始化成功且视频可以播放时触发
            this.instance.on('ready', function () {
                console.log('播放器初始化成功且视频可以播放时触发2');
                // _this.instance.play();
            })
            window.art = this.instance
            this.$nextTick(() => {
                this.$emit("get-instance", this.instance);
            });
        }
    },
    
    beforeDestroy() {
        if (this.instance && this.instance.destroy) {
            this.instance.destroy(false);
        }
    },
}

</script>

<style lang="scss">
body {
    margin: 0;
}

.artplayer-app {
    /* 您可能需要初始化容器元素的大小,如: */
    /* 
    width: 400px;
    height: 300px; 
    */
    /* 或者使用 aspect-ratio: */
    width: 800px;
    aspect-ratio: 16/9
}

ul,
li {
    list-style-type: none;
    padding: 0;
    margin: 0;
}
.btn-wrapper {
    width: 1200px;
    margin: auto;
}
.video-area {
    width: 1200px;
    margin: 20px auto;
    display: flex;

    .art-wrapper {
        border: 1px solid #ccc;
    }

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

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

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

                &:hover {
                    background-color: #8cc6f2;
                    border-radius: 4px;
                    cursor: pointer;
                    color: #fff;
                }
                &.active {
                    background-color: #8cc6f2;
                    color: #fff;
                }
            }
        }
    }
}
</style>

Aplayer2

加载m3u8

<template>
    <div id="app">
        <div class="video-area">
            <div class="art-wrapper">
                <div ref="artRef" class="artplayer-app"></div>
            </div>
            <div class="video-select-wrapper">
                <ul>
                    <li :class="{active: currVideoId == videoItem.videoId}" v-for="(videoItem, index) in videoList" :key="index" @click="switchVideo(videoItem)">{{
                        videoItem.videoName }}</li>
                </ul>
            </div>
        </div>
        <div class="btn-wrapper">
            <button @click="playVideo()">播放视频</button>
            <button @click="pauseVideo()">暂停视频</button>
            <button @click="jumpTo()">跳转到10s</button>
            <button @click="showDanmu()">显示弹幕</button>
            <button @click="hideDanmu()">隐藏弹幕</button>
            <button @click="emitDanmu()">发送1条实时弹幕</button>
            <!-- 通过方法 config 实时改变弹幕配置 -->
            <button @click="configDanmu()">修改弹幕配置</button>
            <button @click="reloadDanmu()">重新加载当前弹幕库</button>
            <button @click="toggleDanmu()">切换当前弹幕库</button>
            <button @click="appendDanmu()">追加弹幕</button>
            <button @click="resetDanmu()">用于清空当前显示的弹幕</button>
        </div>
    </div>
</template>

<script>
import Hls from 'hls.js'
import Artplayer from "artplayer";
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku';

export default {
    name: 'App',
    data() {
        return {
            instance: null,
            currVideoId: null,
            videoList: [
                { videoId: '1', videoUrl: 'http://127.0.0.1/test3.m3u8', poster: 'http://127.0.0.1/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/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集' },                
            ]
        }
    },
    mounted() {
        this.initPlayer(null)
        /* setTimeout(()=>{
            this.switchVideo(this.videoList[0])
        }, 2000) */
    },
    methods: {
        playVideo() {
            this.instance.play()
        },
        pauseVideo() {
            this.instance.pause()
        },
        jumpTo() {
            this.instance.seek = 10
        },
        emitDanmu() {
            this.instance.plugins.artplayerPluginDanmuku.emit({
                text: '发送1条实时弹幕',
                color: '#ff0',
                border: true,
            });
        },
        showDanmu() {
            console.log(art.plugins.artplayerPluginDanmuku.isHide);
            this.instance.plugins.artplayerPluginDanmuku.show()
        },
        hideDanmu() {
            console.log(art.plugins.artplayerPluginDanmuku.isHide);
            this.instance.plugins.artplayerPluginDanmuku.hide()
        },
        configDanmu() {
            this.instance.plugins.artplayerPluginDanmuku.config({
                fontSize: Number(40),
            });
        },
        reloadDanmu() {
            // 当danmuku的配置是路径的时候,会重新发起请求
            this.instance.plugins.artplayerPluginDanmuku.load();
        },
        toggleDanmu() {
            art.plugins.artplayerPluginDanmuku.config({
                danmuku: [
                    {
                        text: '1-toggleDanmu(⊙o⊙)?', // 弹幕文本
                        time: 10, // 弹幕时间, 默认为当前播放器时间
                        mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                        color: '#FFFFFF', // 弹幕颜色,默认为白色
                        border: false, // 弹幕是否有描边, 默认为 false
                        style: {}, // 弹幕自定义样式, 默认为空对象
                    },
                    {
                        text: '3-toggleDanmu(⊙o⊙)?', // 弹幕文本
                        time: 11, // 弹幕时间, 默认为当前播放器时间
                        mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                        color: '#FFFFFF', // 弹幕颜色,默认为白色
                        border: false, // 弹幕是否有描边, 默认为 false
                        style: {}, // 弹幕自定义样式, 默认为空对象
                    }
                ],
            });
            this.instance.plugins.artplayerPluginDanmuku.load();
        },
        appendDanmu() {
            // 追加新的弹幕库,参数类型和option.danmuku相同
            this.instance.plugins.artplayerPluginDanmuku.load([
                {
                    text: '666-appendDanmu(⊙o⊙)?', // 弹幕文本
                    time: 12, // 弹幕时间, 默认为当前播放器时间
                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                    border: false, // 弹幕是否有描边, 默认为 false
                    style: {}, // 弹幕自定义样式, 默认为空对象
                },
                {
                    text: '666-appendDanmu(⊙o⊙)?', // 弹幕文本
                    time: 12, // 弹幕时间, 默认为当前播放器时间
                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                    border: false, // 弹幕是否有描边, 默认为 false
                    style: {}, // 弹幕自定义样式, 默认为空对象
                }
            ])
        },
        resetDanmu() {
            // 并不是真的删除弹幕,只是当前显示的弹幕变成不显示了,再次拖动进度条,仍然会显示
            this.instance.plugins.artplayerPluginDanmuku.reset();
        },
        switchVideo(videoItem) {
            /* // 可以直接修改url,但是比如一些设置无法动态修改,比如highlight 
            this.currVideoId = videoItem.videoId
            this.instance.poster = videoItem.poster
            this.instance.switchUrl(videoItem.videoUrl) */
            if(this.instance) {
                this.instance.destroy()
            }
            this.initPlayer(videoItem)
            this.currVideoId = videoItem.videoId
            this.instance.poster = videoItem.poster
            this.instance.switchUrl(videoItem.videoUrl)
        },
        playM3u8(video, url, art) {
            if (Hls.isSupported()) {
                if (this.instance.hls) this.instance.hls.destroy();
                const hls = new Hls();
                hls.loadSource(url);
                hls.attachMedia(video);
                this.instance.hls = hls;
                this.instance.on('destroy', () => hls.destroy());
                hls.on('hlsError', (e)=>{
                    console.log('hlsError', video, url, art);
                })
                window.hls = hls
            } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
                video.src = url;
            } else {
                this.instance.notice.show = 'Unsupported playback format: m3u8';
            }
        },
        initPlayer(videoItem) {
            let _this = this
            this.instance = new Artplayer(
                {
                    // 播放m3u8视频
                    type: 'm3u8',
                    customType: {
                        m3u8: this.playM3u8,
                    },
                    // 语言
                    lang: 'zh-cn', // or 'en'
                    // 播放器的唯一标识,目前只用于记忆播放 autoplayback
                    id: 'your-url-id',
                    // container: this.$refs.artRef,
                    container: '.artplayer-app',
                    // 默认支持三种视频文件格式:.mp4, .ogg, .webm
                    // url: 'http://127.0.0.1/test1.mp4',
                    // 视频的海报,只会出现在播放器初始化且未播放的状态下
                    // poster: 'http://127.0.0.1/test2.jpg',
                    // 播放器主题颜色,目前用于 进度条 和 高亮元素 上
                    theme: '#00aeec',
                    // 播放器的默认音量 Default: 0.7
                    volume: 0.5,
                    // 使用直播模式,会隐藏进度条和播放时间
                    // isLive: true,
                    // 是否默认静音
                    // muted: true,
                    // 是否自动播放(假如希望默认进入页面就能自动播放视频,muted 必需为 true)
                    // autoplay: true,
                    // 播放器的尺寸默认会填充整个 container 容器尺寸,所以经常出现黑边,该值能自动调整播放器尺寸以隐藏黑边,类似 css 的 object-fit: cover;
                    autoSize: true,
                    // 当播放器滚动到浏览器视口以外时,自动进入 迷你播放 模式
                    autoMini: true,
                    // 是否显示视频翻转功能 (会出现在 设置面板 和 右键菜单 里)
                    // flip: true,
                    // 是否显示视频播放速度功能,会出现在 设置面板 和 右键菜单 里
                    playbackRate: true,
                    // 是否显示视频长宽比功能,会出现在 设置面板 和 右键菜单 里
                    aspectRatio: true,
                    // 是否在底部控制栏里显示 视频截图 功能(由于浏览器安全机制,假如视频源地址和网站是跨域的,可能会出现截图失败)
                    // screenshot: true,
                    // 是否在底部控制栏里显示 设置面板 的开关按钮
                    setting: true,
                    // 是否使用快捷键(只在播放器获得焦点后(如点击了播放器后),这些快捷键才会生效)
                    hotkey: true,
                    // 是否在底部控制栏里显示 画中画 的开关按钮
                    pip: true,
                    // 假如页面里同时存在多个播放器,是否只能让一个播放器播放
                    mutex: true,
                    // 是否在底部控制栏里显示播放器 窗口全屏 按钮
                    fullscreen: true,
                    // 是否在底部控制栏里显示播放器 网页全屏 按钮
                    fullscreenWeb: true,
                    // 迷你进度条,只在播放器失去焦点后且正在播放时出现
                    miniProgressBar: true,
                    // 在移动端是否使用 playsInline 模式
                    playsInline: true,
                    // 是否在移动端显示一个 锁定按钮 ,用于隐藏底部 控制栏
                    lock: true,
                    // 是否在移动端添加长按视频快进功能
                    fastForward: true,
                    // 初始化自定义的 层
                    /* layers: [
                        {
                            name: 'potser',
                            html: `<img style="width: 50px" src="http://127.0.0.1/naughty.gif">`,
                            style: {
                                position: 'absolute',
                                top: '20px',
                                right: '20px',
                                opacity: '.9',
                            },
                            click: function (...args) {
                                console.info('click', args);
                                // 点击后,该层消时
                                _this.instance.layers.show = false;
                            },
                            mounted: function (...args) {
                                console.info('mounted', args);
                            },
                        },
                    ], */
                    // 初始化自定义的 设置面板
                    /* settings: [
                        {
                            html: 'setting01',
                            selector: [
                                {
                                    html: 'setting01-01',
                                },
                                {
                                    html: 'setting01-02',
                                },
                            ],
                            onSelect: function (...args) {
                                console.info(args);
                            },
                        },
                        {
                            html: 'setting02',
                            selector: [
                                {
                                    html: 'setting02-01',
                                },
                                {
                                    html: 'setting02-02',
                                },
                            ],
                            onSelect: function (...args) {
                                console.info(args);
                            },
                        },
                    ], */
                    // 初始化自定义的 右键菜单
                    contextmenu: [
                        {
                            html: 'zzhua的个人主页',
                            click: function (...args) {
                                console.info('click', args);
                                _this.instance.contextmenu.show = false;
                            },
                        },
                    ],
                    // 初始化自定义的底部 控制栏
                    /* controls: [
                        {
                            position: 'right',
                            html: '测试',
                            tooltip: 'Your Control',
                            style: {
                                color: 'red',
                            },
                            click: function (...args) {
                                console.info('click', args);
                            },
                        },
                    ], */
                    /* controls: [
                        {
                            position: 'right',
                            html: '隐藏弹幕',
                            click: function () {
                                _this.instance.plugins.artplayerPluginDanmuku.hide();
                            },
                        },
                        {
                            position: 'right',
                            html: '显示弹幕',
                            click: function () {
                                _this.instance.plugins.artplayerPluginDanmuku.show();
                            },
                        },
                    ], */
                    // 是否在底部控制栏里显示 画质选择 列表
                    /* quality: [
                        {
                            default: true,
                            html: '480P',
                            url: '/assets/sample/video.mp4',
                        },
                        {
                            html: '720P',
                            url: '/assets/sample/video.mp4',
                        },
                    ], */
                    highlight: [
                        {
                            time: 60,
                            text: 'One more chance',
                        },
                        {
                            time: 120,
                            text: '谁でもいいはずなのに',
                        }
                    ],
                    // 在进度条上设置 预览图
                    /* thumbnails: {
                        url: '/assets/sample/thumbnails.png',
                        number: 60,
                        column: 10,
                    }, */
                    // 设置视频的字幕,支持字幕格式:vtt, srt, ass
                    /* subtitle: {
                        url: '/assets/sample/subtitle.srt',
                        type: 'srt',
                        encoding: 'utf-8',
                        escape: true,
                        style: {
                            color: '#03A9F4',
                            'font-size': '30px',
                        },
                    }, */
                    // 用于替换默认图标,支持 Html 字符串和 HTMLElement
                    /* icons: {
                        loading: '<img src="/assets/img/ploading.gif">',
                        state: '<img src="/assets/img/state.png">',
                    }, */
                    plugins: [
                        artplayerPluginDanmuku({
                            // 使用弹幕数组
                            /* danmuku: [
                                {
                                    text: '1-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                    time: 1, // 弹幕时间, 默认为当前播放器时间
                                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                                    border: false, // 弹幕是否有描边, 默认为 false
                                    style: {}, // 弹幕自定义样式, 默认为空对象
                                },
                                {
                                    text: '3-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                    time: 3, // 弹幕时间, 默认为当前播放器时间
                                    mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                    color: '#FFFFFF', // 弹幕颜色,默认为白色
                                    border: false, // 弹幕是否有描边, 默认为 false
                                    style: {}, // 弹幕自定义样式, 默认为空对象
                                }
                            ], */
                            // 使用异步返回
                            danmuku: function () {
                                return new Promise((resovle) => {
                                    return resovle([
                                        {
                                            text: '1-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                            time: 3, // 弹幕时间, 默认为当前播放器时间
                                            mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                            color: '#FFFFFF', // 弹幕颜色,默认为白色
                                            border: false, // 弹幕是否有描边, 默认为 false
                                            style: {}, // 弹幕自定义样式, 默认为空对象
                                        },
                                        {
                                            text: '3-初始弹幕数据(⊙o⊙)?' + videoItem?.videoId, // 弹幕文本
                                            time: 4, // 弹幕时间, 默认为当前播放器时间
                                            mode: 0, // 弹幕模式: 0: 滚动(默认),1: 顶部,2: 底部
                                            color: '#FFFFFF', // 弹幕颜色,默认为白色
                                            border: false, // 弹幕是否有描边, 默认为 false
                                            style: {}, // 弹幕自定义样式, 默认为空对象
                                        }
                                    ]);
                                });
                            },
                            // danmuku: '/assets/sample/danmuku.xml',

                            // 以下为非必填
                            speed: 5, // 弹幕持续时间,范围在[1 ~ 10]
                            margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比
                            opacity: 1, // 弹幕透明度,范围在[0 ~ 1]
                            color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖
                            mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部
                            modes: [0, 1, 2], // 弹幕可见的模式
                            fontSize: 25, // 弹幕字体大小,支持像素数字和百分比
                            antiOverlap: true, // 弹幕是否防重叠
                            synchronousPlayback: false, // 是否同步播放速度
                            mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部
                            heatmap: true, // 是否开启热力图
                            width: 512, // 当播放器宽度小于此值时,弹幕发射器置于播放器底部
                            points: [], // 热力图数据
                            
                            visible: true, // 弹幕层是否可见
                            emitter: true, // 是否开启弹幕发射器
                            maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000]
                            lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60]
                            theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效
                            OPACITY: {}, // 不透明度配置项
                            FONT_SIZE: {}, // 弹幕字号配置项
                            MARGIN: {}, // 显示区域配置项
                            SPEED: {}, // 弹幕速度配置项
                            COLOR: [], // 颜色列表配置项
                            
                            // 手动发送弹幕前的过滤器,返回 true 则可以发送,可以做存库处理
                            beforeEmit(danmu) {
                                return new Promise((resolve) => {
                                    console.log('beforeEmit弹幕',danmu);
                                    setTimeout(() => {
                                        resolve(true);
                                    }, 1000);
                                });
                            },
                            filter: (danmu) => {
                                console.log('filter弹幕',danmu);
                                return danmu.text.length <= 100
                            }, // 弹幕载入前的过滤器
                            beforeVisible: (e) => {
                                console.log('beforeVisible弹幕',e);
                                return true
                            }, // 弹幕显示前的过滤器,返回 true 则可以发送
                        }),
                    ],
                },
                // 构造函数接受一个函数作为第二个参数,播放器初始化成功且视频可以播放时触发,和ready事件一样
                // 等同于:this.instance.on('ready', function() {}
                function onReady(art) {
                    console.log('播放器初始化成功且视频可以播放时触发1', art == _this.instance);
                },
            );
            // 有时候 url 地址没那么快知道,这时候你可以异步设置 url
            /* setTimeout(() => {
                this.instance.url = 'http://127.0.0.1/test1.mp4';
            }, 5000); */
            // 播放器初始化成功且视频可以播放时触发
            this.instance.on('ready', function () {
                console.log('播放器初始化成功且视频可以播放时触发2');
                // _this.instance.play();
            })
            window.art = this.instance
            this.$nextTick(() => {
                this.$emit("get-instance", this.instance);
            });
        }
    },
    
    beforeDestroy() {
        if (this.instance && this.instance.destroy) {
            this.instance.destroy(false);
        }
    },
}

</script>

<style lang="scss">
body {
    margin: 0;
}

.artplayer-app {
    /* 您可能需要初始化容器元素的大小,如: */
    /* 
    width: 400px;
    height: 300px; 
    */
    /* 或者使用 aspect-ratio: */
    width: 800px;
    aspect-ratio: 16/9
}

ul,
li {
    list-style-type: none;
    padding: 0;
    margin: 0;
}
.btn-wrapper {
    width: 1200px;
    margin: auto;
}
.video-area {
    width: 1200px;
    margin: 20px auto;
    display: flex;

    .art-wrapper {
        border: 1px solid #ccc;
    }

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

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

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

                &:hover {
                    background-color: #8cc6f2;
                    border-radius: 4px;
                    cursor: pointer;
                    color: #fff;
                }
                &.active {
                    background-color: #8cc6f2;
                    color: #fff;
                }
            }
        }
    }
}
</style>

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

相关文章:

  • GITHUB的若干操作
  • 【LeetCode Hot100 链表(上)】相交链表、反转链表、回文链表、环形链表、合并两个有序链表、两数相加
  • 学习总结2.19
  • Flutter基础入门
  • HarmonyOS全栈开发指南:从入门到精通,构建万物智联的未来生态(三)
  • INA219电流、电压、功率测量芯片应用
  • 使用(xshell+xftp)将前端项目部署到服务器
  • LeetCode 0624.数组列表中的最大距离:只关心最小最大值
  • 智慧场馆运营系统
  • jenkins自动发版vue前端笔记
  • 2021年下半年软件设计师下午试卷题型和考点总结(附真题及答案解析)
  • JavaScript数组-遍历数组
  • 手机控制电脑远程关机
  • sklearn.ConfusionMatrixDisplay可视化混淆矩阵
  • Golang GORM系列:GORM无缝集成web框架
  • Vue.js 定义 Vue CLI 配置
  • C# ConcurrentQueue 使用详解
  • Python数据可视化简介
  • 支持 30+ AI 大模型!一站式聚合 GPT-4、Claude、DeepSeek、通义千问、文心一言等全球顶级模型!
  • 面试基础---如何设计一个高并发的抢购系统(电商)