近些时间,在项目开发过程中,遇到与视频相关的内容,即前端播放直播流rtmp/rtsp/hls等。而针对这个问题,我们则需要使用videojs这个插件来进行开发,当然如果是使用了vue-cli的话,可以采用vue-video-player,这是对videojs做了进一步的封装,可以引入做组件使用。详细过程如下:

1. VIDEOJS: ERROR: The “flash” tech is undefined. Skipped browser support check for that tech

    顾名思义,就是你的插件里的flash找不到了,具体原因在排查过程中觉得可能是内外都有videojs-flash造成的。这个问题也很经典,网上解决方法五花八门,但真正能用的好像都没有。
    针对这个问题,首先要确保所有相关的插件都是使用npm安装而不是cnpm安装,因为 cnpm 安装 的插件可能会有奇奇怪怪的问题。确保了这个之后,如果是使用了 vue-video-player 来做开发的话,需要进行以下步骤:

  • 1. vue-video-player本身已内置了videojs-flash,以及video-contrib-hls这两个分别播放rtmp / m3u8的插件,所以安装 vue-video-player无需再另外安装这两个插件
  • 2. 除去第一步,在排除没有禁用网站flash的情况下,如果还报这个错误的话,可参考把node_modules,package-lock.json一起移除掉后,再 npm install
  • 3. 第二步尝试 1次之后如果无效,目前我的解决方法是移除掉所有跟videojs有关的插件,直接npm i vue-video-player@5.0.2 -S这个问题,目前我就是这样就解决了,具体原因推测为videojs-flash互相冲突,网上很多种方法都不一定有用,遇到这个问题一定要耐心多尝试几次,也许就能搞定了


2. 封装视频播放组件

    以下只是其中一个例子,主要用于播放rtmp流的直播视频,需要浏览器开启flash配合使用,具体代码如下:

<!--该组件除了默认的底部控制条外,监听了播放,暂停,结束事件,以及设置了顶部条的最大化和关闭视频-->
<template>
    <!--双击全屏,默认就是这样,这一步主要针对videojs没有配置控制条实现-->
    <div @dblclick.stop="onVideoDBClick" class="rmtp-video-player" @mousemove="showTitleBar = true" @mouseleave="showTitleBar = false">
        <div class="head-menu" :class="{showbar: showTitleBar}">
            <slot name="video-name"></slot>
            <div class="right-button">
                <!--这里阻止点击事件冒泡-->
                <i class="icon-maximize" @click.prevent="onMaximize" :title="Maximize ? '还原' : '最大化'" :class="Maximize ? 'el-icon-files' : 'el-icon-full-screen'" />
                <i class="el-icon-close" @click.prevent="onClose" title="关闭" />
            </div>
        </div>
        <video-player class="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="videoParams" @play="onPlayerPlay" @pause="onPlayerPause" @ended="onPlayerEnded"></video-player>
    </div>
</template>

