agora.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. // components/agora/agora.ts
  2. // import * as AgoraMiniappSDK from '../../utils/Agora_Miniapp_SDK_for_WeChat.js'
  3. import * as AgoraMiniappSDK from "agora-miniapp-sdk";
  4. import { ComponentWithComputed, behavior } from 'miniprogram-computed'
  5. interface ConfigPropType {
  6. sdkAppId: string
  7. token: string
  8. chanelName: string
  9. userId: string
  10. rtcSDKInit: boolean
  11. }
  12. type ConfigProp = {
  13. type: ObjectConstructor,
  14. value: ConfigPropType
  15. }
  16. ComponentWithComputed({
  17. /**
  18. * 组件的属性列表
  19. */
  20. behaviors: [behavior],
  21. properties: {
  22. config: <ConfigProp>{
  23. type: Object,
  24. value: {}
  25. },
  26. init: {
  27. type: Boolean,
  28. value: false
  29. },
  30. muted: {
  31. type: Boolean,
  32. value: false
  33. }
  34. },
  35. watch: {
  36. 'config.**': function (obj) {
  37. console.error('watch-config', obj)
  38. if (obj.sdkAppId.length > 0) {
  39. this.setData({
  40. config: obj
  41. }, () => {
  42. if (obj.rtcSDKInit) {
  43. this.initRTC();
  44. }
  45. })
  46. }
  47. },
  48. 'muted': function (val) {
  49. console.error('watch-muted', val)
  50. this.onMute(val);
  51. }
  52. },
  53. /**
  54. * 组件的初始数据
  55. */
  56. data: {
  57. hostRtmpAdress: "",
  58. client: {} as AgoraMiniappSDK.Client,
  59. config: {} as ConfigPropType,
  60. isInit: false,
  61. muted: false,
  62. debug: false,
  63. uid: "", //当前Uid
  64. media: [] as any[],
  65. },
  66. lifetimes: {
  67. attached(): void {
  68. console.log('attached agrora');
  69. },
  70. detached(): void {
  71. console.log('detached agrora');
  72. if (this.data.client) {
  73. this.data.uid = ''
  74. if ('leave' in this.data.client) {
  75. this.data.client.leave();
  76. }
  77. }
  78. }
  79. },
  80. /**
  81. * 组件的方法列表
  82. */
  83. methods: {
  84. setDataAsync(data: any): Promise<void> {
  85. return new Promise(resolve => void this.setData(data, resolve))
  86. },
  87. async initRTC() {
  88. try {
  89. if (!this.data.isInit) {
  90. this.data.isInit = true
  91. //@ts-ignore
  92. let client: AgoraMiniappSDK.Client = new AgoraMiniappSDK.Client();
  93. // this.data.client = client
  94. //@ts-ignore
  95. AgoraMiniappSDK.LOG.setLogLevel(-1);
  96. await this.setDataAsync({ client: client });
  97. this.subscribeEvents();
  98. await client.init(this.data.config.sdkAppId);
  99. console.warn('join--config', this.data.config);
  100. await client.setRole('broadcaster')
  101. let uid
  102. if (!this.data.isReconnect) {
  103. uid = await client.join(this.data.config.token, this.data.config.chanelName, this.data.config.userId, true, 1);
  104. } else {
  105. uid = await client.rejoin(this.data.config.token, this.data.config.chanelName, this.data.config.userId, [this.data.config.userId], true, 1);
  106. }
  107. let url = await client.publish();
  108. console.log('url', url)
  109. this.setData({
  110. uid: uid,
  111. isReconnect: false
  112. });
  113. let ts = new Date().getTime();
  114. this.data.uid = uid;
  115. this.addMedia(0, this.data.uid, url, {
  116. key: ts
  117. });
  118. }
  119. } catch (error) {
  120. console.error('initRTC-error', error)
  121. let code = error.code || 0;
  122. if (code == 903 || code == 904 || code === 501 || code === 431) {
  123. this.reconnect();
  124. }
  125. }
  126. },
  127. async reconnect() {
  128. await this.setDataAsync({ isInit: false })
  129. console.warn('reconnect');
  130. if (this.data.client) {
  131. this.data.client.destroy();
  132. }
  133. await this.setDataAsync({ isReconnect: true });
  134. this.triggerEvent('reconnect');
  135. // await this.initRTC();
  136. },
  137. async onMute(muted: boolean) {
  138. if (muted) {
  139. this.data.isInit && await this.data.client.muteLocal('audio')
  140. } else {
  141. this.data.isInit && await this.data.client?.unmuteLocal('audio')
  142. }
  143. this.setData({
  144. muted: muted
  145. })
  146. },
  147. /**
  148. * return player component via uid
  149. */
  150. getPlayerComponent(uid: string | number) {
  151. const agoraPlayer = this.selectComponent(`#rtc-player-${uid}`);
  152. return agoraPlayer;
  153. },
  154. onPusherNetstatus: function (e: any) {
  155. this.data.client.updatePusherNetStatus(e.detail);
  156. },
  157. onPusherStatechange: function (e: any) {
  158. this.data.client.updatePusherStateChange(e.detail);
  159. },
  160. addMedia(mediaType: number, uid: string | number, url: string, options: any) {
  161. console.log(`add media ${mediaType} ${uid} ${url}`);
  162. let media = this.data.media || [];
  163. if (mediaType === 0) {
  164. //pusher
  165. media.splice(0, 0, {
  166. key: options.key,
  167. type: mediaType,
  168. uid: `${uid}`,
  169. holding: false,
  170. url: url,
  171. left: 0,
  172. top: 0,
  173. width: 0,
  174. height: 0
  175. });
  176. } else {
  177. //player
  178. media.push({
  179. key: options.key,
  180. rotation: options.rotation,
  181. type: mediaType,
  182. uid: `${uid}`,
  183. holding: false,
  184. url: url,
  185. left: 0,
  186. top: 0,
  187. width: 0,
  188. height: 0
  189. });
  190. }
  191. this.refreshMedia(media);
  192. },
  193. /**
  194. * update media object
  195. * the media component will be fully refreshed if you try to update key
  196. * property.
  197. */
  198. refreshMedia(media: any[]): Promise<void> {
  199. return new Promise((resolve) => {
  200. console.log(`updating media: ${JSON.stringify(media)}`);
  201. this.setData({
  202. media: media
  203. }, () => {
  204. resolve();
  205. });
  206. });
  207. },
  208. updateMedia(uid: string | number, options: any) {
  209. console.log(`update media ${uid} ${JSON.stringify(options)}`);
  210. let media = this.data.media || [];
  211. let changed = false;
  212. for (let i = 0; i < media.length; i++) {
  213. let item = media[i];
  214. if (`${item.uid}` === `${uid}`) {
  215. media[i] = Object.assign(item, options);
  216. changed = true;
  217. console.log(`after update media ${uid} ${JSON.stringify(item)}`)
  218. break;
  219. }
  220. }
  221. },
  222. onPlayerNetstatus: function (e: any) {
  223. // 遍历所有远端流进行数据上报
  224. let allPlayerStream = this.data.media.filter(m => m.uid !== this.data.uid);
  225. allPlayerStream.forEach((item: any) => {
  226. this.data.client.updatePlayerNetStatus(item.uid, e.detail);
  227. });
  228. },
  229. onPlayerStatechange: function (e) {
  230. let allPlayerStream = this.data.media.filter(m => m.uid !== this.data.uid);
  231. // 这里 需要去获取所有远端流的 uid
  232. allPlayerStream.forEach((item: any) => {
  233. this.data.client.updatePlayerStateChange(item.uid, e.detail);
  234. });
  235. },
  236. /**
  237. * remove media from view
  238. */
  239. removeMedia(uid: string | number) {
  240. console.log(`remove media ${uid}`);
  241. let media = this.data.media || [];
  242. media = media.filter((item: any) => {
  243. return `${item.uid}` !== `${uid}`
  244. });
  245. if (media.length !== this.data.media.length) {
  246. this.refreshMedia(media);
  247. } else {
  248. console.log(`media not changed: ${JSON.stringify(media)}`)
  249. // return Promise.resolve();
  250. }
  251. },
  252. /**
  253. * 注册stream事件
  254. */
  255. subscribeEvents() {
  256. /**
  257. * sometimes the video could be rotated
  258. * this event will be fired with ratotion
  259. * angle so that we can rotate the video
  260. * NOTE video only supportes vertical or horizontal
  261. * in case of 270 degrees, the video could be
  262. * up side down
  263. */
  264. // this.data.client.on("video-rotation", (e) => {
  265. // console.log(`video rotated: ${e.rotation} ${e.uid}`)
  266. // setTimeout(() => {
  267. // const player = this.getPlayerComponent(e.uid);
  268. // player && player.rotate(e.rotation);
  269. // }, 1000);
  270. // });
  271. /**
  272. * fired when new stream join the channel
  273. */
  274. this.data.client.on("stream-added", async (e) => {
  275. let uid = e.uid;
  276. const ts = new Date().getTime();
  277. console.warn(`stream ${uid} added `);
  278. /**
  279. * subscribe to get corresponding url
  280. */
  281. const { url, rotation } = await this.data.client.subscribe(uid);
  282. let media = this.data.media || [];
  283. let matchItem = null;
  284. for (let i = 0; i < media.length; i++) {
  285. let item = this.data.media[i];
  286. if (`${item.uid}` === `${uid}`) {
  287. //if existing, record this as matchItem and break
  288. matchItem = item;
  289. break;
  290. }
  291. }
  292. if (!matchItem) {
  293. //if not existing, add new media
  294. this.addMedia(1, uid, url, {
  295. key: ts,
  296. rotation: rotation
  297. })
  298. } else {
  299. // if existing, update property
  300. // change key property to refresh live-player
  301. this.updateMedia(matchItem.uid, {
  302. url: url,
  303. key: ts,
  304. });
  305. }
  306. });
  307. /**
  308. * remove stream when it leaves the channel
  309. */
  310. this.data.client.on("stream-removed", e => {
  311. let uid = e.uid;
  312. console.warn(`stream ${uid} removed`);
  313. this.removeMedia(uid);
  314. });
  315. /**
  316. * when bad thing happens - we recommend you to do a
  317. * full reconnect when meeting such error
  318. * it's also recommended to wait for few seconds before
  319. * reconnect attempt
  320. */
  321. this.data.client.on("error", err => {
  322. let errObj = err || {};
  323. let code = errObj.code || 0;
  324. let reason = errObj.reason || "";
  325. console.warn(`error: ${code}, reason: ${reason}`);
  326. // wx.showToast({
  327. // title: `argora有错误,code:${code}`,
  328. // icon: 'none',
  329. // duration: 5000
  330. // });
  331. if (code === 501 || code === 904) {
  332. this.reconnect();
  333. }
  334. });
  335. /**
  336. * there are cases when server require you to update
  337. * player url, when receiving such event, update url into
  338. * corresponding live-player, REMEMBER to update key property
  339. * so that live-player is properly refreshed
  340. * NOTE you can ignore such event if it's for pusher or happens before
  341. * stream-added
  342. */
  343. this.data.client.on('update-url', e => {
  344. console.log(`update-url: ${JSON.stringify(e)}`);
  345. let uid = e.uid;
  346. let url = e.url;
  347. let ts = new Date().getTime();
  348. if (`${uid}` === `${this.data.uid}`) {
  349. // if it's not pusher url, update
  350. console.log(`ignore update-url`);
  351. } else {
  352. this.updateMedia(uid, {
  353. url: url,
  354. key: ts,
  355. });
  356. }
  357. });
  358. this.data.client.on("token-privilege-will-expire", () => {
  359. console.warn("当前 token 即将过期,请更新 token");
  360. // this.data.client.renewToken();
  361. this.reconnect();
  362. });
  363. this.data.client.on("token-privilege-did-expire", () => {
  364. console.warn("当前 token 已过期,请更新 token 并重新加入频道");
  365. this.reconnect();
  366. });
  367. }
  368. }
  369. })