前言

最近公司因为业务需要,研究做直播功能,查阅了大量网友写的demo,最终还是被很多问题绊倒了,由于现在chrom已经屏蔽flash了,咱们要换种解决办法,但咱们是面向csdn等技术交流平台编程,所以问题不大。


总共分为两部分,一篇是针对uniapp+vue,另一篇是ngxin直播服务器的搭建,传送门在结尾。

UniApp(推流)/ OBS(推流)+ Vue(拉流)/ VLC(拉流)

目前有很多关于直播的教程哈,但是都比较单一,不够全面,所以本篇文章经过自己动手实践和整合,搞出来了这一套教程,没有做过直播的同学或者做过出现问题的同学可以详细的看下这篇文章,或许对你有所帮助。

一、先看效果
  • vue拉流
    vue拉流
  • uniapp推流

uniapp推流

二、uniapp篇

这里用的是官方给的基础代码,具体参数请参考官方:
https://uniapp.dcloud.io/component/live-player


  • 创建uniapp项目
  • 在pages–>index–>新建live.nvue页面

重点:后缀是nvue,不了解可以自行百度

这里我们用的软件是hbuilderx,连接手机后直接运动uniapp项目,可以方便测试摄像头等功能,如果做完这个项目要发布的话,必须在发布前,修改manifest.json文件:


在hbuilderx中,双击你项目中manifest.json文件,左侧菜单找到App模块配置,勾选 LivePusher(直播推流) 这一选项
在这里插入图片描述

  • 无需废话,这是app直播主要页面,上代码😁:
<template>
	<view>
		<view class="account-form">
			<view class="uni-form-item">
				<view class="uni-input-wrapper">
					<view class="uni-label uni-label-must">直播间标题</view>
					<input class="uni-input" placeholder="请输入直播间标题" :value="form.liveroomTitle" @input="changeInput($event,'liveroomTitle')"/>
				</view>
			</view>
		</view>
		<button class="cu-btn bg-red margin-tb-sm lg" v-if="!startState" @click="saveForm">开始直播</button>
		<button class="cu-btn bg-red margin-tb-sm lg" v-if="startState" @click="downcast">关闭直播</button>
		<button class="cu-btn bg-red margin-tb-sm lg" @click="switchCamera">切换摄像头</button>
		<live-pusher
			id="livePusher"
			ref="livePusher"
			:style="pusherCalss"
			class="livePusher"
			url="rtmp://192.168.1.171:17002/live/huahua"
			mode="FHD"
			:muted="false"
			:enable-camera="true"
			:auto-focus="true"
			:beauty="1"
			whiteness="2"
			aspect="9:16"
			@statechange="statechange"
			@netstatus="netstatus"
			@error="error"
		></live-pusher>
		// 官方给的一些按钮,具体调用在下面
		//<button class="btn" @click="start">开始推流</button>
		//<button class="btn" @click="pause">暂停推流</button>
		//<button class="btn" @click="resume">resume</button>
		//<button class="btn" @click="stop">停止推流</button>
		//<button class="btn" @click="snapshot">快照</button>
		//<button class="btn" @click="startPreview">开启摄像头预览</button>
		//<button class="btn" @click="stopPreview">关闭摄像头预览</button>
		//<button class="btn" @click="switchCamera">切换摄像头</button> -->
	</view>
</template>