<script>
    import 'video.js/dist/video-js.css';
    import 'videojs-flash';
    import 'videojs-contrib-hls';
    import {
        videoPlayer
    } from 'vue-video-player';
    export default {
        props: {
            videoOptions: [Object], //视频播放选项
            videoUrl: {
                //视频地址
                type: String,
                default: '',
            },
            videoPoster: {
                //视频封面
                type: String,
                default: '',
            },
            maximize: {
                // 是否开启了最大化
                type: Boolean,
                default: false,
            },
        },
        components: {
            // 引入videoplayer
            videoPlayer,
        },
        computed: {
            videoParams: {
                get() {
                    return {
                        playbackRates: [0.7, 1.0, 1.5, 2.0],
                        // autoplay: true,
                        muted: false,
                        // controls: false,
                        loop: true,
                        preload: 'auto',
                        language: 'zh-CN',
                        fluid: false,
                        sources: [{
                            withCredentials: false,
                            type: '',
                            src: this.videoUrl
                        }],
                        flash: {
                            hls: {
                                withCredentials: false
                            }
                        },
                        html5: {
                            hls: {
                                withCredentials: false
                            }
                        },
                        techOrder: ['html5', 'flash'],
                        poster: 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596173685045&di=76144e8eb9bc70c04b6f55dd1b4752ed&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190109%2F352cd11ec90e4c6b9c20800562967137.jpeg',
                        notSupportedMessage: '此视频暂无法播放,请稍后再试',
                        controlBar: {
                            timeDivider: true,
                            durationDisplay: true,
                            remainingTimeDisplay: false,
                            fullscreenToggle: true
                        }
                    }
                },
                set() {}
            }
        },
        data() {
            return {
                showTitleBar: false,
                Maximize: this.maximize,
            };
        },
        methods: {
            onPlayerPlay(e) {
                // 播放器开始播放事件,目前仅将player对象弹出给父组件
                this.showTitleBar = false;
                this.$emit('play', e);
            },

            onPlayerPause(e) {
                // 播放器暂停播放事件,同上
                this.showTitleBar = true;
                this.$emit('pause', e);
            },

            onPlayerEnded(e) {
                // 直播流没有结束的概念,留着备用
                this.$emit('end', e);
            },

            onVideoDBClick() {
                // 双击全屏
                if (!this.$refs.videoPlayer.player.isFullscreen()) {
                    this.$refs.videoPlayer.player.requestFullscreen();
                }
            },

            onMaximize() {
                // 视频单个最大化,占满整个弹窗,类似于视频网站的网页全屏
                this.Maximize = !this.Maximize;
                this.$emit('maximize', this.Maximize);
            },

            onClose() {
                // 关闭视频事件
                this.$emit('close');
            },
        },
    };
</script>

<style lang="less">
    .rmtp-video-player {
        position: relative;

        .head-menu {
            position: absolute;
            width: 100%;
            color: #fff;
            top: 0;
            left: 0;
            right: 0;
            height: 25px;
            background-color: rgba(43, 51, 63, 0.7);
            z-index: 9;
            opacity: 0;

            &.showbar {
                //出现顶部条
                opacity: 1;
                transition: 0.5s;
            }

            .right-button {
                position: absolute;
                right: 5px;
                top: 2px;

                i {
                    cursor: pointer;
                    margin: 0 5px;
                }

                z-index: 3;
            }

            transition: 5s; //模仿底部条缓慢消失

            .app-title {
                display: inline-block;
                width: 100%;
            }
        }

        .video-player {
            cursor: pointer;

            .video-js {
                width: 100%;
            }

            .vjs-big-play-button {
                left: 0;
                right: 0;
                bottom: 0;
                top: 0;
                width: 1.5em;
                border-radius: 50%;
                margin: auto;
            }
        }
    }
</style>

    以上就是一个简单的视频播放组件的封装,是在vue-video-player的基础上进一步封装一些跟业务相关的处理逻辑,目前还在更新中,暂定为以上例子。由于对该插件还不是很熟悉,上述例子目前仅支持离线的rtmp播放。


3. 编写视频播放界面(网格模式与播放列表模式)

    这里主要是视频播放列表比较重要,弹窗部分就忽略了,可使用element-ui提供的el-dialogs或自己另外封装一个可以实现最大化最小化功能的弹窗都可,视频播放列表由于采用的是弹性布局的样式,必须从外到内都是弹性布局,省略外部弹窗的样式代码,具体如下:

