h5使用vue实现直播(主播端和观众端)

观众端:我们是使用vue-video-player进行实现的

1.下载vue-video-player包和videojs-contrib-hls(播放插件)

npm install vue-video-player --save
npm install videojs-contrib-hls --save 

遇到下载失败的情况,可参考这篇文章,下载低版本的包

Vue-video-player下载失败(npm i 报错)_npm安装video失败-CSDN博客

2.在main.js文件中引入

import VideoPlayer from 'vue-video-player'
import 'vue-video-player/src/custom-theme.css'
import 'video.js/dist/video-js.css' 
import 'videojs-contrib-hls'
Vue.use(VideoPlayer)

3.组件中直接引入使用,下面为j基本完整组件代码

<template>
  <div>
    <videoPlayer @playing="onPlayerPlaying($event)" @ended="onPlayerEnded($event)" ref="videoPlayer" class="vjs-custom-skin videoPlayer" :options="playerOptions" :playsinline="true"></videoPlayer>
  </div>
</template>
<script>
import { videoPlayer } from 'vue-video-player';
import 'videojs-flash';
import { antiShake } from '@/utils';
export default {
  components: {
    videoPlayer
  },
  data() {
    return {
      isShowPlayButton: true,
      playerOptions: {
         // 播放器功能按钮
        controlBar: {
          //是否显示音量条,默认显示
          volumePanel: false,
          // 当前时间和持续时间的分隔符'/'
          timeDivider:false,
          // 是否显示剩余时间的功能
          remainingTimeDisplay:false,
          // 是否显示全屏按钮
          fullscreenToggle:true,
          // 是否显示直播时长
          durationDisplay:false,
          // 暂停和播放键
          playToggle:true,
          // 当前时间
          currentTimeDisplay:true,
          // 进度条
          progressControl:true,
        },
        height: '300',
        sources: [
          {
            // 解析的类型(目前只支持m3u8的视频流)
            type: 'application/x-mpegURL',
            // 后台给到的视频流地址(我这里的是测试地址)
            src: 'http://220.161.87.62:8800/hls/0/index.m3u8'
          }
        ],
        // techOrder: ['flash'],
        aspectRatio: '16:9',
         // 可选的播放速度
        playbackRates: [0.5, 1.0, 1.5, 2.0], // 可选的播放速度
        //自动播放,直播视频不支持,
        autoplay: false,
        // 默认情况下将会消除任何音频。
        muted: false, 
        // 结束之后是否重新开始
        loop: false, 
        // 直播封面
        // poster:'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
        //视频播放异常展现给用户的报错信息
        notSupportedMessage: '此视频暂无法播放,请稍后再试',
      }
    };
  },
  methods: {
    // 视频播完的回调
    onPlayerEnded() {},
    // 点击播放触发的回调函数
    onPlayerPlaying() {
      this.isShowPlayButton = true;
      this.$forceUpdate();
    }
  }
};
</script>

<style lang="scss" scoped>

</style>

给他进行配置好就可以进行基本的直播播放了,在线直播源地址可通过此文章获取

常用m3u8,rtsp,rtmp,flv,mp4直播流在线测试地址_m3u8直播源-CSDN博客

如果觉得播放器按钮太多太繁琐,可以在playerOptions的controlBar功能对象进行配置

        // 播放器功能按钮
        controlBar: {
          //是否显示音量条,默认显示
          volumePanel: false,
          // 当前时间和持续时间的分隔符'/'
          timeDivider:false,
          // 是否显示剩余时间的功能
          remainingTimeDisplay:false,
          // 是否显示全屏按钮
          fullscreenToggle:true,
          // 是否显示直播时长
          durationDisplay:false,
          // 暂停和播放键
          playToggle:true,
          // 当前时间
          currentTimeDisplay:true,
          // 进度条
          progressControl:true,
    },

当然也可以用css进行隐藏你不需要的

