|
@@ -0,0 +1,435 @@
|
|
|
+<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="RTC"
|
|
|
+ :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 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>
|
|
|
+export default {
|
|
|
+ name: 'jhlive-wechat',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ pusher: null,
|
|
|
+ users: [],
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ props: {
|
|
|
+ sdkAppID: Number,
|
|
|
+ userSig: String,
|
|
|
+ userid: String,
|
|
|
+ roomid: String,
|
|
|
+ isAuthor: Boolean,
|
|
|
+ linkMic: Boolean
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ streams() {
|
|
|
+ console.log('--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('--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',
|
|
|
+ mode: 'RTC', // RTC:实时通话(trtc sdk) live:直播模式(liteav sdk)
|
|
|
+ autopush: this.isAuthor||this.linkMic, // 自动推送
|
|
|
+ enableCamera: this.isAuthor, // 是否开启摄像头
|
|
|
+ enableMic: this.isAuthor||this.linkMic, // 是否开启麦克风
|
|
|
+ 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: ''
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('JHLIVE-PUSHER CONFIG', this.pusher);
|
|
|
+ setTimeout(()=>{
|
|
|
+ this.pusherContext = wx.createLivePusherContext();
|
|
|
+ this.pusherContext.start();
|
|
|
+ console.log('JHLIVE-PUSHER pusherContext', this.pusherContext);
|
|
|
+ },1000);
|
|
|
+ },
|
|
|
+ exitRoom: function() {
|
|
|
+ if (this.pusherContext) {
|
|
|
+ this.pusherContext.stop();
|
|
|
+ this.pusherContext = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** --- pusher event handler --- **/
|
|
|
+ pusherStateChangeHandlerFun: function(event) {
|
|
|
+ const code = event.detail.code;
|
|
|
+ const message = event.detail.message;
|
|
|
+
|
|
|
+ switch (code) {
|
|
|
+ case 0:
|
|
|
+ console.log(message, code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1001:
|
|
|
+ console.log('已经连接推流服务器', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1002:
|
|
|
+ console.log('已经与服务器握手完毕,开始推流', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1003:
|
|
|
+ console.log('打开摄像头成功', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1004:
|
|
|
+ console.log('录屏启动成功', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1005:
|
|
|
+ console.log('推流动态调整分辨率', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1006:
|
|
|
+ console.log('推流动态调整码率', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1007:
|
|
|
+ console.log('首帧画面采集完成', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1008:
|
|
|
+ console.log('编码器启动', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1018:
|
|
|
+ console.log('进房成功', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1019:
|
|
|
+ console.log('退出房间', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 2003:
|
|
|
+ console.log('渲染首帧视频', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1020: // 远端用户全量列表更新
|
|
|
+ break;
|
|
|
+ case 1031: {// 远端用户进房通知
|
|
|
+ const data = JSON.parse(event.detail.message)||{};
|
|
|
+ console.log('远端用户进房通知', data);
|
|
|
+ (data.userlist||[]).forEach( v=> {
|
|
|
+ if(!this.users.find(vv => vv.userid == v.userid)) {
|
|
|
+ this.users.push({
|
|
|
+ userid: v.userid,
|
|
|
+ streams: {},
|
|
|
+ });
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }break;
|
|
|
+ case 1032: {// 远端用户退房通知
|
|
|
+ console.log('远端用户退房通知', data);
|
|
|
+ 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.users = this.users.filter(v => (data.userlist||[]).find(vv=>vv.userid!=v.userid));
|
|
|
+ }break;
|
|
|
+ case 1033: {// 用户视频状态变化,新增stream或者更新stream 状态
|
|
|
+ console.log('用户视频状态变化,新增stream或者更新stream', data);
|
|
|
+ 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] || {
|
|
|
+ streamType: v.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 // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ // stream.objectFit = stream.hasVideo && stream.streamType === 'aux' && 'contain';
|
|
|
+
|
|
|
+ if(stream.hasVideo) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
|
|
|
+ })
|
|
|
+ this.users = [...this.users];
|
|
|
+ }break;
|
|
|
+ case 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] || {
|
|
|
+ streamType: v.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 // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
|
|
|
+ }
|
|
|
+
|
|
|
+ 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];
|
|
|
+ }break;
|
|
|
+ case -1301:
|
|
|
+ console.error('打开摄像头失败: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case -1302:
|
|
|
+ console.error('打开麦克风失败: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case -1303:
|
|
|
+ console.error('视频编码失败: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case -1304:
|
|
|
+ console.error('音频编码失败: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case -1307:
|
|
|
+ console.error('推流连接断开: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case -100018:
|
|
|
+ console.error('进房失败: ', code, message);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 5000:
|
|
|
+ console.log('小程序被挂起: ', code);
|
|
|
+ this.exitRoom()
|
|
|
+ this.enterRoom()
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1021:
|
|
|
+ console.log('网络类型发生变化,需要重新进房', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 2007:
|
|
|
+ console.log('本地视频播放loading: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 2004:
|
|
|
+ console.log('本地视频播放开始: ', code);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ console.log(message, code);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ pusherNetStatusHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+ pusherErrorHandlerFun: function(event) {
|
|
|
+ try {
|
|
|
+ console.error('pusher error: ');
|
|
|
+ const code = event.detail.errCode;
|
|
|
+ const message = event.detail.errMsg;
|
|
|
+ console.error(code, message);
|
|
|
+ } catch (exception) {}
|
|
|
+ },
|
|
|
+ pusherBGMStartHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+ pusherBGMProgressHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+ pusherBGMCompleteHandlerFun: function(event) {
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ /** --- player event handler --- **/
|
|
|
+ playerStateChangeFun: function(event) {
|
|
|
+ console.log('playerStateChangeFun');
|
|
|
+ },
|
|
|
+ playerFullscreenChangeFun: function(event) {
|
|
|
+ console.log('playerFullscreenChangeFun');
|
|
|
+ },
|
|
|
+ playerNetStatusFun: function(event) {
|
|
|
+ console.log('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('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>
|