<!--左边是带过滤搜索框的树,右边0为网格视频列表,1为普通缩略图播放列表-->
<template>
    <div class="demo-video-all">
        <div class="left" :class="{rightMaximize: maximize}">
            <app-tree ref="tree" :show-checkbox="active==='1'" default-expand-all :check-on-click-node="active==='1'" :data="videoBody" :default-checked-filter="() => true" :default-checked-keys="checkedKeys" node-key="id" :props="treeProps" :expand-on-click-node="false" @check-change="handleCheckChanged" />
        </div>
        <!--以下视频的最大化均是通过绝对定位完成,如遇到弹性布局的,设置not选择器把其他的都隐藏掉,最大化那个绝对定位铺满弹窗即可,不可丢弃弹性布局-->
        <div class="right">
            <div class="right-option">
                <i class="el-icon-s-grid" :class="{active: active == '0'}" @click="active = '0'" title="列表"></i>
                <i class="el-icon-picture" :class="{active: active == '1'}" @click="active = '1'" title="缩略图"></i>
            </div>
            <template v-if="active==='1'">
                <template v-if="checkedList[0]">
                    <div class="first-of-all" :class="{maximize: checkedList[0].maximize, someOneMaximize: someOneMaximize}">
                        <rtmp-player :videoUrl="checkedList[0].uri" @play="onPlay($event,checkedList[0].id)" @maximize="onMaximize($event, checkedList[0].id)" @close="onClose(checkedList[0].id)" :ref=" `player-${checkedList[0].id}` ">
                            <template slot="video-name">
                                <app-title :value="checkedList[0].name" :italic="false" :size="16" textAlign="center" />
                            </template>
                        </rtmp-player>
                    </div>
                </template>
                <template v-if="checkedList.length > 1">
                    <div class="is-going-to-play" :class="{maximize: someOneMaximize}">
                        <rtmp-player v-for="item in checkedList.slice(1)" :key="item.id" :videoUrl="item.uri" @play="onPlay($event, item.id)" @maximize="onMaximize($event, item.id)" @close="onClose(item.id)" :class="{'player-maximize': item.maximize}" :ref=" `player-${item.id}` ">
                            <template slot="video-name">
                                <app-title :value="item.name" :italic="false" :size="14" textAlign="center" />
                            </template>
                        </rtmp-player>
                    </div>
                </template>
            </template>
            <!--以下网格化视频列表分页,是通过弹性布局加浮动合在一起完成-->
            <template v-if="active==='0'">
                <div class="demo-video-page">
                    <div class="demo-video-list-page">
                        <template v-for="item in videoListBody">
                            <rtmp-player :videoUrl="item.uri" :key="item.id">
                                <template slot="video-name">
                                    <app-title :value="item.name" :italic="false" :size="16" textAlign="center" />
                                </template>
                            </rtmp-player>
                        </template>
                    </div>
                    <!--分页清除浮动,固定在弹窗底部-->
                    <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-sizes="[12, 24, 60, 120]" :page-size="listPagination.pagesize" :current-page="listPagination.pageindex" layout="total, sizes, prev, pager, next, jumper" :total="total" class="pagination">
                    </el-pagination>
                </div>
            </template>
        </div>
    </div>
</template>