<script>
export default {
	data() {
		return {
			// 视频宽高
			pusherCalss: {
				width: '200px',
				height: '300px'
			},
			// form本人测试请求的参数,不发请求可以不加
			form: {
				liveroomTitle: null, // 标题
				equipmentType: 1 // 设备类型(1.手机 2.电脑)
			},
			// 控制开启,关闭直播按钮的显示
			startState: false,// 直播状态(false 关闭)
		};
	},
	onReady() {
		// 注意:需要在onReady中 或 onLoad 延时
		this.context = uni.createLivePusherContext('livePusher', this);
	},
	onLoad() {
		// 获取可视区域高度,减去固定高度
		this.pusherCalss.width = wx.getSystemInfoSync().windowWidth;
		this.pusherCalss.height = wx.getSystemInfoSync().windowHeight;
	},
	mounted() {
		// 一进页面,先调用摄像头,保证摄像头是打开状态,不加也可以,手动开启,参考上面官方给出的那些按钮
		this.startPreview();
	},
	methods: {
		// 下面的这些方法,可以参考官网,具体查看每个方法的意义
		statechange(e) {
			console.log('statechange:' + JSON.stringify(e));
		},
		netstatus(e) {
			console.log('netstatus:' + JSON.stringify(e));
		},
		error(e) {
			console.log('error:' + JSON.stringify(e));
		},
		start: function() {
			this.context.start({
				success: a => {
					this.startState = true;
					console.log('livePusher.start:' + JSON.stringify(a));
				}
			});
		},
		close: function() {
			this.context.close({
				success: a => {
					console.log('livePusher.close:' + JSON.stringify(a));
				}
			});
		},
		snapshot: function() {
			this.context.snapshot({
				success: e => {
					console.log(JSON.stringify(e));
				}
			});
		},
		resume: function() {
			this.context.resume({
				success: a => {
					console.log('livePusher.resume:' + JSON.stringify(a));
				}
			});
		},
		pause: function() {
			this.context.pause({
				success: a => {
					console.log('livePusher.pause:' + JSON.stringify(a));
				}
			});
		},
		stop: function() {
			this.context.stop({
				success: a => {
					this.startState = false;
					console.log(JSON.stringify(a));
				}
			});
		},
		switchCamera: function() {
			this.context.switchCamera({
				success: a => {
					console.log('livePusher.switchCamera:' + JSON.stringify(a));
				}
			});
		},
		startPreview: function() {
			this.context.startPreview({
				success: a => {
					console.log('livePusher.startPreview:' + JSON.stringify(a));
				}
			});
		},
		stopPreview: function() {
			this.context.stopPreview({
				success: a => {
					console.log('livePusher.stopPreview:' + JSON.stringify(a));
				}
			});
		},
		// 输入框改变(没有接口可以忽略,这里就是发送请求时起一个直播间的名字)
		changeInput: function(e, name) {
			this.form[name] = e.detail.value;
		},
		// 开始直播
		saveForm() {
			if(this.form.liveroomTitle == null || this.form.liveroomTitle == ""){
				return;
			}
			// 这里是我自己的测试请求,因为需要和vue数据同步,做一个开播关播的数据交互,大家如果用不到的话可以直接调用 this.start();
			getApp().$util.requestUrl(`/test/live/createLiveRecord?anchorId=2&liveroomTitle=${this.form.liveroomTitle}&equipmentType=${this.form.equipmentType}`).then(res => {
				console.log(res)
				if (res.status == 200) {
					this.start();
				}
			});
		},
		// 关闭直播
		downcast(){
			// 同样,用不到接口只简单测试,调用this.stop();
			// vue的工具类等调用是this.$util;
			// uniapp使用getApp().$util
			getApp().$util.requestUrl(`/test/live/downcast?anchorId=2`).then(res => {
				console.log(res)
				if (res.status == 200) {
					this.stop();
				}
			});
		}
	}
};
</script>
三、vue篇
  • 需要安装两个组件😄:

npm install vue-video-player -S
npm install videojs-flash --save

  • 然后在你需要直播的页面,引用组件:

import ‘video.js/dist/video-js.css’;
import ‘vue-video-player/src/custom-theme.css’;
import { videoPlayer } from ‘vue-video-player’;
import ‘videojs-contrib-hls’;
import ‘videojs-flash’;

  • 别急,上代码🤭,这是vue直播的核心页面:
<template>
  <a-card class="card">
    <div style="overflow: hidden; display: flex;">
      <!-- 直播间信息区 -->
      <div class="live">
        <div class="live-avatar">
          <a-avatar shape="square" :size="64" icon="user" />
          <p style="margin-left: 10px;"><b>二大爷的三蹦子</b></p>
        </div>
        <!-- 视频区 -->
        // ---------这里是我们本次直播教程的主要关注区域 START-----------
        <div class="video-class">
          <videoPlayer
            class="vjs-custom-skin videoPlayer"
            :options="playerOptions"
            @play="onPlayerPlay($event)"
            @pause="onPlayerPause($event)"
            @ended="onPlayerEnded($event)"
            @loadeddata="onPlayerLoadeddata($event)"
            @waiting="onPlayerWaiting($event)"
            @playing="onPlayerPlaying($event)"
            @timeupdate="onPlayerTimeupdate($event)"
            @canplay="onPlayerCanplay($event)"
            @canplaythrough="onPlayerCanplaythrough($event)"
            @ready="playerReadied"
            @statechanged="playerStateChanged($event)"
          ></videoPlayer>
        </div>
       // -----------------主要关注区域 END------------- 
      </div>
      <!-- 弹幕区 -->
      <div class="chat"><p>这里是弹幕区</p></div>
    </div>
  </a-card>
</template>

