live.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import { Message } from 'element-ui';
  2. import store from '@/store';
  3. import { GetLiveRoomInfo } from '@/api/live';
  4. /**
  5. * @description WebSDK 实例化对象
  6. */
  7. let rtc;
  8. /**
  9. * @method initSDK
  10. * @description 初始化SDK方法
  11. * @param { object } data 初始化SDK所需参数
  12. * @returns { object }
  13. */
  14. export function initSDK(data) {
  15. rtc = new Rtc(data);
  16. return rtc;
  17. }
  18. /**
  19. * 推送本地流
  20. * @method publishStream
  21. */
  22. export function publishStream(streamName) {
  23. rtc.publish({
  24. streamName,
  25. // 推流成功,更新上麦结果
  26. success: function (stream) {
  27. console.log('推流成功', stream);
  28. rtc.updateMcResult({
  29. pid: 1,
  30. stid: stream.id(),
  31. success: function (data) {
  32. console.log('更新上麦结果请求成功,此处可处理应用层逻辑', data);
  33. },
  34. fail: function (data) {
  35. console.log('更新上麦结果请求失败:' + JSON.stringify(data));
  36. }
  37. });
  38. },
  39. fail: function (str) {
  40. // 推流失败,更新上麦结果
  41. console.log('推流失败,更新上麦结果', str);
  42. }
  43. });
  44. }
  45. /**
  46. * @description 查询直播是否开启
  47. */
  48. export function getLiveStat(obj) {
  49. rtc.getLiveStat(obj);
  50. }
  51. /**
  52. * @method startLive
  53. * @description 开启直播
  54. */
  55. export function startLive() {
  56. rtc.startLive({
  57. success(data) {
  58. console.log(data);
  59. publishStream('main'); // 如果需要立即推流,执行 publish 方法
  60. Message({
  61. message: '开启直播成功',
  62. type: 'success'
  63. });
  64. },
  65. fail(data) {
  66. Message({
  67. message: `开启直播失败:${data}`,
  68. type: 'warning'
  69. });
  70. }
  71. });
  72. }
  73. /**
  74. * 创建本地流
  75. * @method createLocalStram
  76. */
  77. export function createLocalStream() {
  78. const createData = {
  79. video: true,
  80. audio: true
  81. };
  82. rtc.createLocalStream({
  83. streamName: 'main',
  84. createData,
  85. success: function (stream) {
  86. console.log('创建本地流成功', stream);
  87. // 创建本地流成功,将流展示到id为 live 的dom元素盒子中
  88. stream.show('live');
  89. publishStream('main'); // 如果需要立即推流,执行 publish 方法
  90. // getLiveStat({
  91. // success: function (data) {
  92. // console.log(data.started);
  93. // },
  94. // fail: function (str) {
  95. // // startLive();
  96. // }
  97. // });
  98. },
  99. fail: function (data) {
  100. // 创建本地流失败,应用层处理
  101. Message({
  102. type: 'error',
  103. message: '创建本地流失败:' + data
  104. });
  105. }
  106. });
  107. }
  108. /**
  109. * @method closeVideo
  110. * @description 结束本地流
  111. */
  112. export function closeVideo(streamName) {
  113. rtc.closeVideo({
  114. streamName,
  115. success: function () {
  116. console.log('结束本地流成功');
  117. },
  118. fail: function (str) {
  119. console.log(str);
  120. }
  121. });
  122. }
  123. /**
  124. * 初始化监听事件
  125. * @method initListener
  126. */
  127. export function initListener(vue) {
  128. rtc.on('login_success', data => {
  129. console.log('登录成功', data);
  130. Message({
  131. message: '登录成功',
  132. type: 'success'
  133. });
  134. vue.roomData = data;
  135. // 初始化画板需要的数据
  136. let canvasInitData = {
  137. allowDraw: data.user.role === 'presenter', // 是否具有书写画笔权限(讲师权限) true / false
  138. id: 'draw-parent',
  139. pptDisplay: 1, // 文档展示方式。默认0,按窗口 1, 按宽度
  140. liveId: data.live.status === 1 ? data.live.id : '' // 如果直播已经开始,需将直播 id 传入 sdk 中
  141. };
  142. // 初始化画板
  143. rtc.canvasInit(canvasInitData);
  144. });
  145. rtc.on('login_failed', data => {
  146. console.log('登录失败', data);
  147. Message({
  148. message: '登录失败:' + JSON.stringify(data),
  149. type: 'warning'
  150. });
  151. });
  152. // 必须在加入房间成功的“conference_join”事件回调里创建本地流
  153. rtc.on('conference_join', () => {
  154. console.log('加入房间成功');
  155. // 有监听就是加入房间成功
  156. if (store.state.user.user_type === 'TEACHER') {
  157. createLocalStream();
  158. }
  159. });
  160. rtc.on('conference_join_failed', err => {
  161. // 加入房间失败 err为错误原因
  162. console.log('加入房间失败', err);
  163. });
  164. // 新增订阅流事件
  165. rtc.on('allow_sub', function (stream) {
  166. if (stream.isMixed()) {
  167. console.log('是混合流,不订阅');
  168. } else {
  169. // 订阅远程流
  170. rtc.trySubscribeStream({
  171. tryStream: stream,
  172. success: function (stream) {
  173. // 订阅流成功
  174. let streamType = stream.streamType();
  175. console.log('订阅流成功', streamType);
  176. stream.show(store.state.user.user_type === 'STUDENT' ? 'live' : 'student', 'contain'); // 将流显示到id为 live 的盒子中
  177. if (streamType === 10 || streamType === 1) {
  178. vue.connection = true;
  179. }
  180. },
  181. fail: function (err) {
  182. console.log('订阅流失败', err);
  183. }
  184. });
  185. }
  186. });
  187. // 直播未开始,不能推流
  188. rtc.on('not_live', function () {
  189. console.log('直播未开始,不能推流');
  190. Message({
  191. message: '直播未开始,不能推流',
  192. type: 'warning'
  193. });
  194. });
  195. // 推流前查询直播状态失败,导致没有推流
  196. rtc.on('local_stream_publish_failed', function () {
  197. console.log('推流前查询直播状态失败,导致没有推流');
  198. Message({
  199. message: '推流前查询直播状态失败,导致没有推流',
  200. type: 'warning'
  201. });
  202. });
  203. // 房间全量信息事件(人员进出时广播)
  204. rtc.on('room_context', roomData => {
  205. vue.roomContext = JSON.parse(roomData);
  206. vue.getLiveRoomStudentList();
  207. console.log('房间全量信息事件(人员进出时广播)', JSON.parse(roomData));
  208. });
  209. rtc.on('publish_stream', str => {
  210. console.log('直播已开启', str);
  211. vue.liveStat = true;
  212. });
  213. rtc.on('end_stream', str => {
  214. console.log('直播已关闭', str);
  215. vue.liveStat = false;
  216. });
  217. rtc.on('switch_user_settings', settingData => {
  218. // 单个用户配置监听
  219. console.log(settingData);
  220. });
  221. // 人员列表事件(人员麦序变化时广播)
  222. rtc.on('speak_context', speakData => {
  223. vue.speakData = JSON.parse(speakData);
  224. console.log('人员列表事件(人员麦序变化时广播)', JSON.parse(speakData));
  225. });
  226. rtc.on('switch_settings', data => {
  227. console.log('房间设置事件', data); // 房间设置事件
  228. });
  229. rtc.on('publishStreamErr', data => {
  230. console.log('推流意外终止:' + data.streamName);
  231. // 直播开启状态下,尝试重推这条流
  232. });
  233. // 视频无法自动播放
  234. rtc.on('playError', data => {
  235. console.log('视频无法自动播放', data);
  236. });
  237. // 用户退出房间通知其他人员事件
  238. rtc.on('exit_room_user', function (data) {
  239. console.log('用户退出房间通知其他人员事件', data);
  240. vue.studentExitLiveRoom(data.id);
  241. });
  242. // 监听通知移除流事件
  243. rtc.on('stream_removed', function (stream) {
  244. console.log('监听通知移除流事件');
  245. if (stream.streamType() === 10 || stream.streamType() === 1) {
  246. vue.connection = false;
  247. }
  248. });
  249. // 停止订阅流
  250. rtc.on('unSub', function (stream) {
  251. console.log('停止订阅流');
  252. rtc.unSubscribeStream({
  253. unSubStream: stream,
  254. success: function (stream) {
  255. console.log('取消订阅流成功', stream.id());
  256. },
  257. fail: function (str) {
  258. console.log(str);
  259. }
  260. });
  261. });
  262. /**
  263. * 排麦监听事件
  264. */
  265. // 监听自己被邀请事件
  266. rtc.on('inviteUp', uid => {
  267. console.log('监听自己被邀请事件', uid);
  268. rtc.inviteAccept({
  269. success: function (str) {
  270. console.log('接受邀请成功', str);
  271. },
  272. fail: function (data) {
  273. console.log('接受邀请失败', data);
  274. }
  275. });
  276. });
  277. rtc.on('mcDown', () => {
  278. closeVideo('main');
  279. vue.connection = false;
  280. });
  281. rtc.on('createLocalStream', () => {
  282. // 创建本地流推流
  283. GetLiveRoomInfo({ task_id: vue.task_id }).then(({ video_mode }) => {
  284. console.log('创建本地流推流');
  285. const createData = {
  286. video: video_mode === 1,
  287. audio: true
  288. };
  289. rtc.createLocalStream({
  290. streamName: 'main',
  291. createData,
  292. success: function (stream) {
  293. vue.connection = true;
  294. console.log('创建本地流成功', stream);
  295. // 创建本地流成功,将流展示到id为 student 的dom元素盒子中
  296. stream.show('student');
  297. publishStream('main'); // 如果需要立即推流,执行 publish 方法
  298. },
  299. fail: function (data) {
  300. console.log('创建本地流失败,应用层处理', data);
  301. // 创建本地流失败,应用层处理
  302. Message({
  303. type: 'error',
  304. message: '创建本地流失败:' + data
  305. });
  306. }
  307. });
  308. });
  309. });
  310. /**
  311. * 监听聊天事件
  312. */
  313. rtc.on('chat_message', data => {
  314. let dat = JSON.parse(data);
  315. console.log(dat);
  316. // 敏感词过滤:如果发送的聊天消息被系统判定包含敏感词,则只有发送者能收到本条消息,房间内其他人都不会收到这条聊天消息。
  317. // 如果返回消息中有 isFilterChat 字段(消息不包含敏感词返回数据中无isFilterChat字段),且isFilterChat的值为1,则说明该消息包含敏感字,除发送者外其他人不会收到这条消息。
  318. if (dat.isFilterChat && dat.isFilterChat === 1) {
  319. return;
  320. }
  321. vue.chatList.push(dat);
  322. });
  323. rtc.on('allowChatChange', function (data) {
  324. let msg = data.settings.allow_chat ? '开言' : '禁言';
  325. Message({
  326. type: 'success',
  327. message: `全体${msg}成功`
  328. });
  329. });
  330. }
  331. /**
  332. * @method stopLive
  333. * @description 结束直播
  334. */
  335. export function stopLive() {
  336. rtc.stopLive({
  337. success() {
  338. Message({
  339. type: 'success',
  340. message: '直播已结束'
  341. });
  342. },
  343. fail(data) {
  344. Message({
  345. type: 'error',
  346. message: '结束直播失败:' + JSON.stringify(data)
  347. });
  348. }
  349. });
  350. }
  351. /**
  352. * @method pauseAudio
  353. * @description 关闭本地流声音
  354. */
  355. export function pauseAudio() {
  356. rtc.pauseAudio({
  357. streamName: 'main',
  358. success: function () {
  359. console.log('关闭本地流声音成功');
  360. },
  361. fail: function (str) {
  362. console.log(str);
  363. }
  364. });
  365. }
  366. /**
  367. * @method playAudio
  368. * @description 开启本地流声音
  369. */
  370. export function playAudio() {
  371. rtc.playAudio({
  372. streamName: 'main',
  373. success: function () {
  374. console.log('开启本地流声音成功');
  375. },
  376. fail: function (str) {
  377. console.log(str);
  378. }
  379. });
  380. }
  381. /**
  382. * @method pauseVideo
  383. * @description 关闭本地流视频画面
  384. */
  385. export function pauseVideo() {
  386. rtc.pauseVideo({
  387. streamName: 'main',
  388. success: function () {
  389. console.log('关闭本地流视频画面成功');
  390. },
  391. fail: function (str) {
  392. console.log(str);
  393. }
  394. });
  395. }
  396. /**
  397. * @method playVideo
  398. * @description 开启本地流视频画面
  399. */
  400. export function playVideo() {
  401. rtc.playVideo({
  402. streamName: 'main',
  403. success: function () {
  404. console.log('开启本地流视频画面成功');
  405. },
  406. fail: function (str) {
  407. console.log(str);
  408. }
  409. });
  410. }
  411. /**
  412. * @description 推送桌面共享
  413. */
  414. export function publishShareStream() {
  415. rtc.publishShareStream({
  416. success: function (stream) {
  417. console.log('推送桌面共享成功', stream);
  418. },
  419. fail: function (str) {
  420. console.log(str);
  421. }
  422. });
  423. }
  424. /**
  425. * 关闭桌面共享
  426. */
  427. export function unPubShareStream() {
  428. rtc.unPubShareStream();
  429. }
  430. /**
  431. * @description 开启、结束、暂停、恢复录制
  432. * @param { String } status: 'start' 开启, 'end' 结束, 'pause' 暂停, 'resume' 恢复
  433. */
  434. export function liveRecord(status) {
  435. rtc.liveRecord({
  436. status: status,
  437. success: function (data) {
  438. console.log('成功', data);
  439. },
  440. fail: function (str) {
  441. console.log(str);
  442. }
  443. });
  444. }
  445. // 排麦
  446. /**
  447. * @description 老师端发起邀请,邀请学生上麦。(举手模式)
  448. * @param {String} uid 被邀请用户id
  449. */
  450. export function invite(uid) {
  451. rtc.invite({
  452. uid: uid,
  453. success: function (str) {
  454. console.log('邀请上麦成功', str);
  455. },
  456. fail: function (data) {
  457. console.log(data);
  458. Message({
  459. type: 'error',
  460. message: `邀请上麦失败:${data.errorMsg}`
  461. });
  462. }
  463. });
  464. }
  465. /**
  466. * @method handsUp
  467. * @description 申请连麦
  468. */
  469. export function handsUp(data) {
  470. rtc.handsUp(data);
  471. }
  472. /**
  473. * 下麦操作
  474. * @param { String } uid
  475. */
  476. export function handsDown(uid) {
  477. rtc.handsDown({
  478. uid,
  479. success: function (str) {
  480. console.log('下麦成功', str);
  481. },
  482. fail: function (data) {
  483. console.log('下麦失败', data);
  484. }
  485. });
  486. }
  487. /**
  488. * @description 学生端接受老师的上麦邀请,同意上麦
  489. */
  490. export function inviteAccept() {
  491. rtc.inviteAccept({
  492. success: function (str) {
  493. console.log('接受邀请成功', str);
  494. },
  495. fail: function (data) {
  496. console.log('接受邀请失败', data);
  497. }
  498. });
  499. }
  500. // 聊天组件
  501. /**
  502. * @descrption 发送聊天
  503. * @param {String} msg length不能超过400
  504. */
  505. export function sendMsg(msg) {
  506. rtc.sendMsg(msg);
  507. }
  508. /**
  509. * @description 房间配置项更新
  510. * @param {Object} option 房间配置项 (具体看2.0 https://doc.bokecc.com/class/developer/web/chat.html),以键值对的形式传
  511. */
  512. export function roomUpdate(option) {
  513. rtc.roomUpdate(option);
  514. }
  515. // 文档
  516. export function drawChange(action, value) {
  517. rtc.drawChange({
  518. action,
  519. value
  520. });
  521. }