<script>
    import axios from '@/axios/request';
    import rtmpPlayer from '@/components/rtmp-player';
    export default {
        components: {
            rtmpPlayer,
        },
        props: {
            resizeBody: [Object],
            minimize: {
                type: Boolean,
                default: false,
            },
        },
        data() {
            return {
                loading: false,
                //树的 结点数据
                videoBody: [],
                //视频分页列表
                videoListBody: [],
                listPagination: {
                    pageindex: 1,
                    pagesize: 12,
                },
                //勾选的结点key
                checkedKeys: [],
                checkedList: [],
                pagination: {
                    pageindex: 1,
                    pagesize: 9999,
                },
                total: 0,
                treeProps: {
                    label: 'name',
                    children: 'children',
                },
                // 是否有某个视频最大化
                maximize: false,
                //缩略图还是网格
                active: '0',
            };
        },
        computed: {
            // 底部列表是否有 某个视频最大化
            someOneMaximize() {
                return this.checkedList.slice(1).some(item => item.maximize);
            },
        },
        created() {
            this.getVideoList();
            this.getVideoListPage();
        },
        methods: {
            getVideoList() {
                /**
                 *    这里是左边的树结点数据的请求接口
                 * */
                //   this.videoBody = response.data.item.list;
            },

            getVideoListPage() {
                /**
                 *    这里是分页请求视频列表的接口请求
                 * */
                //   this.total = response.data.total;
                //   this.videoListBody = response.data.list; // 视频列表
            },
            // 监听树节点的勾选
            handleCheckChanged(node, checked) {
                if (checked) {
                    if (!this.checkedKeys.includes(node.id)) {
                        this.checkedKeys.push(node.id);
                        this.checkedList.push(Object.assign({}, node, {
                            maximize: false
                        }));
                    }
                } else {
                    this.checkedKeys.splice(
                        this.checkedKeys.findIndex(id => id === node.id),
                        1
                    );
                    this.checkedList.splice(
                        this.checkedList.findIndex(Node => Node.id === node.id),
                        1
                    );
                }
            },
            // 视频播放
            onPlay(player, id) {
                const playIndex = this.checkedList.findIndex(Node => Node.id === id);
                if (playIndex > 0) {
                    const temp = this.checkedList[0];
                    this.$set(this.checkedList, 0, this.checkedList[playIndex]);
                    this.$set(this.checkedList, playIndex, temp);
                }
            },

            // 关闭视频,顺便取消树的勾选
            onClose(id) {
                const anotherIndex = this.checkedList.findIndex(Node => Node.id === id);
                const removeKeys = this.checkedList[anotherIndex];
                this.maximize = false;
                this.$nextTick(() => {
                    this.$refs.tree.$children[1].setChecked(removeKeys, false);
                });
            },

            //最大化某个视频
            onMaximize(maximize, id) {
                const index = this.checkedList.findIndex(Node => Node.id === id);
                this.checkedList[index].maximize = maximize;
                this.maximize = maximize;
            },

            // 分页页数
            handleSizeChange(val) {
                this.listPagination.pagesize = val;
                this.getVideoListPage();
            },

            // 分页当前页
            handleCurrentChange(val) {
                this.listPagination.pageindex = val;
                this.getVideoListPage();
            },
        },
    };
</script>