/* 时间 */
/deep/ .video-js .vjs-current-time,
.vjs-no-flex .vjs-current-time {
  opacity: 0;
}
// 时间后分割线
/deep/ .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-divider {
  opacity: 0;
}
// 播放按钮
.yesPlayButton {
  /deep/ .vjs-custom-skin > .video-js .vjs-big-play-button {
    opacity: 0;
  }
}

4.设置自定义按钮(组件提供的功能按钮无法满足我们需求时候)

比如我这边需要一个刷新直播的按钮和一个收起直播的按钮

// 原播放器直播时长按钮,这里我直接直接设置为透明色,加上一个背景icon图,变成自定义按钮
/deep/ .vjs-duration-display {
  background: url('../../../assets/images/retract.png') no-repeat;
  background-size: 17px;
  color: transparent;
}

// 原播放器live标识,这里我直接直接设置为透明色,加上一个背景icon图,变成自定义按钮
/deep/ .vjs-custom-skin > .video-js .vjs-control.vjs-live-control {
  background: url('../../../assets/images/refresh.png') no-repeat;
  background-size: 20px;
  color: transparent;
  position: relative;
  top: 25%;
  left: 6%;
}

最后页面点击需要判断点击的是我们自定义的哪一个,我们通过$events拿到点击的类名,这样我们就可以写我们自己的逻辑了

  <div @click="handleVideoClick($event)" :class="isShowPlayButton?'yesPlayButton':'noPlayButton'">
    <videoPlayer @playing="onPlayerPlaying($event)" @ended="onPlayerEnded($event)" ref="videoPlayer" class="vjs-custom-skin videoPlayer" :options="playerOptions" :playsinline="true"></videoPlayer>
  </div>


    handleVideoClick: antiShake(function (data) {
      if (data.srcElement.classList[0] === 'vjs-live-display') {
        // 点击刷新
        this.$emit('refresh');
      } else if (data.srcElement.classList[0] === 'vjs-duration-display') {
        // 收起直播
        this.$emit('playVedio',false)
      }
    }),

完整代码:(引入组件传入后台数据)

<template>
  <div>
    <videoPlayer @playing="onPlayerPlaying($event)" @ended="onPlayerEnded($event)" ref="videoPlayer" class="vjs-custom-skin videoPlayer" :options="playerOptions" :playsinline="true"></videoPlayer>
  </div>
</template>
<script>
import { videoPlayer } from 'vue-video-player';
import 'videojs-flash';
import { antiShake } from '@/utils';
export default {
  props: {
    cloneTabsData: {
      typeof: Object,
      default: () => {}
    },
    vedioButtonStatus: {
      typeof: Boolean,
      default: true
    },
    isNum: {
      typeof: Number,
      default: 0
    }
  },
  components: {
    videoPlayer
  },
  watch: {
    cloneTabsData: {
      deep: true,
      immediate: true,
      handler(val) {
        if (val.playUrl) {
          this.playerOptions.sources[0].src = val.playUrl.replace('http:','https:');;
        }
      }
    },
    vedioButtonStatus: {
      deep: true,
      immediate: true,
      handler(val) {
        if (val && this.isNum !== 0) {
          this.isShowPlayButton = false;
        }
      }
    }
  },
  data() {
    return {
      isShowPlayButton: true,
      playerOptions: {
         // 播放器功能按钮
        controlBar: {
          //是否显示音量条,默认显示
          volumePanel: false,
          // 当前时间和持续时间的分隔符'/'
          timeDivider:false,
          // 是否显示剩余时间的功能
          remainingTimeDisplay:false,
          // 是否显示全屏按钮
          fullscreenToggle:true,
          // 是否显示直播时长
          durationDisplay:false,
          // 暂停和播放键
          playToggle:true,
          // 当前时间
          currentTimeDisplay:true,
          // 进度条
          progressControl:true,
        },
        height: '300',
        sources: [
          {
            // 解析的类型(目前只支持m3u8的视频流)
            type: 'application/x-mpegURL',
            // 后台给到的视频流地址(我这里的是测试地址)
            src: 'http://220.161.87.62:8800/hls/0/index.m3u8'
          }
        ],
        // techOrder: ['flash'],
        aspectRatio: '16:9',
         // 可选的播放速度
        playbackRates: [0.5, 1.0, 1.5, 2.0], // 可选的播放速度
        //自动播放,直播视频不支持,
        autoplay: false,
        // 默认情况下将会消除任何音频。
        muted: false, 
        // 结束之后是否重新开始
        loop: false, 
        // 直播封面
        // poster:'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
        //视频播放异常展现给用户的报错信息
        notSupportedMessage: '此视频暂无法播放,请稍后再试',
      }
    };
  },
  methods: {
    // 视频播完的回调
    onPlayerEnded() {},
    // 点击播放触发的回调函数
    onPlayerPlaying() {
      this.isShowPlayButton = true;
      this.$forceUpdate();
    },
    // 刷新直播进度
    handleVideoClick: antiShake(function (data) {
      if (data.srcElement.classList[0] === 'vjs-live-display') {
        // 点击刷新
        this.$emit('refresh');
      } else if (data.srcElement.classList[0] === 'vjs-duration-display') {
        // 收起直播
        this.$emit('playVedio',false)
      }
    }),
  }
};
</script>