<script>
import 'video.js/dist/video-js.css';
import 'vue-video-player/src/custom-theme.css';
import { videoPlayer } from 'vue-video-player';
import 'videojs-contrib-hls';
import 'videojs-flash';
export default {
  name: '',
  components: { videoPlayer },
  data() {
    return {
      // ---------这里是我们本次直播教程的主要关注区域 START-----------
      playerOptions: {
        // height: '300',//播放器高度
        language: 'zh-CN',
        muted: false, //默认情况下将会消除任何音频。
        aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
        fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
        sources: [
          {
            type: ['application/x-mpegURL', 'rtmp/flv','video/x-flv','rtmp/mp4'], //视频流协议,如果是hls,需要后端开启跨域
            src: 'http://192.168.1.171:17001/live/huahua/index.m3u8' ,//拉流地址
          }
        ],
        autoplay: true, //自动播放
        controls: true, //编辑器控件
        notSupportedMessage: "此视频暂无法播放,请稍后再试", // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
        controlBar: {
          timeDivider: true,
          durationDisplay: true,
          remainingTimeDisplay: false,
          fullscreenToggle: true // 全屏按钮
        },
        flash: { hls: { withCredentials: false }},
        html5: { hls: { withCredentials: false }},
      }
      // -----------------主要关注区域 END-------------
    };
  },
  mounted() {},
  computed: {},
  methods: {
  	// 以下的这些方法,都是针对视频组件的监听,可以移步百度哈
    // 播放回调
    onPlayerPlay(player) {
      console.log('player play!', player)
    },
    // 暂停回调
    onPlayerPause(player) {
      console.log('player pause!', player)
    },
    // 视频播完回调
    onPlayerEnded(player) {
      console.log('player ended!', player)
    },
    // DOM元素上的readyState更改导致播放停止
    onPlayerLoadeddata(player) {
      console.log('player Loadeddata!', player)
    },
    // 已开始播放回调
    onPlayerWaiting(player) {
      console.log('player Waiting!', player)
    },
    // 当播放器在当前播放位置下载数据时触发
    onPlayerPlaying(player) {
      console.log('player Playing!', player)
    },
    // 当前播放位置发生变化时触发
    onPlayerTimeupdate(player) {
      // console.log('player Timeupdate!', player.currentTime())
    },
    // 媒体的readyState为HAVE_FUTURE_DATA或更高
    onPlayerCanplay(player) {
      console.log('player Canplay!', player)
    },
    // 媒体的readyState为HAVE_ENOUGH_DATA或更高。这意味着可以在不缓冲的情况下播放整个媒体文件。
    onPlayerCanplaythrough(player) {
      console.log('player Canplaythrough!', player)
    },
    // 播放状态改变回调
    playerStateChanged(playerCurrentState) {
      // console.log('player current update state', playerCurrentState)
    },
    //将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数
    playerReadied(player) {
      var hls = player.tech({ IWillNotUseThisInPlugins: true }).hls
      player.tech_.hls.xhr.beforeRequest = function(options) {
        return options
      }
    },
  }
};
</script>

<style scoped="scoped" less>
// 这是页面的基本几个样式,可以先不要,自己写,无非就是画几个框框,
// 确定一下布局,我们这里主要关注的是直播视频区,不写样式也无所谓,之间渲染,那就看上面的<!-- 视频区 -->部分
.select_btn {
  float: right;
  position: absolute;
  right: 10%;
  top: 27px;
}
.live {
  width: 70%;
  height: 800px;
  border: 1px solid #bfbfbf;
  padding: 10px;
}
/deep/ .live-avatar {
  padding: 5px;
  overflow: hidden;
  display: flex;
  border-bottom: 1px solid #bfbfbf;
}
.live /deep/ .video-class {
  height: 90%;
  border: 1px solid #bfbfbf;
}
.chat {
  width: 30%;
  height: 800px;
  border: 1px solid #bfbfbf;
  text-align: center;
  line-height: 50;
}
</style>

  • 至此,你可以在data里playerOptions—>sources中的src替换下面这个地址,测试一下组件是否存在问题:
  • http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8

解释一下为什么文章中拉流用到的地址格式是:http://192.168.1.171:17001/live/huahua/index.m3u8,而网上部分文章中格式是:rtmp://58.200.131.2:1935/livetv/cctv1
我们服务器中将直播流录制成 ts 视频片段, 然后再把直播流完整录制成flv视频文件,最后客户端通过http去获取切好的视频片段,具体详见末尾的Nginx直播流媒体搭建连接!
在这里插入图片描述
我们要做的是:
在这里插入图片描述
图片来源:https://blog.csdn.net/qq_37306786/article/details/103956190

四、说明
  • 对于以上页面中的 推流/拉流 地址没有特殊说明,不过不要着急,现在基础的页面已经完成了,接下来就是配置,回到我们标题里说的:uniapp(推流)+vue(拉流),那么实现直播还有nginx没有配置,下面请移步 [Nginx使用rtmp搭建流媒体服务器,实现hls直播]会对地址进行说明
  • Nginx使用rtmp搭建流媒体服务器,实现hls直播
五、如果这两篇文章你都看完了,相信你一定能搭建出一个基本的直播平台!!!
  • 有问题请在下方留言,尽量解答!
  • 下一篇文章

《Vue实现直播弹幕功能》

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