<style lang="less">
    .demo-video-all {
        padding: 10px;
        flex: 1;
        display: flex;
        overflow: hidden;

        .el-tree {
            margin: 10px 0;
            background: transparent;
        }

        .el-tree-node__label {
            color: rgba(255, 255, 255, 0.8);
        }

        .el-tree-node__content:hover {
            background: rgba(0, 84, 160, 0.5) !important;
        }

        .el-tree-node.is-current .el-tree-node__content {
            background: transparent;
        }

        .app-tree__search .el-input__inner {
            background-color: transparent;
            width: 98%;
            border-radius: 0;
            line-height: 30px;
            border-color: rgba(255, 255, 255, 0.3);
            color: rgba(255, 255, 255, 0.8);

            &:focus {
                border-color: rgba(0, 84, 160, 0.8);
                box-shadow: 0 0 10px 3px rgba(0, 84, 160, 0.3);
            }
        }

        .left {
            width: 200px;
            border-right: 1px solid rgba(0, 84, 160, 0.8);
            padding: 0 15px 0 20px;
            opacity: 1;
            transition: 0.5s;

            &.rightMaximize {
                opacity: 0;
            }

            .el-tree {
                overflow-y: hidden;
                max-height: 600px;
                padding-right: 18px;

                &:hover {
                    overflow-y: auto;
                }
            }
        }

        ::-webkit-scrollbar {
            width: 5px;
            height: 5px;
        }

        ::-webkit-scrollbar-thumb {
            border-radius: 8px;
            background: rgba(0, 84, 160, 0.8);
        }

        ::-webkit-scrollbar-track {
            border-radius: 8px;
        }

        .right {
            flex: 1;
            display: flex;
            flex-direction: column;
            overflow: hidden;

            .first-of-all {
                width: 65%;
                height: 100%;
                margin: 0 auto;
                display: flex;
                flex-direction: column;

                .rmtp-video-player {
                    display: flex;
                    flex-direction: column;
                    flex: 1;

                    .video-player {
                        display: flex;
                        flex-direction: column;
                        flex: 1;

                        .video-js {
                            width: 100%;
                            flex: 1;
                            padding-top: 45%;
                        }
                    }
                }

                &.maximize {
                    width: 100%;
                    position: absolute;
                    top: 0;
                    left: 0;
                    bottom: 0;
                    right: 0;
                    z-index: 9999;

                    .rmtp-video-player {
                        .video-player {
                            .video-js {
                                padding-top: 0;
                            }
                        }
                    }
                }

                &.someOneMaximize {
                    opacity: 0;
                }

                opacity: 1;
                transition: 0.5s;
            }

            .is-going-to-play {
                width: 100%;
                overflow: auto hidden;
                height: 250px;
                margin: 20px 10px;
                display: flex;
                flex-flow: row nowrap;

                &.maximize {
                    flex: 1;
                    width: 100%;
                    height: 100%;
                    position: absolute;
                    left: -10px;
                    top: -20px;
                    z-index: 9999;
                    overflow: hidden;
                    transition: 0.5s;

                    .rmtp-video-player {
                        position: absolute;
                        left: 0;
                        margin: 0;
                        padding: 0;
                        right: 0;
                        width: 100%;
                        height: 100%;

                        &:not(.player-maximize) {
                            visibility: hidden;
                            opacity: 0;
                        }

                        .video-player {
                            display: flex;
                            flex-direction: column;
                            flex: 1;

                            .video-js {
                                padding-top: 0;
                            }
                        }
                    }
                }

                .rmtp-video-player {
                    flex: 0 0 250px;
                    margin: 0 10px;
                    display: flex;
                    flex-direction: column;
                    transition: 0.5s;

                    .video-player {
                        display: flex;
                        flex-direction: column;
                        flex: 1;

                        .video-js {
                            width: 100%;
                            flex: 1;
                        }
                    }
                }
            }

            .right-option {
                position: absolute;
                right: 0;
                top: 0;
                z-index: 999;
                border-left: solid 1px #409eff;
                border-bottom: solid 1px #409eff;
                border-radius: 0 0 0 8px;
                width: 30px;

                i {
                    display: inline-block;
                    margin: 0 5px;
                    font-size: 18px;
                    line-height: 26px;
                    cursor: pointer;

                    &:hover {
                        color: #1ccdcc;
                    }

                    &.active {
                        color: #1ccdcc;
                    }
                }
            }

            .demo-video-page {
                flex: 1;
                display: flex;
                flex-direction: column;
                overflow: hidden;

                .demo-video-list-page {
                    flex: 1;
                    overflow: hidden auto;

                    .rmtp-video-player {
                        float: left;
                        position: relative;
                        width: 300px;
                        margin: 5px 12px;

                        .head-menu {
                            i {
                                display: none;
                            }
                        }

                        .video-player {
                            display: flex;
                            flex-flow: column wrap;

                            .video-js {
                                width: 100%;
                                flex: 0 0 150px;
                            }
                        }
                    }
                }

                .el-pagination {
                    border-top: solid 1px rgba(0, 145, 202, 0.8);
                    padding-bottom: 10px;
                }

                .pagination {
                    text-align: center;
                    border-top: none !important;
                    margin: 5px auto;
                    clear: both;
                    display: block;
                    width: 100%;
                }
            }
        }
    }
</style>

4. 编写视频播放界面(监控网格模式)

  以上这种模式,排版布局终归不是特别的好,而且还有交换导致的播放问题,后续更新为以下这种模式,网格从少到多的区分,兼容较好,保留右侧操纵视频的界面还未完成,暂定如下:

