|
@@ -0,0 +1,352 @@
|
|
|
+<template>
|
|
|
+ <view class="container" data-type="template" data-is="1v1" data-attr="pusher, streamList, debug">
|
|
|
+ <view v-for="item in streams.slice(0,1)" :key="item.streamID" class="player-container" :style="'display:'+playerStyle">
|
|
|
+ <template v-if="item.src && (item.hasVideo || item.hasAudio)">
|
|
|
+ <live-player
|
|
|
+ class="player"
|
|
|
+ :data-userid="item.userID"
|
|
|
+ :data-streamid="item.streamID"
|
|
|
+ :data-streamtype="item.streamType"
|
|
|
+ :src="item.src"
|
|
|
+ :mode="item.mode"
|
|
|
+ :autoplay="item.autoplay"
|
|
|
+ :mute-audio="item.muteAudio"
|
|
|
+ :mute-video="item.muteVideo"
|
|
|
+ :orientation="item.orientation"
|
|
|
+ :object-fit="item.objectFit"
|
|
|
+ :background-mute="item.enableBackgroundMute"
|
|
|
+ :min-cache="item.minCache"
|
|
|
+ :max-cache="item.maxCache"
|
|
|
+ :sound-mode="item.soundMode"
|
|
|
+ :enable-recv-message="item.enableRecvMessage"
|
|
|
+ :auto-pause-if-navigate="item.autoPauseIfNavigate"
|
|
|
+ :auto-pause-if-open-native="item.autoPauseIfOpenNative"
|
|
|
+ :debug="debug"
|
|
|
+ @statechange="playerStateChangeFun"
|
|
|
+ @fullscreenchange="playerFullscreenChangeFun"
|
|
|
+ @netstatus="playerNetStatusFun"
|
|
|
+ @audiovolumenotify="playerAudioVolumeNotifyFun"
|
|
|
+ :idAttr="item.streamID" />
|
|
|
+ </template>
|
|
|
+ </view>
|
|
|
+ <view v-if="pusher.url" class="pusher-container" :style="'display:'+pusherStyle">
|
|
|
+ <live-pusher
|
|
|
+ class="pusher"
|
|
|
+ :url="pusher.url"
|
|
|
+ :mode="pusher.mode"
|
|
|
+ :autopush="pusher.autopush"
|
|
|
+ :enable-camera="pusher.enableCamera"
|
|
|
+ :enable-mic="pusher.enableMic"
|
|
|
+ :enable-agc="pusher.enableAgc"
|
|
|
+ :enable-ans="pusher.enableAns"
|
|
|
+ :enable-ear-monitor="pusher.enableEarMonitor"
|
|
|
+ :auto-focus="pusher.enableAutoFocus"
|
|
|
+ :zoom="pusher.enableZoom"
|
|
|
+ :min-bitrate="pusher.minBitrate"
|
|
|
+ :max-bitrate="pusher.maxBitrate"
|
|
|
+ :video-width="pusher.videoWidth"
|
|
|
+ :video-height="pusher.videoHeight"
|
|
|
+ :beauty="pusher.beautyLevel"
|
|
|
+ :whiteness="pusher.whitenessLevel"
|
|
|
+ :orientation="pusher.videoOrientation"
|
|
|
+ :aspect="pusher.videoAspect"
|
|
|
+ :device-position="pusher.frontCamera"
|
|
|
+ :remote-mirror="pusher.enableRemoteMirror"
|
|
|
+ :local-mirror="pusher.localMirror"
|
|
|
+ :background-mute="pusher.enableBackgroundMute"
|
|
|
+ :audio-quality="pusher.audioQuality"
|
|
|
+ :audio-volume-type="pusher.audioVolumeType"
|
|
|
+ :audio-reverb-type="pusher.audioReverbType"
|
|
|
+ :waiting-image="pusher.waitingImage"
|
|
|
+ :debug="debug"
|
|
|
+ @statechange="pusherStateChangeHandlerFun"
|
|
|
+ @netstatus="pusherNetStatusHandlerFun"
|
|
|
+ @error="pusherErrorHandlerFun"
|
|
|
+ @bgmstart="pusherBGMStartHandlerFun"
|
|
|
+ @bgmprogress="pusherBGMProgressHandlerFun"
|
|
|
+ @bgmcomplete="pusherBGMCompleteHandlerFun" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+const LOGTAG = '--JHLIVE--:';
|
|
|
+const pusherStateCodeDic = {
|
|
|
+ 1001: '已经连接推流服务器',
|
|
|
+ 1002: '已经与服务器握手完毕,开始推流',
|
|
|
+ 1003: '打开摄像头成功',
|
|
|
+ 1004: '录屏启动成功',
|
|
|
+ 1005: '推流动态调整分辨率',
|
|
|
+ 1006: '推流动态调整码率',
|
|
|
+ 1007: '首帧画面采集完成',
|
|
|
+ 1008: '编码器启动',
|
|
|
+ 1018: '进房成功',
|
|
|
+ 1019: '退出房间',
|
|
|
+ 2003: '渲染首帧视频',
|
|
|
+ 1020: '远端用户全量列表更新',
|
|
|
+ 1031: '远端用户进房通知',
|
|
|
+ 1032: '远端用户退房通知',
|
|
|
+ 1033: '用户视频状态变化,新增stream或者更新stream',
|
|
|
+ 1034: '远端用户音频状态位变化通知',
|
|
|
+ '-1301': '打开摄像头失败',
|
|
|
+ '-1302': '打开麦克风失败',
|
|
|
+ '-1303': '视频编码失败',
|
|
|
+ '-1304': '音频编码失败',
|
|
|
+ '-1307': '推流连接断开',
|
|
|
+ '-100018': '进房失败',
|
|
|
+ '5000': '小程序被挂起',
|
|
|
+ '1021': '网络类型发生变化,需要重新进房',
|
|
|
+ '2007': '本地视频播放loading',
|
|
|
+ '2004': '本地视频播放开始',
|
|
|
+}
|
|
|
+
|
|
|
+const defaultStreamParams = {
|
|
|
+ streamType: '',
|
|
|
+ src: '',
|
|
|
+ mode: 'RTC',
|
|
|
+ autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
|
|
|
+ muteAudio: false, // 默认不拉取音频,需要手动订阅
|
|
|
+ muteVideo: false, // 默认不拉取视频,需要手动订阅
|
|
|
+ orientation: 'vertical', // 画面方向 vertical horizontal
|
|
|
+ objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
|
|
|
+ enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
|
|
|
+ minCache: 1, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
|
|
|
+ maxCache: 2, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
|
|
|
+ soundMode: 'speaker', // 声音输出方式 ear speaker
|
|
|
+ enableRecvMessage: 'false', // 是否接收SEI消息
|
|
|
+ autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
|
|
|
+ autoPauseIfOpenNative: true // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'jhlive-wechat',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ pusher: {
|
|
|
+ url: '',
|
|
|
+ mode: 'RTC', // RTC:实时通话(trtc sdk) live:直播模式(liteav sdk)
|
|
|
+ autopush: false, // 自动推送
|
|
|
+ enableCamera: false, // 是否开启摄像头
|
|
|
+ enableMic: false, // 是否开启麦克风
|
|
|
+ enableAgc: false, // 是否开启音频自动增益
|
|
|
+ enableAns: false, // 是否开启音频噪声抑制
|
|
|
+ enableEarMonitor: false, // 是否开启耳返(目前只在iOS平台有效)
|
|
|
+ enableAutoFocus: true, // 是否自动对焦
|
|
|
+ enableZoom: false, // 是否支持调整焦距
|
|
|
+ minBitrate: 200, // 最小码率
|
|
|
+ maxBitrate: 1000, // 最大码率
|
|
|
+ videoWidth: 360, // 视频宽(若设置了视频宽高就会忽略aspect)
|
|
|
+ videoHeight: 640, // 视频高(若设置了视频宽高就会忽略aspect)
|
|
|
+ beautyLevel: 0, // 美颜,取值范围 0-9 ,0 表示关闭
|
|
|
+ whitenessLevel: 0, // 美白,取值范围 0-9 ,0 表示关闭
|
|
|
+ videoOrientation: 'vertical', // vertical horizontal
|
|
|
+ videoAspect: '9:16', // 宽高比,可选值有 3:4,9:16
|
|
|
+ frontCamera: 'front', // 前置或后置摄像头,可选值:front,back
|
|
|
+ enableRemoteMirror: false, // 设置推流画面是否镜像,产生的效果会表现在 live-player
|
|
|
+ localMirror: 'auto', // auto:前置摄像头镜像,后置摄像头不镜像(系统相机的表现)enable:前置摄像头和后置摄像头都镜像 disable: 前置摄像头和后置摄像头都不镜像
|
|
|
+ enableBackgroundMute: false, // 进入后台时是否静音
|
|
|
+ audioQuality: 'high', // 高音质(48KHz)或低音质(16KHz),可选值:high,low
|
|
|
+ audioVolumeType: 'voicecall', // 声音类型 可选值: media: 媒体音量,voicecall: 通话音量
|
|
|
+ audioReverbType: 0, // 音频混响类型 0: 关闭 1: KTV 2: 小房间 3:大会堂 4:低沉 5:洪亮 6:金属声 7:磁性
|
|
|
+ waitingImage: '', // 当微信切到后台时的垫片图片 trtc暂不支持
|
|
|
+ waitingImageHash: ''
|
|
|
+ },
|
|
|
+ users: [],
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ props: {
|
|
|
+ sdkAppID: Number,
|
|
|
+ userSig: String,
|
|
|
+ userid: String,
|
|
|
+ roomid: String,
|
|
|
+ isAuthor: Boolean,
|
|
|
+ linkMic: Boolean
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ streams() {
|
|
|
+ console.log(LOGTAG+'users', this.users);
|
|
|
+ let streams = [];
|
|
|
+ this.users.forEach(v=>{
|
|
|
+ let userStreams = Object.entries(v.streams);
|
|
|
+ streams = streams.concat(userStreams.map(([kk,vv])=>vv).filter(vv=>vv.hasVideo));
|
|
|
+ });
|
|
|
+ console.log(LOGTAG+'streams', streams);
|
|
|
+ return streams;
|
|
|
+ },
|
|
|
+ playerStyle() {
|
|
|
+ return (!this.isAuthor)?'block':'none';
|
|
|
+ },
|
|
|
+ pusherStyle() {
|
|
|
+ return this.isAuthor?'block':'none';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ enterRoom() {
|
|
|
+ this.pusher.url = `room://cloud.tencent.com/rtc?sdkappid=${this.sdkAppID}&roomid=${this.roomid}&userid=${this.userid}&usersig=${this.userSig}&appscene=live&encsmall=1&cloudenv=PRO`;
|
|
|
+ this.pusher.autopush = this.isAuthor||this.linkMic;
|
|
|
+ this.pusher.enableCamera = this.isAuthor;
|
|
|
+ this.pusher.enableMic = this.isAuthor||this.linkMic;
|
|
|
+ this.$nextTick(()=>{
|
|
|
+ this.pusherContext = wx.createLivePusherContext();
|
|
|
+ this.pusherContext.start();
|
|
|
+ })
|
|
|
+ console.log(LOGTAG+'pusher',this.pusher);
|
|
|
+ },
|
|
|
+ exitRoom: function() {
|
|
|
+ if (this.pusherContext) {
|
|
|
+ this.pusherContext.stop();
|
|
|
+ this.pusherContext = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ switchCarema() {
|
|
|
+ this.pusher.frontCamera = this.pusher.frontCamera=='front'?'back':'front';
|
|
|
+ console.log(LOGTAG+'pusher',this.pusher);
|
|
|
+ },
|
|
|
+ setBeauty(blv, wlv) {
|
|
|
+ this.pusher.beautyLevel = blv;
|
|
|
+ this.pusher.whitenessLevel = wlv;
|
|
|
+ console.log(LOGTAG+'pusher',this.pusher);
|
|
|
+ },
|
|
|
+
|
|
|
+ /** --- pusher event handler --- **/
|
|
|
+ pusherStateChangeHandlerFun: function(event) {
|
|
|
+ const code = event.detail.code;
|
|
|
+ const message = event.detail.message;
|
|
|
+ console.log(LOGTAG+'pusherState',code, pusherStateCodeDic[code]);
|
|
|
+
|
|
|
+ // 远端用户进房通知
|
|
|
+ if(code == 1031) {
|
|
|
+ const data = JSON.parse(event.detail.message)||{};
|
|
|
+ (data.userlist||[]).forEach( v=> {
|
|
|
+ if(!this.users.find(vv => vv.userid == v.userid)) {
|
|
|
+ this.users.push({
|
|
|
+ userid: v.userid,
|
|
|
+ streams: {},
|
|
|
+ });
|
|
|
+ this.$emit('liveEvent', {detail:{eventName: 'onRemoteUserEnterRoom', eventData: {userId: v.userid}}});
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 远端用户退房通知
|
|
|
+ if(code == 1032) {
|
|
|
+ const data = JSON.parse(event.detail.message)||{};
|
|
|
+ (data.userlist||[]).forEach( v=> {
|
|
|
+ let user = this.users.find(v => v.userid == data.userid);
|
|
|
+ if(!user) return;
|
|
|
+ user.streams.forEach(vv=>{
|
|
|
+ if(vv.playerContext) {
|
|
|
+ vv.playerContext.stop;
|
|
|
+ vv.playerContext = null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ user.streams = [];
|
|
|
+ this.$emit('liveEvent', {detail:{eventName: 'onRemoteUserLeaveRoom', eventData: {userId: v.userid}}});
|
|
|
+ })
|
|
|
+ this.users = this.users.filter(v => (data.userlist||[]).find(vv=>vv.userid!=v.userid));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用户视频状态变化,新增stream或者更新stream 状态
|
|
|
+ if(code == 1033) {
|
|
|
+ const data = JSON.parse(event.detail.message)||{};
|
|
|
+ (data.userlist||[]).forEach( v=> {
|
|
|
+ let user = this.users.find(vv => v.userid == v.userid);
|
|
|
+ if(!user) return;
|
|
|
+ let stream = user.streams[v.streamtype] = user.streams[v.streamtype] || {...defaultStreamParams};
|
|
|
+ stream.streamtype = v.streamtype;
|
|
|
+ stream.userID = v.userid;
|
|
|
+ stream.streamID = v.userid + '_' + stream.streamType;
|
|
|
+ stream.hasVideo = v.hasvideo;
|
|
|
+ stream[`has${stream.streamType}Video`] = stream.hasVideo;
|
|
|
+ stream.src = v.playurl;
|
|
|
+ if(stream.hasVideo) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
|
|
|
+ })
|
|
|
+ this.users = [...this.users];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 远端用户音频状态位变化通知
|
|
|
+ if(code == 1034) {
|
|
|
+ console.log('远端用户音频状态位变化通知', data);
|
|
|
+ const data = JSON.parse(event.detail.message)||{};
|
|
|
+ (data.userlist||[]).forEach( v=> {
|
|
|
+ let user = this.users.find(vv => v.userid == vv.userid);
|
|
|
+ if(!user) return;
|
|
|
+ v.streamtype = 'main';
|
|
|
+ let stream = user.streams[v.streamtype] = user.streams[v.streamtype] || {...defaultStreamParams}
|
|
|
+ stream.streamtype = v.streamtype;
|
|
|
+ stream.userID = v.userid;
|
|
|
+ stream.streamID = v.userid + '_' + stream.streamType;
|
|
|
+ stream.hasAudio = v.hasaudio;
|
|
|
+ stream[`has${stream.streamType}Audio`] = stream.hasAudio;
|
|
|
+ stream.src = v.playurl;
|
|
|
+ if(stream.hasAudio) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
|
|
|
+ })
|
|
|
+ this.users = [...this.users];
|
|
|
+ }
|
|
|
+ },
|
|
|
+ pusherNetStatusHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+ pusherErrorHandlerFun: function(event) {
|
|
|
+ this.$emit('liveEvent', {detail:{eventName: 'onError', eventData: {msg: '推送错误'}}});
|
|
|
+ console.error(LOGTAG+'pusher error: ',event.detail);
|
|
|
+ },
|
|
|
+ pusherBGMStartHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+ pusherBGMProgressHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+ pusherBGMCompleteHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ /** --- player event handler --- **/
|
|
|
+ playerStateChangeFun: function(event) {
|
|
|
+ console.log(LOGTAG+'playerStateChangeFun');
|
|
|
+ },
|
|
|
+ playerFullscreenChangeFun: function(event) {
|
|
|
+ console.log(LOGTAG+'playerFullscreenChangeFun');
|
|
|
+ },
|
|
|
+ playerNetStatusFun: function(event) {
|
|
|
+ console.log(LOGTAG+'playerNetStatusFun');
|
|
|
+ let dataset = event.currentTarget.dataset;
|
|
|
+ let info = event.detail.info;
|
|
|
+ let user = this.users.find(v => v.userid == dataset.userid);
|
|
|
+ if(!user) return;
|
|
|
+ let stream = user.streams[dataset.streamtype];
|
|
|
+ if(!stream) return;
|
|
|
+ stream.videoWidth = info.videoWidth;
|
|
|
+ stream.videoHeight = info.videoHeight;
|
|
|
+ },
|
|
|
+ playerAudioVolumeNotifyFun: function(event) {
|
|
|
+ console.log(LOGTAG+'playerAudioVolumeNotifyFun');
|
|
|
+ },
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.pusher-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.player-container{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.pusher {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.player {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|