一、视频直播的协议

视频直播服务支持输出RTMP、FLV、HLS、ARTC四种协议

  • RTMP(Real Time Messaging Protocol,实时消息传送协议)
    是Adobe公司为Flash播放器和服务器之间音频、视频和数据传输开发的开放协议。RTMP协议比较全能,既可以用来推送,又可以用来直播。其核心理念是将大块的视频帧和音频帧“剁碎”,然后以小数据包的形式在互联网上进行传输,且支持加密,因此隐私性相对比较理想,但拆包组包的过程比较复杂,所以在海量并发时容易出现一些不可预期的稳定性问题。
  • HLS
    HLS是苹果推出的解决方案。
    将视频分成5秒10秒的视频小分片,然后用m3u8索引表进行管理。由于客户端下载到的视频都是5秒10秒的完整数据,故视频的流畅性很好,但也同样引入了很大的延迟(HLS的一般延迟在10秒~30秒左右)。相比于FLV、HLS在iPhone和大部分Android手机浏览器上的支持非常足,所以常用于QQ和微信朋友圈的URL分享。在safari上可以直接打开m3u8格式的文件。
  • FLV
    HTTP-FLV协议由Adobe公司主推。
    格式极其简单,只是在大块的视频帧和音视频头部加入一些标记头信息,由于这种极致的简洁,在延迟表现和大规模并发方面都很成熟。唯一的不足就是在手机浏览器上的支持非常有限,但是用作手机端App直播协议却异常合适。
  • ARTC是阿里云提供的低延迟直播RTS(Real-time Streaming)解决方案使用的协议头。
    该方案参考WebRTC交互方式,采用UDP传输协议打造,实现可以承载大规模并发,端到端延时1秒内的低延时直播体验,同时对交互流程做了秒开优化,以及兼容更多的直播音视频特性例如AAC解码、B帧。RTS服务部署于阿里云直播节点,在接入成本、节点覆盖、承载能力上实现了平衡,大幅优化直播体验。

RTMP、HLS、HTTP-FLV、ARTC协议对比

element ui直播播放组件_css

二、推流+拉流是什么?

  • 推流:把本地的视频直播推到服务器上,用OBS可以推流
  • 拉流:已有直播内容,从指定的地址拉取

三、 浏览器播放器选择,为什么选择vue-video-player

  • video.js:包比较大,实现hls直播的时候其实是内嵌了hls.js的代码,由于封装好UI和功能,使其不够灵活,修改UI时需要通过插件实现。
  • vue-video-player:将video.js集成到了Vue中,考虑vue项目,技术融合比较好
  • clappr:就是个垃圾,反正我不会,不妨碍我骂他垃圾。气死。
  • 阿里云播放器
  • 大牛播放器
  • flv.js:bilibili开源

四、实现代码

1. 下载依赖

cnpm i vue-video-player -S
cnpm i videojs-flash -S  //这个一定要下载

1. 引入

  • 全局引入
import VideoPlayer from 'vue-video-player'
require('video.js/dist/video-js.css')
require('vue-video-player/src/custom-theme.css')
Vue.use(VideoPlayer)
//引入 hls,视频直播(m3u8)必须引入的
import 'videojs-contrib-hls'
//播放rtmp视频
import 'videojs-flash'

这个我没有在全局引入,只有这个页面用到播放器

  • 局部引入
import { videoPlayer } from 'vue-video-player'
  import 'video.js/dist/video-js.css'
  import 'vue-video-player/src/custom-theme.css'
  // import 'videojs-contrib-hls/dist/videojs-contrib-hls' 
  //引入 hls,视频直播(m3u8)必须引入的
  import 'videojs-contrib-hls'
  //播放rtmp视频
  import 'videojs-flash'

  export default {
    name: 'live',
    components: {
      videoPlayer
    },

main.js中

//引入 hls,视频直播(m3u8)必须引入的
import 'videojs-contrib-hls'
//播放rtmp视频
import 'videojs-flash'

3. 基本功能可行。

<template>
  <div class="liveView">
    <video-player
      class="vjs-custom-skin"
      ref="videoPlayer"
      :options="playerOptions"
      @ready="onPlayerReadied"
      @timeupdate="onTimeupdate"
    >
    </video-player>

    <div class="inputWrapper">
      <div class="form-group row">
        <label for="" class="col-sm-4 col-form-label">HLS: </label>
        <div class="col-sm-8">
          <input class="form-control" type="text" placeholder="HLS url here" v-model="streams.hls" />
        </div>
      </div>
    </div>
    <div class="buttonWrapper">
      <button class="btn btn-primary" type="button" @click="enterStream">Apply</button>
    </div>
  </div>
</template>

<script>
  import { videoPlayer } from 'vue-video-player'
  import 'video.js/dist/video-js.css'
  import 'vue-video-player/src/custom-theme.css'
  // import 'videojs-contrib-hls/dist/videojs-contrib-hls' 
  //引入 hls,视频直播(m3u8)必须引入的
  import 'videojs-contrib-hls'
  //播放rtmp视频
  import 'videojs-flash'

  export default {
    name: 'live',
    components: {
      videoPlayer
    },
    data() {
      return {
        initialized: false,
        currentTech: '',
        streams: {
          rtmp: '',
          hls: ''
        },
        playerOptions: {
          overNative: true,
          autoplay: false,
          controls: true,
          techOrder: ['html5'],
          sourceOrder: true,
          flash: {
            hls: { withCredentials: false }
          },
          html5: { hls: { withCredentials: false } },
          sources: [
            {
              withCredentials: false,
              type: 'application/x-mpegURL',
              src: 'http://ivi.bupt.edu.cn/hls/btv1hd.m3u8'
            }
          ],
           controlBar: {
             timeDivider: false, // 时间分割线
             durationDisplay: false, // 总时间
             progressControl: true, // 进度条
             customControlSpacer: true, // 未知
             fullscreenToggle: true // 全屏
           },
        }
      }
    },
    computed: {
      player() {
        return this.$refs.videoPlayer.player
      },
      currentStream() {
        return this.currentTech === 'Flash' ? 'RTMP' : 'HLS'
      }
    },
    methods: {
      onPlayerReadied() {
        if (!this.initialized) {
          this.initialized = true
          this.currentTech = this.player.techName_
        }
      },
      // record current time
      onTimeupdate(e) {
        console.log('currentTime', e.cache_.currentTime)
      },
      enterStream() {
        this.playerOptions.sources[0].src = this.streams.hls
        this.playerOptions.autoplay = true
      },
      changeTech() {
        if (this.currentTech === 'Html5') {
          this.playerOptions.techOrder = ['html5']
        } else if (this.currentTech === 'Flash') {
          this.playerOptions.techOrder = ['flash']
        }
        this.playerOptions.autoplay = true
      }
    }
  }
</script>

<style scoped>
  .liveView {
    position: relative;
  }

  .selectWrapper {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 20px;
    line-height: 30px;
    margin: 20px;
    vertical-align: baseline;
  }

  .inputWrapper {
    width: 500px;
    margin: 0 auto;
  }

  .buttonWrapper {
    text-align: center;
  }
</style>