<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>