live-wechat.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <template>
  2. <view class="container" data-type="template" data-is="1v1" data-attr="pusher, streamList, debug">
  3. <view v-for="item in streams.slice(0,1)" :key="item.streamID" class="player-container" :style="'display:'+playerStyle">
  4. <template v-if="item.src && (item.hasVideo || item.hasAudio)">
  5. <live-player
  6. class="player"
  7. :data-userid="item.userID"
  8. :data-streamid="item.streamID"
  9. :data-streamtype="item.streamType"
  10. :src="item.src"
  11. :mode="item.mode"
  12. :autoplay="item.autoplay"
  13. :mute-audio="item.muteAudio"
  14. :mute-video="item.muteVideo"
  15. :orientation="item.orientation"
  16. :object-fit="item.objectFit"
  17. :background-mute="item.enableBackgroundMute"
  18. :min-cache="item.minCache"
  19. :max-cache="item.maxCache"
  20. :sound-mode="item.soundMode"
  21. :enable-recv-message="item.enableRecvMessage"
  22. :auto-pause-if-navigate="item.autoPauseIfNavigate"
  23. :auto-pause-if-open-native="item.autoPauseIfOpenNative"
  24. :debug="debug"
  25. @statechange="playerStateChangeFun"
  26. @fullscreenchange="playerFullscreenChangeFun"
  27. @netstatus="playerNetStatusFun"
  28. @audiovolumenotify="playerAudioVolumeNotifyFun"
  29. :idAttr="item.streamID" />
  30. </template>
  31. </view>
  32. <view v-if="pusher.url" class="pusher-container" :style="'display:'+pusherStyle">
  33. <live-pusher
  34. class="pusher"
  35. :url="pusher.url"
  36. :mode="pusher.mode"
  37. :autopush="pusher.autopush"
  38. :enable-camera="pusher.enableCamera"
  39. :enable-mic="pusher.enableMic"
  40. :enable-agc="pusher.enableAgc"
  41. :enable-ans="pusher.enableAns"
  42. :enable-ear-monitor="pusher.enableEarMonitor"
  43. :auto-focus="pusher.enableAutoFocus"
  44. :zoom="pusher.enableZoom"
  45. :min-bitrate="pusher.minBitrate"
  46. :max-bitrate="pusher.maxBitrate"
  47. :video-width="pusher.videoWidth"
  48. :video-height="pusher.videoHeight"
  49. :beauty="pusher.beautyLevel"
  50. :whiteness="pusher.whitenessLevel"
  51. :orientation="pusher.videoOrientation"
  52. :aspect="pusher.videoAspect"
  53. :device-position="pusher.frontCamera"
  54. :remote-mirror="pusher.enableRemoteMirror"
  55. :local-mirror="pusher.localMirror"
  56. :background-mute="pusher.enableBackgroundMute"
  57. :audio-quality="pusher.audioQuality"
  58. :audio-volume-type="pusher.audioVolumeType"
  59. :audio-reverb-type="pusher.audioReverbType"
  60. :waiting-image="pusher.waitingImage"
  61. :debug="debug"
  62. @statechange="pusherStateChangeHandlerFun"
  63. @netstatus="pusherNetStatusHandlerFun"
  64. @error="pusherErrorHandlerFun"
  65. @bgmstart="pusherBGMStartHandlerFun"
  66. @bgmprogress="pusherBGMProgressHandlerFun"
  67. @bgmcomplete="pusherBGMCompleteHandlerFun" />
  68. </view>
  69. </view>
  70. </template>
  71. <script>
  72. const LOGTAG = '--JHLIVE--:';
  73. const pusherStateCodeDic = {
  74. 1001: '已经连接推流服务器',
  75. 1002: '已经与服务器握手完毕,开始推流',
  76. 1003: '打开摄像头成功',
  77. 1004: '录屏启动成功',
  78. 1005: '推流动态调整分辨率',
  79. 1006: '推流动态调整码率',
  80. 1007: '首帧画面采集完成',
  81. 1008: '编码器启动',
  82. 1018: '进房成功',
  83. 1019: '退出房间',
  84. 2003: '渲染首帧视频',
  85. 1020: '远端用户全量列表更新',
  86. 1031: '远端用户进房通知',
  87. 1032: '远端用户退房通知',
  88. 1033: '用户视频状态变化,新增stream或者更新stream',
  89. 1034: '远端用户音频状态位变化通知',
  90. '-1301': '打开摄像头失败',
  91. '-1302': '打开麦克风失败',
  92. '-1303': '视频编码失败',
  93. '-1304': '音频编码失败',
  94. '-1307': '推流连接断开',
  95. '-100018': '进房失败',
  96. '5000': '小程序被挂起',
  97. '1021': '网络类型发生变化,需要重新进房',
  98. '2007': '本地视频播放loading',
  99. '2004': '本地视频播放开始',
  100. }
  101. const defaultStreamParams = {
  102. streamType: '',
  103. src: '',
  104. mode: 'RTC',
  105. autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
  106. muteAudio: false, // 默认不拉取音频,需要手动订阅
  107. muteVideo: false, // 默认不拉取视频,需要手动订阅
  108. orientation: 'vertical', // 画面方向 vertical horizontal
  109. objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
  110. enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
  111. minCache: 1, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
  112. maxCache: 2, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
  113. soundMode: 'speaker', // 声音输出方式 ear speaker
  114. enableRecvMessage: 'false', // 是否接收SEI消息
  115. autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
  116. autoPauseIfOpenNative: true // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
  117. }
  118. export default {
  119. name: 'jhlive-wechat',
  120. data() {
  121. return {
  122. pusher: {
  123. url: '',
  124. mode: 'RTC', // RTC:实时通话(trtc sdk) live:直播模式(liteav sdk)
  125. autopush: false, // 自动推送
  126. enableCamera: false, // 是否开启摄像头
  127. enableMic: false, // 是否开启麦克风
  128. enableAgc: false, // 是否开启音频自动增益
  129. enableAns: false, // 是否开启音频噪声抑制
  130. enableEarMonitor: false, // 是否开启耳返(目前只在iOS平台有效)
  131. enableAutoFocus: true, // 是否自动对焦
  132. enableZoom: false, // 是否支持调整焦距
  133. minBitrate: 200, // 最小码率
  134. maxBitrate: 1000, // 最大码率
  135. videoWidth: 360, // 视频宽(若设置了视频宽高就会忽略aspect)
  136. videoHeight: 640, // 视频高(若设置了视频宽高就会忽略aspect)
  137. beautyLevel: 0, // 美颜,取值范围 0-9 ,0 表示关闭
  138. whitenessLevel: 0, // 美白,取值范围 0-9 ,0 表示关闭
  139. videoOrientation: 'vertical', // vertical horizontal
  140. videoAspect: '9:16', // 宽高比,可选值有 3:4,9:16
  141. frontCamera: 'front', // 前置或后置摄像头,可选值:front,back
  142. enableRemoteMirror: false, // 设置推流画面是否镜像,产生的效果会表现在 live-player
  143. localMirror: 'auto', // auto:前置摄像头镜像,后置摄像头不镜像(系统相机的表现)enable:前置摄像头和后置摄像头都镜像 disable: 前置摄像头和后置摄像头都不镜像
  144. enableBackgroundMute: false, // 进入后台时是否静音
  145. audioQuality: 'high', // 高音质(48KHz)或低音质(16KHz),可选值:high,low
  146. audioVolumeType: 'voicecall', // 声音类型 可选值: media: 媒体音量,voicecall: 通话音量
  147. audioReverbType: 0, // 音频混响类型 0: 关闭 1: KTV 2: 小房间 3:大会堂 4:低沉 5:洪亮 6:金属声 7:磁性
  148. waitingImage: '', // 当微信切到后台时的垫片图片 trtc暂不支持
  149. waitingImageHash: ''
  150. },
  151. users: [],
  152. };
  153. },
  154. props: {
  155. sdkAppID: Number,
  156. userSig: String,
  157. userid: String,
  158. roomid: String,
  159. isAuthor: Boolean,
  160. linkMic: Boolean
  161. },
  162. computed: {
  163. streams() {
  164. console.log(LOGTAG+'users', this.users);
  165. let streams = [];
  166. this.users.forEach(v=>{
  167. let userStreams = Object.entries(v.streams);
  168. streams = streams.concat(userStreams.map(([kk,vv])=>vv).filter(vv=>vv.hasVideo));
  169. });
  170. console.log(LOGTAG+'streams', streams);
  171. return streams;
  172. },
  173. playerStyle() {
  174. return (!this.isAuthor)?'block':'none';
  175. },
  176. pusherStyle() {
  177. return this.isAuthor?'block':'none';
  178. }
  179. },
  180. methods: {
  181. enterRoom() {
  182. 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`;
  183. this.pusher.autopush = this.isAuthor||this.linkMic;
  184. this.pusher.enableCamera = this.isAuthor;
  185. this.pusher.enableMic = this.isAuthor||this.linkMic;
  186. this.$nextTick(()=>{
  187. this.pusherContext = wx.createLivePusherContext();
  188. this.pusherContext.start();
  189. })
  190. console.log(LOGTAG+'pusher',this.pusher);
  191. },
  192. exitRoom: function() {
  193. if (this.pusherContext) {
  194. this.pusherContext.stop();
  195. this.pusherContext = null;
  196. }
  197. },
  198. switchCarema() {
  199. wx.createLivePusherContext().switchCamera();
  200. this.pusher.frontCamera = this.pusher.frontCamera=='front'?'back':'front';
  201. console.log(LOGTAG+'pusher',this.pusher);
  202. },
  203. setBeauty(blv, wlv) {
  204. this.pusher.beautyLevel = blv;
  205. this.pusher.whitenessLevel = wlv;
  206. console.log(LOGTAG+'pusher',this.pusher);
  207. },
  208. /** --- pusher event handler --- **/
  209. pusherStateChangeHandlerFun: function(event) {
  210. const code = event.detail.code;
  211. const message = event.detail.message;
  212. console.log(LOGTAG+'pusherState',code, pusherStateCodeDic[code]);
  213. // 远端用户进房通知
  214. if(code == 1031) {
  215. const data = JSON.parse(event.detail.message)||{};
  216. (data.userlist||[]).forEach( v=> {
  217. if(!this.users.find(vv => vv.userid == v.userid)) {
  218. this.users.push({
  219. userid: v.userid,
  220. streams: {},
  221. });
  222. this.$emit('onRemoteUserEnterRoom', {detail:v.userid});
  223. }
  224. })
  225. }
  226. // 远端用户退房通知
  227. if(code == 1032) {
  228. const data = JSON.parse(event.detail.message)||{};
  229. (data.userlist||[]).forEach( v=> {
  230. let user = this.users.find(v => v.userid == data.userid);
  231. if(!user) return;
  232. user.streams.forEach(vv=>{
  233. if(vv.playerContext) {
  234. vv.playerContext.stop;
  235. vv.playerContext = null;
  236. }
  237. });
  238. user.streams = [];
  239. this.$emit('onRemoteUserLeaveRoom', {detail:v.userid});
  240. })
  241. this.users = this.users.filter(v => (data.userlist||[]).find(vv=>vv.userid!=v.userid));
  242. }
  243. // 用户视频状态变化,新增stream或者更新stream 状态
  244. if(code == 1033) {
  245. const data = JSON.parse(event.detail.message)||{};
  246. (data.userlist||[]).forEach( v=> {
  247. let user = this.users.find(vv => v.userid == v.userid);
  248. if(!user) return;
  249. let stream = user.streams[v.streamtype] = user.streams[v.streamtype] || {...defaultStreamParams};
  250. stream.streamtype = v.streamtype;
  251. stream.userID = v.userid;
  252. stream.streamID = v.userid + '_' + stream.streamType;
  253. stream.hasVideo = v.hasvideo;
  254. stream[`has${stream.streamType}Video`] = stream.hasVideo;
  255. stream.src = v.playurl;
  256. if(stream.hasVideo) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
  257. })
  258. this.users = [...this.users];
  259. }
  260. // 远端用户音频状态位变化通知
  261. if(code == 1034) {
  262. console.log('远端用户音频状态位变化通知', data);
  263. const data = JSON.parse(event.detail.message)||{};
  264. (data.userlist||[]).forEach( v=> {
  265. let user = this.users.find(vv => v.userid == vv.userid);
  266. if(!user) return;
  267. v.streamtype = 'main';
  268. let stream = user.streams[v.streamtype] = user.streams[v.streamtype] || {...defaultStreamParams}
  269. stream.streamtype = v.streamtype;
  270. stream.userID = v.userid;
  271. stream.streamID = v.userid + '_' + stream.streamType;
  272. stream.hasAudio = v.hasaudio;
  273. stream[`has${stream.streamType}Audio`] = stream.hasAudio;
  274. stream.src = v.playurl;
  275. if(stream.hasAudio) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
  276. })
  277. this.users = [...this.users];
  278. }
  279. },
  280. pusherNetStatusHandlerFun: function(event) {
  281. },
  282. pusherErrorHandlerFun: function(event) {
  283. this.$emit('liveEvent', {detail:{eventName: 'onError', eventData: {msg: '推送错误'}}});
  284. console.error(LOGTAG+'pusher error: ',event.detail);
  285. },
  286. pusherBGMStartHandlerFun: function(event) {
  287. },
  288. pusherBGMProgressHandlerFun: function(event) {
  289. },
  290. pusherBGMCompleteHandlerFun: function(event) {
  291. },
  292. /** --- player event handler --- **/
  293. playerStateChangeFun: function(event) {
  294. console.log(LOGTAG+'playerStateChangeFun');
  295. },
  296. playerFullscreenChangeFun: function(event) {
  297. console.log(LOGTAG+'playerFullscreenChangeFun');
  298. },
  299. playerNetStatusFun: function(event) {
  300. console.log(LOGTAG+'playerNetStatusFun');
  301. let dataset = event.currentTarget.dataset;
  302. let info = event.detail.info;
  303. let user = this.users.find(v => v.userid == dataset.userid);
  304. if(!user) return;
  305. let stream = user.streams[dataset.streamtype];
  306. if(!stream) return;
  307. stream.videoWidth = info.videoWidth;
  308. stream.videoHeight = info.videoHeight;
  309. },
  310. playerAudioVolumeNotifyFun: function(event) {
  311. console.log(LOGTAG+'playerAudioVolumeNotifyFun');
  312. },
  313. }
  314. };
  315. </script>
  316. <style lang="less" scoped>
  317. .container {
  318. width: 100%;
  319. height: 100%;
  320. }
  321. .pusher-container {
  322. width: 100%;
  323. height: 100%;
  324. }
  325. .player-container{
  326. width: 100%;
  327. height: 100%;
  328. }
  329. .pusher {
  330. width: 100%;
  331. height: 100%;
  332. }
  333. .player {
  334. width: 100%;
  335. height: 100%;
  336. }
  337. </style>