live-wechat.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  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="RTC"
  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 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. export default {
  73. name: 'jhlive-wechat',
  74. data() {
  75. return {
  76. pusher: null,
  77. users: [],
  78. };
  79. },
  80. props: {
  81. sdkAppID: Number,
  82. userSig: String,
  83. userid: String,
  84. roomid: String,
  85. isAuthor: Boolean,
  86. linkMic: Boolean
  87. },
  88. computed: {
  89. streams() {
  90. console.log('--users', this.users);
  91. let streams = [];
  92. this.users.forEach(v=>{
  93. let userStreams = Object.entries(v.streams);
  94. streams = streams.concat(userStreams.map(([kk,vv])=>vv).filter(vv=>vv.hasVideo));
  95. });
  96. console.log('--streams', streams);
  97. return streams;
  98. },
  99. playerStyle() {
  100. return (!this.isAuthor)?'block':'none';
  101. },
  102. pusherStyle() {
  103. return this.isAuthor?'block':'none';
  104. }
  105. },
  106. methods: {
  107. enterRoom() {
  108. this.pusher = {
  109. url: 'room://cloud.tencent.com/rtc?sdkappid=' +
  110. this.sdkAppID +
  111. '&roomid=' +
  112. this.roomid +
  113. '&userid=' +
  114. this.userid +
  115. '&usersig=' +
  116. this.userSig +
  117. '&appscene=' +
  118. 'live' +
  119. '&encsmall=' +
  120. '1' +
  121. '&cloudenv=' +
  122. 'PRO',
  123. mode: 'RTC', // RTC:实时通话(trtc sdk) live:直播模式(liteav sdk)
  124. autopush: this.isAuthor||this.linkMic, // 自动推送
  125. enableCamera: this.isAuthor, // 是否开启摄像头
  126. enableMic: this.isAuthor||this.linkMic, // 是否开启麦克风
  127. enableAgc: false, // 是否开启音频自动增益
  128. enableAns: false, // 是否开启音频噪声抑制
  129. enableEarMonitor: false, // 是否开启耳返(目前只在iOS平台有效)
  130. enableAutoFocus: true, // 是否自动对焦
  131. enableZoom: false, // 是否支持调整焦距
  132. minBitrate: 200, // 最小码率
  133. maxBitrate: 1000, // 最大码率
  134. videoWidth: 360, // 视频宽(若设置了视频宽高就会忽略aspect)
  135. videoHeight: 640, // 视频高(若设置了视频宽高就会忽略aspect)
  136. beautyLevel: 0, // 美颜,取值范围 0-9 ,0 表示关闭
  137. whitenessLevel: 0, // 美白,取值范围 0-9 ,0 表示关闭
  138. videoOrientation: 'vertical', // vertical horizontal
  139. videoAspect: '9:16', // 宽高比,可选值有 3:4,9:16
  140. frontCamera: 'front', // 前置或后置摄像头,可选值:front,back
  141. enableRemoteMirror: false, // 设置推流画面是否镜像,产生的效果会表现在 live-player
  142. localMirror: 'auto', // auto:前置摄像头镜像,后置摄像头不镜像(系统相机的表现)enable:前置摄像头和后置摄像头都镜像 disable: 前置摄像头和后置摄像头都不镜像
  143. enableBackgroundMute: false, // 进入后台时是否静音
  144. audioQuality: 'high', // 高音质(48KHz)或低音质(16KHz),可选值:high,low
  145. audioVolumeType: 'voicecall', // 声音类型 可选值: media: 媒体音量,voicecall: 通话音量
  146. audioReverbType: 0, // 音频混响类型 0: 关闭 1: KTV 2: 小房间 3:大会堂 4:低沉 5:洪亮 6:金属声 7:磁性
  147. waitingImage: '', // 当微信切到后台时的垫片图片 trtc暂不支持
  148. waitingImageHash: ''
  149. };
  150. console.log('JHLIVE-PUSHER CONFIG', this.pusher);
  151. setTimeout(()=>{
  152. this.pusherContext = wx.createLivePusherContext();
  153. this.pusherContext.start();
  154. console.log('JHLIVE-PUSHER pusherContext', this.pusherContext);
  155. },1000);
  156. },
  157. exitRoom: function() {
  158. if (this.pusherContext) {
  159. this.pusherContext.stop();
  160. this.pusherContext = null;
  161. }
  162. },
  163. /** --- pusher event handler --- **/
  164. pusherStateChangeHandlerFun: function(event) {
  165. const code = event.detail.code;
  166. const message = event.detail.message;
  167. switch (code) {
  168. case 0:
  169. console.log(message, code);
  170. break;
  171. case 1001:
  172. console.log('已经连接推流服务器', code);
  173. break;
  174. case 1002:
  175. console.log('已经与服务器握手完毕,开始推流', code);
  176. break;
  177. case 1003:
  178. console.log('打开摄像头成功', code);
  179. break;
  180. case 1004:
  181. console.log('录屏启动成功', code);
  182. break;
  183. case 1005:
  184. console.log('推流动态调整分辨率', code);
  185. break;
  186. case 1006:
  187. console.log('推流动态调整码率', code);
  188. break;
  189. case 1007:
  190. console.log('首帧画面采集完成', code);
  191. break;
  192. case 1008:
  193. console.log('编码器启动', code);
  194. break;
  195. case 1018:
  196. console.log('进房成功', code);
  197. break;
  198. case 1019:
  199. console.log('退出房间', code);
  200. break;
  201. case 2003:
  202. console.log('渲染首帧视频', code);
  203. break;
  204. case 1020: // 远端用户全量列表更新
  205. break;
  206. case 1031: {// 远端用户进房通知
  207. const data = JSON.parse(event.detail.message)||{};
  208. console.log('远端用户进房通知', data);
  209. (data.userlist||[]).forEach( v=> {
  210. if(!this.users.find(vv => vv.userid == v.userid)) {
  211. this.users.push({
  212. userid: v.userid,
  213. streams: {},
  214. });
  215. }
  216. })
  217. }break;
  218. case 1032: {// 远端用户退房通知
  219. console.log('远端用户退房通知', data);
  220. const data = JSON.parse(event.detail.message)||{};
  221. (data.userlist||[]).forEach( v=> {
  222. let user = this.users.find(v => v.userid == data.userid);
  223. if(!user) return;
  224. user.streams.forEach(vv=>{
  225. if(vv.playerContext) {
  226. vv.playerContext.stop;
  227. vv.playerContext = null;
  228. }
  229. });
  230. user.streams = [];
  231. })
  232. this.users = this.users.filter(v => (data.userlist||[]).find(vv=>vv.userid!=v.userid));
  233. }break;
  234. case 1033: {// 用户视频状态变化,新增stream或者更新stream 状态
  235. console.log('用户视频状态变化,新增stream或者更新stream', data);
  236. const data = JSON.parse(event.detail.message)||{};
  237. (data.userlist||[]).forEach( v=> {
  238. let user = this.users.find(vv => v.userid == v.userid);
  239. if(!user) return;
  240. let stream = user.streams[v.streamtype] = user.streams[v.streamtype] || {
  241. streamType: v.streamtype,
  242. src: '',
  243. mode: 'RTC',
  244. autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
  245. muteAudio: false, // 默认不拉取音频,需要手动订阅
  246. muteVideo: false, // 默认不拉取视频,需要手动订阅
  247. orientation: 'vertical', // 画面方向 vertical horizontal
  248. objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
  249. enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
  250. minCache: 1, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
  251. maxCache: 2, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
  252. soundMode: 'speaker', // 声音输出方式 ear speaker
  253. enableRecvMessage: 'false', // 是否接收SEI消息
  254. autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
  255. autoPauseIfOpenNative: true // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
  256. }
  257. stream.userID = v.userid;
  258. stream.streamID = v.userid + '_' + stream.streamType;
  259. stream.hasVideo = v.hasvideo;
  260. stream[`has${stream.streamType}Video`] = stream.hasVideo;
  261. stream.src = v.playurl;
  262. // stream.objectFit = stream.hasVideo && stream.streamType === 'aux' && 'contain';
  263. if(stream.hasVideo) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
  264. })
  265. this.users = [...this.users];
  266. }break;
  267. case 1034: {// 远端用户音频状态位变化通知
  268. console.log('远端用户音频状态位变化通知', data);
  269. const data = JSON.parse(event.detail.message)||{};
  270. (data.userlist||[]).forEach( v=> {
  271. let user = this.users.find(vv => v.userid == vv.userid);
  272. if(!user) return;
  273. v.streamtype = 'main';
  274. let stream = user.streams[v.streamtype] = user.streams[v.streamtype] || {
  275. streamType: v.streamtype,
  276. src: '',
  277. mode: 'RTC',
  278. autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
  279. muteAudio: false, // 默认不拉取音频,需要手动订阅
  280. muteVideo: false, // 默认不拉取视频,需要手动订阅
  281. orientation: 'vertical', // 画面方向 vertical horizontal
  282. objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
  283. enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
  284. minCache: 1, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
  285. maxCache: 2, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
  286. soundMode: 'speaker', // 声音输出方式 ear speaker
  287. enableRecvMessage: 'false', // 是否接收SEI消息
  288. autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
  289. autoPauseIfOpenNative: true // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
  290. }
  291. stream.userID = v.userid;
  292. stream.streamID = v.userid + '_' + stream.streamType;
  293. stream.hasAudio = v.hasaudio;
  294. stream[`has${stream.streamType}Audio`] = stream.hasAudio;
  295. stream.src = v.playurl;
  296. if(stream.hasAudio) stream.playerContext = wx.createLivePlayerContext(stream.streamID, this);
  297. })
  298. this.users = [...this.users];
  299. }break;
  300. case -1301:
  301. console.error('打开摄像头失败: ', code);
  302. break;
  303. case -1302:
  304. console.error('打开麦克风失败: ', code);
  305. break;
  306. case -1303:
  307. console.error('视频编码失败: ', code);
  308. break;
  309. case -1304:
  310. console.error('音频编码失败: ', code);
  311. break;
  312. case -1307:
  313. console.error('推流连接断开: ', code);
  314. break;
  315. case -100018:
  316. console.error('进房失败: ', code, message);
  317. break;
  318. case 5000:
  319. console.log('小程序被挂起: ', code);
  320. this.exitRoom()
  321. this.enterRoom()
  322. break;
  323. case 1021:
  324. console.log('网络类型发生变化,需要重新进房', code);
  325. break;
  326. case 2007:
  327. console.log('本地视频播放loading: ', code);
  328. break;
  329. case 2004:
  330. console.log('本地视频播放开始: ', code);
  331. break;
  332. default:
  333. console.log(message, code);
  334. }
  335. },
  336. pusherNetStatusHandlerFun: function(event) {
  337. },
  338. pusherErrorHandlerFun: function(event) {
  339. try {
  340. console.error('pusher error: ');
  341. const code = event.detail.errCode;
  342. const message = event.detail.errMsg;
  343. console.error(code, message);
  344. } catch (exception) {}
  345. },
  346. pusherBGMStartHandlerFun: function(event) {
  347. },
  348. pusherBGMProgressHandlerFun: function(event) {
  349. },
  350. pusherBGMCompleteHandlerFun: function(event) {
  351. },
  352. /** --- player event handler --- **/
  353. playerStateChangeFun: function(event) {
  354. console.log('playerStateChangeFun');
  355. },
  356. playerFullscreenChangeFun: function(event) {
  357. console.log('playerFullscreenChangeFun');
  358. },
  359. playerNetStatusFun: function(event) {
  360. console.log('playerNetStatusFun');
  361. let dataset = event.currentTarget.dataset;
  362. let info = event.detail.info;
  363. let user = this.users.find(v => v.userid == dataset.userid);
  364. if(!user) return;
  365. let stream = user.streams[dataset.streamtype];
  366. if(!stream) return;
  367. stream.videoWidth = info.videoWidth;
  368. stream.videoHeight = info.videoHeight;
  369. },
  370. playerAudioVolumeNotifyFun: function(event) {
  371. console.log('playerAudioVolumeNotifyFun');
  372. },
  373. }
  374. };
  375. </script>
  376. <style lang="less" scoped>
  377. .container {
  378. width: 100%;
  379. height: 100%;
  380. }
  381. .pusher-container {
  382. width: 100%;
  383. height: 100%;
  384. }
  385. .player-container{
  386. width: 100%;
  387. height: 100%;
  388. }
  389. .pusher {
  390. width: 100%;
  391. height: 100%;
  392. }
  393. .player {
  394. width: 100%;
  395. height: 100%;
  396. }
  397. </style>