<template>
  <div class="demo-video-player-page">
    <div class="left-list">
      <app-tree
          ref="videoTree"
          v-loading="loading"
          show-checkbox
          default-expand-all
          check-on-click-node
          :data="videoData"
          :default-checked-filter="() => true"
          :default-checked-keys="checkedKeys"
          node-key="id"
          :props="treeProps"
          :expand-on-click-node="false"
          @check-change="onCheckedChange"
      />
    </div>
    <div class="middle-player-list">
      <div class="content" ref="videoContent">
        <div v-for="(item, index) in videoList" :key="index" :class="`video-player-${row}x${row}`" >
            <div v-if="item" @click.stop="activeNum=index" class="container">
                <rtmp-player :videoUrl="item.uri" v-if="item && item.uri" :class="{active: activeNum == index}" ></rtmp-player>
            </div>
            <div class="place-holder" v-else :class="{active: activeNum == index}" @click="activeNum = index">
              <app-title
                :value="(index+1).toString()"
                :italic="false"
                :size="14"
                textAlign="center"
              />
            </div>
        </div>
      </div>
      <div class="footer">
         <i v-for="item in 4" :key="item"
          class="iconfont"
          :title="`${item}x${item}`"
          :class="[`icon-grid-${item}x${item}`, {active: row == item}]"
          :style="{fontSize: item < 3 ? '17px': ''}"
          @click="onShowGrid(item)"
        ></i>
        <i class='icon-svg-fullscreen'
        @click="onFullScreen" title="全屏"></i>
      </div>
    </div>
  </div>
</template>

<script>
import axios from '@/axios/request'
import rtmpPlayer from '@/components/rtmp-player'
export default {
  name: 'VideoPlayer',
  components: {
    rtmpPlayer
  },
  data () {
    return {
      videoList: [],
      row: 4,
      activeNum: -1,
      videoData: [],
      loading: false,
      checkedKeys: [],
      treeProps: {
        label: 'name',
        children: 'children'
      },
      pagination: {
        pageindex: 1,
        pagesize: 16
      },
      isFullScreen: false
    }
  },
  mounted () {
    this.onShowGrid(this.row)
    this.getVideoList()
  },
  methods: {
    onShowGrid (row) {
      this.row = row
      this.activeNum = -1
      const hasVideo = this.videoList.filter(Boolean) // 过滤出有视频数据的数组元素
      this.videoList = new Array(row ** 2) // 重新变形数组
      for (let i = 0; i < Math.min(hasVideo.length, row ** 2); i++) {
        this.videoList[i] = Object.assign([], hasVideo[i])
      } // 按顺序赋值
      this.$nextTick(() => {
        this.$refs.videoTree.$children[1].setCheckedNodes(this.videoList, true)
      })  //重新渲染左边树的勾选情况
    },

    getVideoList () {
      /**
       * 这里是请求视频数据,并赋值给this.videoData
       * */
    },

    onCheckedChange (node, checked) {
      if (checked) {
        if (!this.checkedKeys.includes(node.id)) {
          if (this.activeNum >= 0) {  //如果是已有的视频,执行替换逻辑
            const removeVideo = this.videoList[this.activeNum]
            if (removeVideo && removeVideo.id) {
              this.checkedKeys.splice(this.checkedKeys.findIndex(item => item === removeVideo.id), 1)
              this.$nextTick(() => {
                this.$refs.videoTree.$children[1].setChecked(removeVideo, false)
              })
            }
          } else {
            this.activeNum = 0
          }
          //否则替换黑框
          this.checkedKeys.push(node.id)
          this.videoList[this.activeNum] = Object.assign({}, node)
          //循环替换,防止出错
          this.activeNum = (this.activeNum + 1) % this.videoList.length
        }
      } else {
        //如果左边取消了视频勾选,找到对应的数组元素删除
        this.checkedKeys.splice(this.checkedKeys.findIndex(id => id === node.id), 1)
        //网格切换为黑框
        const index = this.videoList.findIndex(Node => Node && Node.id === node.id)
        if (index >= 0) {
          this.videoList[index] = undefined
        }
      }
    },

    onFullScreen () {
      //中间整个监控块全屏
      this.isFullScreen = true
      const videoDom = this.$refs.videoContent
      if (this.isFullScreen) {
        //全屏
        if (videoDom.requestFullscreen) {
          videoDom.requestFullscreen()
        } else if (videoDom.webkitRequestFullScreen) {
          videoDom.webkitRequestFullScreen()
        } else if (videoDom.mozRequestFullScreen) {
          videoDom.mozRequestFullScreen()
        } else {
          videoDom.msRequestFullscreen()
        }
      } else {
        //退出全屏
        if (document.exitFullscreen) {
          document.exitFullscreen()
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen()
        } else if (document.msExitFullscreen) {
          document.msExiFullscreen()
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen()
        }
      }
    }
  }
}
</script>

