agora.ts 10 KB

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