<style lang="scss" scoped>
.custom-button {
  position: absolute;
  width: 15px;
  top: 91%;
  left: 56%;
  transform: translate(-50%, -50%);
  z-index: 1;
}
/deep/ .vjs-duration-display {
  background: url('../../../assets/images/retract.png') no-repeat;
  background-size: 17px;
  color: transparent;
}

// 声音标识
/deep/ .video-js .vjs-mute-control .vjs-icon-placeholder:before, .vjs-icon-volume-high:before {
  display: none;
}
// 直播live标识
/deep/ .vjs-custom-skin > .video-js .vjs-control.vjs-live-control {
  background: url('../../../assets/images/refresh.png') no-repeat;
  background-size: 20px;
  color: transparent;
  // border: 1px solid yellow;
  position: relative;
  top: 25%;
  left: 6%;
  // margin-top: 3%;
  // transform: scale(0.5);
  // margin-left: 1%;
}
/* 时间 */
/deep/ .video-js .vjs-current-time,
.vjs-no-flex .vjs-current-time {
  opacity: 1;
}
// 时间后分割线
/deep/ .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-divider {
  opacity: 1;
}
// 播放按钮
.yesPlayButton {
  /deep/ .vjs-custom-skin > .video-js .vjs-big-play-button {
    opacity: 1;
  }
}
.noPlayButton {
  /deep/ .vjs-custom-skin > .video-js .vjs-big-play-button {
    opacity: 0;
  }
}
</style>

主播端:主播端我们是使用阿里云进行直播的(h5端)

注意:需要现在阿里云购买推流服务器等进行一系列配置,本文章是直接讲述前端方面,已经拿到了后台给的推流地址之后的前端步骤

WebRTS推流SDK如何快速接入_视频直播(LIVE)-阿里云帮助中心

我这里有用html写一个demo,需要看的可以私聊我,给源码

在vue项目的用法就是

1.下载aliyun-rts-pusher包

npm install aliyun-rts-pusher --save

2.组件中使用

<template>
  <div v-if="$route.query.play">
    <div id="videoContainer"></div>
    <!-- 悬浮组件 -->
    <drag-icon ref="dragIconCom" :gapWidthPx="13" :coefficientHeight="0.66">
      <div class="activityDrag" slot="icon" @click.stop="play()">
        <img src="../../assets/images/icon/fz.png" alt="" />
      </div>
    </drag-icon>
    <!-- 关闭直播 -->
    <div class="closeVedio">
      <span @click="releash" style="margin-right:10px">刷新直播</span>
      <span @click="closeVedio">结束直播</span>
    </div>
  </div>