<!--全为弹性布局-->
<style lang="less">
.demo-video-player-page {
  flex: 1;
  display: flex;
  color: #fff;

  .left-list {
    width: 13%;
    .el-tree {
      margin: 10px 0;
      background: transparent;
    }
    .el-tree-node__label {
        color: rgba(255, 255, 255, 0.8);
    }
    .el-tree-node__content:hover {
        background: rgba(0, 84, 160, 0.5) !important;
    }
    .el-tree-node.is-current .el-tree-node__content {
        background: transparent !important;
    }
    .app-tree__search .el-input__inner {
        background-color: transparent;
        width: 85%;
        border-radius: 0;
        line-height: 30px;
        border-color: rgba(255, 255, 255, 0.3);
        color: rgba(255, 255, 255, 0.8);
        &:focus {
            border-color: rgba(0, 84, 160, 0.8);
            box-shadow: 0 0 10px 3px rgba(0, 84, 160, 0.3);
        }
    }
  }

  .middle-player-list {
    flex: 1;
    display: flex;
    flex-direction: column;
    .content {
      max-height: calc(100% - 40px);
      flex: 1;
      display: flex;
      flex-flow: row wrap;
      .place-holder {
        flex: 1;
        background-color: rgba(0, 0, 0, 0.8);
        border: solid 1px #222;
        text-align: center;
        cursor: pointer;
        display: flex;
        flex-direction: column;
        justify-content: center;
        &.active {
          border: solid 1px #CDAD00
        }
      }
      .container {
        flex: 1;
        display: flex;
        flex-direction: column;
        .rmtp-video-player {
          border: solid 1px #222;
          &.active {
            border: solid 1px #CDAD00;
            border-right: solid 1.5px #CDAD00;
          }
        }
      }
      .video-player-3x3 {
        flex: 0 0 33.33%;
        display: flex;
        flex-direction: column;
      }

      .video-player-4x4 {
        flex: 0 0 25%;
        display: flex;
        flex-direction: column;
      }

      .video-player-2x2 {
        flex: 0 0 50%;
        display: flex;
        flex-direction: column;
      }

      .video-player-1x1 {
        display: flex;
        flex-direction: column;
        flex: 1;
      }
    }

    .footer {
      color: #fff;
      height: 40px;
      i {
        display: inline-block;
        line-height: 40px;
        padding: 0 5px;
        cursor: pointer;
        &:hover {
          color: #1ccdcc;
        }
        &:last-child {
          padding: 0;
          margin: 10px 5px;
          float: right;
        }
        &.active {
          color: #1ccdcc;
          box-shadow: 0 0 8px 3px rgba(0, 204, 255, 0.2) inset;
        }
      }
    }
  }
}
</style>

   更改 rtmpPlayer 的配置后,目前已经能播放 m3u8 及 rtmp 格式的视频,因为涉及为直播,流不稳定及网络原因会导致比较明显的卡顿,这部分问题后续再看看如何处理, 大神们有更好的方法也欢迎留言区指教,目前的话暂定为网格模式来显示。

  • alipay
  • wechat

一个好奇的人