live.service.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. import { Injectable } from '@angular/core';
  2. import { Router } from '@angular/router';
  3. import { AlertController } from '@ionic/angular';
  4. import * as Parse from 'parse';
  5. import { AiChatService } from './aichart.service';
  6. import { HttpService } from './http.service';
  7. import { MessageService } from './message.service';
  8. declare const AgoraRTC: any;
  9. @Injectable({
  10. providedIn: 'root',
  11. })
  12. export class LiveService {
  13. isAnchor: boolean = false; //是否是主播
  14. options: {
  15. appid: string;
  16. channel: string;
  17. token: string;
  18. } = {
  19. appid: '',
  20. channel: '',
  21. token: '',
  22. };
  23. localTracks: any = {
  24. audioTrack: null,
  25. videoTrack: null,
  26. };
  27. rid?: string; //房间id(channel)
  28. profile?: any = localStorage.getItem('profile');
  29. client: any; //客户端
  30. company: string = '';
  31. UID: any;
  32. tools: any = {
  33. audio: false, //是否关闭音频
  34. camera: false, //是否切换摄像头
  35. mute: false, //是否静音
  36. };
  37. user_published_list = new Set(); //已发布列表
  38. timer: any; //轮询获取频道token的定时
  39. alert: any; //提示框
  40. media_devices: {
  41. //音视频设备列表
  42. audioDevices: Array<any>;
  43. videoDevices: Array<any>;
  44. } = {
  45. audioDevices: [],
  46. videoDevices: [],
  47. };
  48. currentUsedDevice: { audioDevice: string; videoDevice: string } = {
  49. //当前使用的设备
  50. audioDevice: '',
  51. videoDevice: '',
  52. };
  53. room?: Parse.Object; //直播间
  54. connection_state?: string; //连接状态
  55. surplusNumber: number = 0; //剩余可通话时长(单位:秒s)
  56. countdown: number = 0; // 新增倒计时变量
  57. timer_countdown: any;
  58. liveLog?: Parse.Object; //直播记录
  59. isOpenEvaluate: boolean = false; //是否开启评价
  60. constructor(
  61. private http: HttpService,
  62. private router: Router,
  63. private aiServ: AiChatService,
  64. private alertController: AlertController,
  65. private msgSer: MessageService
  66. ) {
  67. this.client?.leave();
  68. this.company = this.aiServ.company;
  69. this.getProfile();
  70. }
  71. async getProfile() {
  72. if (this.profile) return;
  73. let queryProfile = new Parse.Query('Profile');
  74. queryProfile.equalTo('user', Parse.User.current()?.id);
  75. queryProfile.notEqualTo('isDeleted', true);
  76. queryProfile.equalTo('isCross', true);
  77. this.profile = await queryProfile.first();
  78. this.profile?.id &&
  79. localStorage.setItem('profile', JSON.stringify(this.profile.toJSON()));
  80. }
  81. /* 初始化Agora */
  82. initAgora() {
  83. this.timer && clearTimeout(this.timer);
  84. this.timer_countdown && clearInterval(this.timer_countdown);
  85. this.options['token'] = '';
  86. this.options['channel'] = '';
  87. this.client?.leave();
  88. this.client = AgoraRTC.createClient({ mode: 'rtc', codec: 'h264' });
  89. AgoraRTC.enableLogUpload();
  90. }
  91. // 获取所有音视频设备
  92. getDevices() {
  93. AgoraRTC.getDevices()
  94. .then((devices: any) => {
  95. this.media_devices.audioDevices = devices.filter(function (
  96. device: any
  97. ) {
  98. return device.kind === 'audioinput';
  99. });
  100. this.media_devices.videoDevices = devices.filter(function (
  101. device: any
  102. ) {
  103. return device.kind === 'videoinput';
  104. });
  105. console.log(this.media_devices);
  106. this.currentUsedDevice = {
  107. audioDevice: this.media_devices.audioDevices[0].deviceId,
  108. videoDevice: this.media_devices.videoDevices[0].deviceId,
  109. };
  110. // return Promise.all([
  111. // AgoraRTC.createCameraVideoTrack(),
  112. // AgoraRTC.createMicrophoneAudioTrack(),
  113. // ]);
  114. })
  115. .then((tracks: any) => {
  116. console.log(tracks);
  117. })
  118. .catch((err: any) => {
  119. console.warn('获取媒体权限失败', err);
  120. this.alertTips('获取媒体权限失败');
  121. });
  122. }
  123. /* 获取token */
  124. async getToken(room: Parse.Object) {
  125. this.room = room;
  126. this.isAnchor = room?.get('user')?.id === Parse.User.current()?.id;
  127. this.timer && clearTimeout(this.timer);
  128. let remoteEle = document.getElementById('vice-video');
  129. (remoteEle as any).style.display = 'none';
  130. //获取频道token记录
  131. let baseurl = 'https://server.fmode.cn/api/ailiao/token';
  132. if (this.isAnchor) {
  133. this.UID = 111111;
  134. baseurl = 'https://server.fmode.cn/api/webrtc/build_token';
  135. }
  136. let reqBody = {
  137. company: this.company, // this.aiSer.company,
  138. profile: room?.get('profile').id,
  139. channelName: room?.get('profile').id,
  140. };
  141. let data: any = await this.http.httpRequst(baseurl, reqBody, 'POST');
  142. console.log(data);
  143. if (data.code == 200) {
  144. this.options.token = data.data.token;
  145. this.options.appid = data.data.appid;
  146. } else {
  147. this.timer = setTimeout(() => {
  148. this.getToken(room);
  149. }, 2000);
  150. return;
  151. }
  152. this.options.channel = room?.get('profile').id;
  153. this.join();
  154. }
  155. async updateToken(pid: string) {
  156. let sql = `select "rtc"."objectId" as "rid","rtc"."channel", "rtc"."token", "rtc"."expiraTime" from "RtcToken" as "rtc" where "rtc"."profile" = '${pid}' and "rtc"."expiraTime" >= now() order by "createdAt" desc limit 1`;
  157. let tokenData: any = await this.http.customSQL(sql);
  158. if (
  159. tokenData &&
  160. tokenData.code == 200 &&
  161. tokenData.data &&
  162. tokenData.data.length > 0
  163. ) {
  164. return tokenData.data[0];
  165. } else {
  166. return null;
  167. }
  168. }
  169. /* 进入频道 */
  170. async join() {
  171. let path = location.pathname;
  172. if (path.indexOf('/live/link-room/') == -1) return;
  173. console.log('频道:', this.options.channel);
  174. this.client
  175. .join(
  176. this.options.appid,
  177. this.options.channel,
  178. this.options.token,
  179. this.UID
  180. )
  181. .then(async (uid: any) => {
  182. // await this.client.setClientRole('host');
  183. this.connection_state = this.client.connectionState;
  184. console.log('进入频道当前uid:', uid, '状态:', this.connection_state);
  185. let user = Parse.User.current();
  186. this.msgSer?.publishMessage(
  187. `${user?.get('name') || user?.get('nickname')}加入频道直播`,
  188. this.room?.get('user')?.id
  189. );
  190. if (!this.isAnchor) {
  191. // 观众进入直播间,创建直播记录
  192. await this.createLiveLog(uid);
  193. } else {
  194. //主播进入直播间直接发送自己推流
  195. await this.publishSelf();
  196. }
  197. await this.joinReady();
  198. this.afterJoin();
  199. })
  200. .catch((err: any) => {
  201. console.log('进入频道失败:', err);
  202. });
  203. }
  204. /* 发布本地视频 */
  205. async publishSelf() {
  206. try {
  207. let data = await Promise.all([
  208. /* 创建音频和视频轨道 */
  209. AgoraRTC.createMicrophoneAudioTrack(this.currentUsedDevice.audioDevice),
  210. AgoraRTC.createCameraVideoTrack(this.currentUsedDevice.videoDevice),
  211. ]);
  212. this.localTracks.audioTrack = data[0];
  213. this.localTracks.videoTrack = data[1];
  214. // console.log(this.localTracks);
  215. let remoteEle = document.getElementById('vice-video');
  216. if (remoteEle) {
  217. remoteEle.textContent = '';
  218. remoteEle.style.display = 'block';
  219. }
  220. this.localTracks.videoTrack.play('vice-video'); //播放自己视频渲染
  221. if (this.tools['audio']) {
  222. this.localTracks.audioTrack.setEnabled(false);
  223. } else {
  224. this.localTracks.audioTrack.setEnabled(true);
  225. }
  226. await this.client.publish(Object.values(this.localTracks));
  227. } catch (err) {
  228. console.log('发布本地视频失败:', err);
  229. // history.back()
  230. this.alertTips('发布本地视频失败,请检查摄像头是否授权或正常', '提示');
  231. this.client.leave();
  232. }
  233. }
  234. /* 订阅远程视频 */
  235. async joinReady() {
  236. this.client.remoteUsers.forEach((user: any) => {
  237. console.log('remoteUsers', user.uid);
  238. this.client.subscribe(user, 'audio').then((audioTrack: any) => {
  239. this.user_published_list.add(user);
  240. audioTrack.setVolume(100);
  241. audioTrack.play();
  242. });
  243. this.client.subscribe(user, 'video').then((videoTrack: any) => {
  244. let remoteEle = document.getElementById('video');
  245. if (remoteEle) {
  246. remoteEle.textContent = '';
  247. }
  248. videoTrack.play('video');
  249. });
  250. });
  251. this.client.on('user-joined', (user: any) => {
  252. console.log(user, `${user.uid} 加入频道`);
  253. });
  254. this.client.on('user-published', async (user: any, mediaType: string) => {
  255. console.log('用户推流成功', user);
  256. await this.client.subscribe(user, mediaType);
  257. let remoteEle = document.getElementById('video');
  258. if (remoteEle) {
  259. remoteEle.textContent = '';
  260. }
  261. if (mediaType === 'video') {
  262. user.videoTrack.play('video');
  263. this.alert?.dismiss();
  264. }
  265. if (mediaType === 'audio') {
  266. user.audioTrack.play();
  267. this.user_published_list.add(user);
  268. }
  269. });
  270. this.client.on('user-unpublished', async (user: any, mediaType: string) => {
  271. if (mediaType === 'audio') {
  272. console.log('对方已静音');
  273. this.user_published_list.delete(user);
  274. }
  275. if (mediaType === 'video') {
  276. console.log('用户取消推流');
  277. let remoteEle = document.getElementById('video');
  278. if (remoteEle) {
  279. remoteEle.textContent = '对方离开直播间';
  280. }
  281. //主播离开,停止计时计费
  282. // if (!this.isAnchor) {
  283. this.client.leave();
  284. // }
  285. // history.back()
  286. this.alertTips('对方已离开直播间');
  287. }
  288. });
  289. this.client.on(
  290. 'connection-state-change',
  291. (curState: any, prevState: any) => {
  292. this.connection_state = curState;
  293. if (
  294. curState == 'RECONNECTING' ||
  295. curState == 'DISCONNECTED' ||
  296. curState == 'DISCONNECTING'
  297. ) {
  298. this.timer && clearTimeout(this.timer);
  299. this.timer_countdown && clearInterval(this.timer_countdown);
  300. if (!this.isAnchor) {
  301. //用户离开直播间,断开与主播状态连接频道
  302. this.msgSer.unsubscribeMessage(this.room?.get('user')?.id);
  303. this.isOpenEvaluate = true;
  304. }
  305. console.log(this.router.url);
  306. if(this.router.url.indexOf('/live/link-room/') == 0)
  307. curState == 'DISCONNECTED' && history.back();
  308. }
  309. console.log('live状态变更:', this.connection_state);
  310. }
  311. );
  312. this.monitorDevices();
  313. }
  314. async createLiveLog(uid: string) {
  315. let profile = JSON.parse(localStorage.getItem('profile') || '{}');
  316. let obj = Parse.Object.extend('LiveLog');
  317. let liveLog = new obj();
  318. liveLog.set('title', `与${profile?.name || profile?.mobile}进行直播`);
  319. liveLog.set('uid', String(uid));
  320. liveLog.set('company', {
  321. __type: 'Pointer',
  322. className: 'Company',
  323. objectId: this.company,
  324. });
  325. liveLog.set('room', {
  326. __type: 'Pointer',
  327. className: 'Room',
  328. objectId: this.room?.id,
  329. });
  330. liveLog.set('user', {
  331. __type: 'Pointer',
  332. className: '_User',
  333. objectId: Parse.User.current()?.id,
  334. });
  335. liveLog.set('profile', {
  336. __type: 'Pointer',
  337. className: 'Profile',
  338. objectId: this.room?.get('profile')?.id,
  339. });
  340. this.liveLog = await liveLog.save();
  341. }
  342. /* 连线成功立即创建直播记录,同步获取剩余可通话时长,并且开始计时 */
  343. async afterJoin() {
  344. this.timer && clearTimeout(this.timer);
  345. this.timer_countdown && clearInterval(this.timer_countdown);
  346. const targetUser = this.client.remoteUsers?.find(
  347. (user: any) => user.uid !== 100001
  348. ); //排出超管
  349. console.log(targetUser);
  350. if (this.client.remoteUsers.length > 0 && targetUser) {
  351. if (this.isAnchor) {
  352. //如果是主播,进入获取remoteUsers.user获取livelog再获取对方剩余通话时长
  353. this.getLiveLog(targetUser.uid);
  354. } else {
  355. let query = new Parse.Query('LiveLog');
  356. query.equalTo('objectId', this.liveLog?.id);
  357. query.equalTo('isLive', true);
  358. query.select('objectId');
  359. const resultData = await query.first();
  360. if (resultData?.id) {
  361. //首次进入至少计时2分钟,不足2分钟按2分钟计算扣时
  362. await this.publishSelf();
  363. await this.get_duration();
  364. this.computeDuration(60000 * 2);
  365. } else {
  366. this.timer = setTimeout(() => {
  367. this.afterJoin();
  368. }, 1000);
  369. }
  370. }
  371. } else {
  372. this.timer = setTimeout(() => {
  373. this.afterJoin();
  374. }, 1000);
  375. }
  376. }
  377. async getLiveLog(tarUid: string) {
  378. // let uid = this.client.remoteUsers[0].uid;
  379. this.timer && clearTimeout(this.timer);
  380. let query = new Parse.Query('LiveLog');
  381. query.equalTo('uid', String(tarUid));
  382. query.equalTo('room', this.room?.id);
  383. query.notEqualTo('isDeleted', true);
  384. query.notEqualTo('isLive', true);
  385. query.descending('createdAt');
  386. this.liveLog = await query.first();
  387. if (this.liveLog?.id) {
  388. this.liveLog?.set('isLive', true);
  389. await this.liveLog?.save();
  390. await this.get_duration();
  391. this.getCallDuration();
  392. return;
  393. }
  394. this.timer = setTimeout(() => {
  395. this.getLiveLog(tarUid);
  396. }, 1000);
  397. }
  398. async get_duration() {
  399. this.timer_countdown && clearInterval(this.timer_countdown);
  400. let url = 'https://server.fmode.cn/api/ailiao/remain_second';
  401. let params = {
  402. rid: this.room?.id,
  403. uid: this.liveLog?.get('user')?.id || this.liveLog?.get('user')?.objectId,
  404. };
  405. let data = await this.http.httpRequst(url, params, 'POST');
  406. console.log(data);
  407. this.surplusNumber = data.data ?? 0;
  408. this.countdown = this.surplusNumber; // 初始化倒计时
  409. let arr = ['CONNECTING', 'CONNECTED'];
  410. if (arr.includes(this.connection_state as any)) {
  411. if (this.countdown <= 120) {
  412. this.alertTips('剩余通话时间不足2分钟,请及时充值');
  413. }
  414. this.startCountdown();
  415. }
  416. }
  417. startCountdown() {
  418. this.timer_countdown = setInterval(() => {
  419. if (this.countdown > 0) {
  420. this.countdown--;
  421. // console.log(this.countdown);
  422. } else {
  423. clearInterval(this.timer_countdown);
  424. // history.back()
  425. this.alertTips('通话时间结束');
  426. this.client.leave(); // 结束通话
  427. }
  428. }, 1000);
  429. }
  430. /* 开始计时 */
  431. async computeDuration(num?: number) {
  432. //每10秒保存一次数据
  433. this.timer && clearTimeout(this.timer);
  434. let p = JSON.parse(this.profile);
  435. console.log(p);
  436. let url = 'https://server.fmode.cn/api/ailiao/count/duration';
  437. let params = {
  438. company: this.company,
  439. profile: p?.objectId,
  440. rid: this.room?.id,
  441. uid: Parse.User.current()?.id,
  442. duration: num ? num / 1000 : 10,
  443. logid: this.liveLog?.id,
  444. };
  445. let data = await this.http.httpRequst(url, params, 'POST');
  446. console.log(data);
  447. this.timer = setTimeout(() => {
  448. this.computeDuration();
  449. }, num ?? 10000);
  450. }
  451. /* 主播每10s获取一次对方可通话时长 */
  452. getCallDuration() {
  453. this.timer && clearTimeout(this.timer);
  454. this.timer = setTimeout(async () => {
  455. await this.get_duration();
  456. let arr = ['CONNECTING', 'CONNECTED'];
  457. if (arr.includes(this.connection_state as any)) {
  458. this.getCallDuration();
  459. }
  460. }, 10000);
  461. }
  462. /* 监听音视频设备插拔 */
  463. monitorDevices() {
  464. AgoraRTC.onMicrophoneChanged = async (changedDevice: any) => {
  465. // 插入麦克风设备时,切换到新插入的设备
  466. if (changedDevice.state === 'ACTIVE') {
  467. this.localTracks.audioTrack.setDevice(changedDevice.device.deviceId);
  468. this.currentUsedDevice.audioDevice = changedDevice.device.deviceId;
  469. // 拔出设备为当前设备时,切换到一个已有的设备
  470. } else if (
  471. changedDevice.device.label ===
  472. this.localTracks.audioTrack.getTrackLabel()
  473. ) {
  474. const oldMicrophones = await AgoraRTC.getMicrophones();
  475. oldMicrophones[0] &&
  476. this.localTracks.audioTrack.setDevice(oldMicrophones[0].deviceId);
  477. this.currentUsedDevice.audioDevice = oldMicrophones[0].deviceId;
  478. }
  479. };
  480. AgoraRTC.onCameraChanged = async (changedDevice: any) => {
  481. // 插入相机设备时,切换到新插入的设备
  482. if (changedDevice.state === 'ACTIVE') {
  483. this.localTracks.videoTrack.setDevice(changedDevice.device.deviceId);
  484. this.currentUsedDevice.videoDevice = changedDevice.device.deviceId;
  485. // 拔出设备为当前设备时,切换到一个已有的设备
  486. } else if (
  487. changedDevice.device.label ===
  488. this.localTracks.videoTrack.getTrackLabel()
  489. ) {
  490. const oldCameras = await AgoraRTC.getCameras();
  491. oldCameras[0] &&
  492. this.localTracks.videoTrack.setDevice(oldCameras[0].deviceId);
  493. this.currentUsedDevice.videoDevice = oldCameras[0].deviceId;
  494. }
  495. };
  496. }
  497. /* 关开麦 */
  498. async updatePublishedAudioTrack() {
  499. this.tools['audio'] = !this.tools['audio'];
  500. if (this.tools['audio'] && this.localTracks.audioTrack) {
  501. await this.localTracks.audioTrack.setEnabled(false);
  502. console.log('停止推送音频');
  503. } else {
  504. await this.localTracks.audioTrack.setEnabled(true);
  505. console.log('恢复推送音频');
  506. }
  507. // await this.client.unpublish(this.localTracks.audioTrack);
  508. return true;
  509. }
  510. /* 静音 */
  511. async muteAudio() {
  512. this.tools['mute'] = !this.tools['mute'];
  513. let list = Array.from(this.user_published_list);
  514. for (let index = 0; index < list.length; index++) {
  515. const user = list[index];
  516. if (this.tools['mute']) {
  517. console.log('静音');
  518. await this.client.unsubscribe(user, 'audio');
  519. } else {
  520. await this.client.subscribe(user, 'audio');
  521. console.log('恢复声音');
  522. }
  523. }
  524. }
  525. /* 切换摄像头 */
  526. async changeCamera() {
  527. const oldCameras = await AgoraRTC.getCameras(true);
  528. let newCamers = oldCameras.find(
  529. (item: any) => item.deviceId !== this.currentUsedDevice.videoDevice
  530. );
  531. console.log(newCamers);
  532. newCamers &&
  533. this.localTracks.videoTrack
  534. .setDevice(newCamers.deviceId)
  535. .then(() => {
  536. this.tools['camera'] = !this.tools['camera'];
  537. this.currentUsedDevice.videoDevice = newCamers.deviceId;
  538. })
  539. .catch((err: any) => {
  540. console.log('set device error', err);
  541. this.alertTips('切换摄像头失败');
  542. });
  543. return true;
  544. }
  545. /* 提示 */
  546. async alertTips(message: string, title?: string, callBack?: Function) {
  547. this.alert = await this.alertController.create({
  548. header: title || '提示',
  549. message: message,
  550. backdropDismiss: false,
  551. buttons: [
  552. {
  553. text: '确认',
  554. handler: () => {
  555. callBack && callBack();
  556. },
  557. },
  558. ],
  559. });
  560. await this.alert.present();
  561. }
  562. }