</template>
<script>
import { getpushstreamUrl } from '@/api/index.js';
import dragIcon from '@/components/dragIcon.vue';
import { AliRTSPusher } from 'aliyun-rts-pusher';
const pushClient = AliRTSPusher.createClient();
// 监听错误事件
pushClient.on('error', (err) => {
  console.log(err.errorCode);
});

export default {
  components: {
    dragIcon
  },
  data() {
    return {
      options: [],
      head: false,
      pushUrl:''
    };
  },
  created(){
    this.mesPushUrl()
  },  
  async mounted() {
    const videoEl = pushClient.setRenderView('videoContainer');
    // 获取摄像头麦克风列表
    const deviceManager = await pushClient.getDeviceManager();
    // 获取摄像头列表
    const cameraList = await deviceManager.getCameraList();
    // 获取麦克风列表
    const micList = await deviceManager.getMicList();
    this.options = cameraList;
    console.log(cameraList[cameraList.length - 1].deviceId, '99');
    // // 打开摄像头
    await pushClient.startCamera(cameraList[cameraList.length - 1].deviceId);
    // // 打开麦克风
    await pushClient.startMicrophone(
      cameraList[cameraList.length - 1].deviceId
    );
    pushClient.startPush(
     this.pushUrl
    );
   
  },
  methods: {
    async getList() {
      const pushClient = AliRTSPusher.createClient();
      // 获取摄像头麦克风列表
      const deviceManager = await pushClient.getDeviceManager();
      // 获取摄像头列表
      const cameraList = await deviceManager.getCameraList();
      // 获取麦克风列表
      const micList = await deviceManager.getMicList();
      this.options = cameraList;
      console.log(cameraList, 'cameraList');
    },
    // 获取推流地址
    mesPushUrl(){
      getpushstreamUrl().then(res=>{
        this.pushUrl = res.data.pushUrl
      }).catch(err=>{
        console.log(err);
      })
    },  
    async play() {
      this.head = !this.head;
      console.log(this.head, 'this.head');
      console.log(
        this.options[this.options.length - 1].deviceId,
        ' this.options[this.options.length - 1].deviceId'
      );
      if (!this.head) {
        // 后置
        await pushClient.startCamera(
          this.options[this.options.length - 1].deviceId
        );
        // 打开麦克风
        await pushClient.startMicrophone(
          this.options[this.options.length - 1].deviceId
        );
        pushClient.startPush(
          this.pushUrl
        );
      } else {
        // 前置
        await pushClient.startCamera(this.options[0].deviceId);
        // 打开麦克风
        await pushClient.startMicrophone(this.options[0].deviceId);
        pushClient.startPush(
          this.pushUrl
        );
      }
    },
    // 关闭直播
    closeVedio() {
      // 停止推流
      pushClient.stopPush();
      // 关闭摄像头
      pushClient.stopCamera();
      // 关闭麦克风
      pushClient.stopMicrophone();
      pushClient.dispose();
      pushClient.stopScreenCapture();
      this.$router.push({
        path: '/live',
        query: { id: this.$route.query.id , type: this.$route.query.type }
      });
    },
    releash() {
      window.location.reload();
    }
  }
};
</script>

<style lang="scss" scoped>
.closeVedio {
  width: 100%;
  text-align: center;
  span {
    width: 100px;
    text-align: center;
    display: inline-block;
    line-height: 36px;
    background: #FF0000;
    width: 120px;
    height: 36px;
    border-radius: 20px;
    opacity: 1;
    color: white;
  }
}
</style>

dragIcon:图标悬浮组件,我这里是一个翻转摄像头icon,用户反转摄像头

 

附件:dragIcon组件代码

<template>
    <div class="ys-float-btn" :style="{
          width: itemWidth + 'px',
          height: itemHeight + 'px',
          left: left + 'px',
          top: top + 'px',
        }" ref="dragIcon" @click="onBtnClick" @touchstart.stop="handleTouchStart" @touchmove.prevent.stop="handleTouchMove($event)" @touchend.stop="handleTouchEnd">
      <slot class="aaa" name="icon"></slot>
    </div>
  </template>
    
    <script>
  export default {
    name: 'DragIcon',
    props: {
      // 距离屏幕边距的距离 传入px,进行处理
      gapWidthPx: {
        type: Number,
        default: 0
      },
      // 页面初始化到顶部的百分比,小数形式展示
      coefficientHeight: {
        type: Number,
        default: 0.65
      }
    },
    computed: {
      gapWidth() {
        let num = this.gapWidthPx;
        if (
          document.getElementsByTagName('html')[0] &&
          document.getElementsByTagName('html')[0].style.fontSize.split('px')[0]
        ) {
          num =
            (num / 100) *
            document
              .getElementsByTagName('html')[0]
              .style.fontSize.split('px')[0];
        }
        return num;
      },
      itemWidth() {
        return this.itemWidth1 || 40;
      },
      itemHeight() {
        return this.itemHeight1 || 40;
      }
    },
    watch: {
      // 初始化计算组件宽度,获取组件距离左边的宽度
      itemWidth: {
        handler(newV, oldV) {
          if (newV > 0 && newV != oldV) {
            this.clientWidth = document.documentElement.clientWidth;
            this.clientHeight = document.documentElement.clientHeight;
            this.left = this.clientWidth - this.itemWidth - this.gapWidth;
          }
        }
      }
    },
    updated() {
      this.$nextTick(() => {
        if (
          this.$refs.dragIcon &&
          this.$refs.dragIcon.children[0] &&
          this.$refs.dragIcon.children[0].clientHeight
        ) {
          this.itemHeight1 = this.$refs.dragIcon.children[0].clientHeight;
          this.itemWidth1 = this.$refs.dragIcon.children[0].clientWidth;
        }
      });
    },
    created() {
      this.clientWidth = document.documentElement.clientWidth;
      this.clientHeight = document.documentElement.clientHeight;
      this.left = this.clientWidth - this.itemWidth - this.gapWidth;
      this.top = this.clientHeight * this.coefficientHeight;
    },
    methods: {
      onBtnClick() {
        this.$emit('onBtnClick');
      },
      handleTouchStart() {
        this.startToMove = true;
        this.$refs.dragIcon.style.transition = 'none';
      },
      handleTouchMove(e) {
        if (this.startToMove && e.targetTouches.length === 1) {
          const clientX = e.targetTouches[0].clientX; //手指相对视口的x
          const clientY = e.targetTouches[0].clientY; //手指相对视口的y
          this.left = clientX - this.itemWidth / 2;
          this.top = clientY - this.itemHeight / 2;
        }
      },
      handleTouchEnd() {
        this.$refs.dragIcon.style.transition = 'all 0.3s';
        if (this.left > this.clientWidth / 2) {
          this.left = this.clientWidth - this.itemWidth - this.gapWidth;
        } else {
          this.left = this.gapWidth;
        }
        if (this.top <= 36) {
          this.top = 36 + this.gapWidth;
        } else {
          let bottom = this.clientHeight - 50 - this.itemHeight - this.gapWidth;
          if (this.top >= bottom) {
            this.top = bottom;
          }
        }
      }
    },
    data() {
      return {
        currentTop: 0,
        clientWidth: 0,
        clientHeight: 0,
        left: 0,
        top: 0,
        itemWidth1: 40,
        itemHeight1: 40
      };
    }
  };
  </script>
    
    <style lang="scss" scoped>
  .ys-float-btn {
    z-index: 20;
    transition: all 0.3s;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: fixed;
    bottom: 20vw;
  }
  </style>

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值