|
@@ -0,0 +1,376 @@
|
|
|
+// components/agora/agora.ts
|
|
|
+// import * as AgoraMiniappSDK from '../../utils/Agora_Miniapp_SDK_for_WeChat.js'
|
|
|
+import * as AgoraMiniappSDK from "agora-miniapp-sdk";
|
|
|
+import { ComponentWithComputed, behavior } from 'miniprogram-computed'
|
|
|
+
|
|
|
+interface ConfigPropType {
|
|
|
+ sdkAppId: string
|
|
|
+ token: string
|
|
|
+ chanelName: string
|
|
|
+ userId: string
|
|
|
+}
|
|
|
+type ConfigProp = {
|
|
|
+ type: ObjectConstructor,
|
|
|
+ value: ConfigPropType
|
|
|
+}
|
|
|
+
|
|
|
+ComponentWithComputed({
|
|
|
+ /**
|
|
|
+ * 组件的属性列表
|
|
|
+ */
|
|
|
+ behaviors: [behavior],
|
|
|
+ properties: {
|
|
|
+ config: <ConfigProp>{
|
|
|
+ type: Object,
|
|
|
+ value: {}
|
|
|
+ },
|
|
|
+ muted: {
|
|
|
+ type: Boolean,
|
|
|
+ value: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ 'config': function (obj) {
|
|
|
+ console.error('watch', obj)
|
|
|
+ if (obj.sdkAppId.length > 0) {
|
|
|
+ this.setData({
|
|
|
+ config: obj
|
|
|
+ }, () => {
|
|
|
+ this.initRTC();
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 'muted': function (val) {
|
|
|
+ console.error('watch-muted', val)
|
|
|
+ this.onMute(val);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 组件的初始数据
|
|
|
+ */
|
|
|
+ data: {
|
|
|
+ hostRtmpAdress: "",
|
|
|
+ client: {} as AgoraMiniappSDK.Client,
|
|
|
+ config: {} as ConfigPropType,
|
|
|
+ isInit: false,
|
|
|
+ muted: false,
|
|
|
+ debug: false,
|
|
|
+ uid: "", //当前Uid
|
|
|
+ media: [] as any[],
|
|
|
+ },
|
|
|
+ lifetimes: {
|
|
|
+ attached(): void {
|
|
|
+ console.log('attached agrora');
|
|
|
+ },
|
|
|
+ detached(): void {
|
|
|
+ console.log('detached agrora');
|
|
|
+ if (this.data.client) {
|
|
|
+ this.data.client.leave();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 组件的方法列表
|
|
|
+ */
|
|
|
+ methods: {
|
|
|
+ setDataAsync(data: any): Promise<void> {
|
|
|
+ return new Promise(resolve => void this.setData(data, resolve))
|
|
|
+ },
|
|
|
+ async initRTC() {
|
|
|
+ try {
|
|
|
+ if (!this.data.isInit) {
|
|
|
+ this.data.isInit = true
|
|
|
+ //@ts-ignore
|
|
|
+ let client: AgoraMiniappSDK.Client = new AgoraMiniappSDK.Client();
|
|
|
+
|
|
|
+ // this.data.client = client
|
|
|
+ //@ts-ignore
|
|
|
+ AgoraMiniappSDK.LOG.setLogLevel(-1);
|
|
|
+ await this.setDataAsync({ client: client });
|
|
|
+ this.subscribeEvents();
|
|
|
+ await client.init(this.data.config.sdkAppId);
|
|
|
+ console.warn('join--config', this.data.config);
|
|
|
+ await client.setRole('broadcaster')
|
|
|
+ const uid = await client.join(this.data.config.token, this.data.config.chanelName, this.data.config.userId, true, 1);
|
|
|
+ let url = await client.publish();
|
|
|
+ console.log('url', url)
|
|
|
+ this.setData({
|
|
|
+ uid: uid
|
|
|
+ });
|
|
|
+ let ts = new Date().getTime();
|
|
|
+ this.data.uid = uid;
|
|
|
+ this.addMedia(0, this.data.uid, url, {
|
|
|
+ key: ts
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('initRTC-error', error)
|
|
|
+ let code = error.code || 0;
|
|
|
+ if (code == 903 || code == 904 || code === 501 || code === 431) {
|
|
|
+ this.reconnect();
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ async reconnect() {
|
|
|
+ await this.setDataAsync({ isInit: false })
|
|
|
+ console.warn('reconnect');
|
|
|
+ if (this.data.client) {
|
|
|
+ this.data.client.destroy();
|
|
|
+ // this.data.client.
|
|
|
+ }
|
|
|
+ this.triggerEvent('reconnect');
|
|
|
+ // await this.initRTC();
|
|
|
+ },
|
|
|
+
|
|
|
+ async onMute(muted: boolean) {
|
|
|
+ if (muted) {
|
|
|
+ this.data.isInit && await this.data.client.muteLocal('audio')
|
|
|
+ } else {
|
|
|
+ this.data.isInit && await this.data.client?.unmuteLocal('audio')
|
|
|
+ }
|
|
|
+ this.setData({
|
|
|
+ muted: muted
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * return player component via uid
|
|
|
+ */
|
|
|
+ getPlayerComponent(uid: string | number) {
|
|
|
+ const agoraPlayer = this.selectComponent(`#rtc-player-${uid}`);
|
|
|
+ return agoraPlayer;
|
|
|
+ },
|
|
|
+
|
|
|
+ onPusherNetstatus: function (e: any) {
|
|
|
+ this.data.client.updatePusherNetStatus(e.detail);
|
|
|
+ },
|
|
|
+
|
|
|
+ onPusherStatechange: function (e: any) {
|
|
|
+ this.data.client.updatePusherStateChange(e.detail);
|
|
|
+ },
|
|
|
+ addMedia(mediaType: number, uid: string | number, url: string, options: any) {
|
|
|
+ console.log(`add media ${mediaType} ${uid} ${url}`);
|
|
|
+ let media = this.data.media || [];
|
|
|
+
|
|
|
+ if (mediaType === 0) {
|
|
|
+ //pusher
|
|
|
+ media.splice(0, 0, {
|
|
|
+ key: options.key,
|
|
|
+ type: mediaType,
|
|
|
+ uid: `${uid}`,
|
|
|
+ holding: false,
|
|
|
+ url: url,
|
|
|
+ left: 0,
|
|
|
+ top: 0,
|
|
|
+ width: 0,
|
|
|
+ height: 0
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ //player
|
|
|
+ media.push({
|
|
|
+ key: options.key,
|
|
|
+ rotation: options.rotation,
|
|
|
+ type: mediaType,
|
|
|
+ uid: `${uid}`,
|
|
|
+ holding: false,
|
|
|
+ url: url,
|
|
|
+ left: 0,
|
|
|
+ top: 0,
|
|
|
+ width: 0,
|
|
|
+ height: 0
|
|
|
+ });
|
|
|
+ }
|
|
|
+ this.refreshMedia(media);
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * update media object
|
|
|
+ * the media component will be fully refreshed if you try to update key
|
|
|
+ * property.
|
|
|
+ */
|
|
|
+ refreshMedia(media: any[]): Promise<void> {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ console.log(`updating media: ${JSON.stringify(media)}`);
|
|
|
+ this.setData({
|
|
|
+ media: media
|
|
|
+ }, () => {
|
|
|
+ resolve();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ updateMedia(uid: string | number, options: any) {
|
|
|
+ console.log(`update media ${uid} ${JSON.stringify(options)}`);
|
|
|
+ let media = this.data.media || [];
|
|
|
+ let changed = false;
|
|
|
+ for (let i = 0; i < media.length; i++) {
|
|
|
+ let item = media[i];
|
|
|
+ if (`${item.uid}` === `${uid}`) {
|
|
|
+ media[i] = Object.assign(item, options);
|
|
|
+ changed = true;
|
|
|
+ console.log(`after update media ${uid} ${JSON.stringify(item)}`)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onPlayerNetstatus: function (e: any) {
|
|
|
+ // 遍历所有远端流进行数据上报
|
|
|
+ let allPlayerStream = this.data.media.filter(m => m.uid !== this.data.uid);
|
|
|
+ allPlayerStream.forEach((item: any) => {
|
|
|
+ this.data.client.updatePlayerNetStatus(item.uid, e.detail);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onPlayerStatechange: function (e) {
|
|
|
+ let allPlayerStream = this.data.media.filter(m => m.uid !== this.data.uid);
|
|
|
+
|
|
|
+ // 这里 需要去获取所有远端流的 uid
|
|
|
+ allPlayerStream.forEach((item: any) => {
|
|
|
+ this.data.client.updatePlayerStateChange(item.uid, e.detail);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * remove media from view
|
|
|
+ */
|
|
|
+ removeMedia(uid: string | number) {
|
|
|
+ console.log(`remove media ${uid}`);
|
|
|
+ let media = this.data.media || [];
|
|
|
+ media = media.filter((item: any) => {
|
|
|
+ return `${item.uid}` !== `${uid}`
|
|
|
+ });
|
|
|
+
|
|
|
+ if (media.length !== this.data.media.length) {
|
|
|
+
|
|
|
+ this.refreshMedia(media);
|
|
|
+ } else {
|
|
|
+ console.log(`media not changed: ${JSON.stringify(media)}`)
|
|
|
+ // return Promise.resolve();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册stream事件
|
|
|
+ */
|
|
|
+ subscribeEvents() {
|
|
|
+ /**
|
|
|
+ * sometimes the video could be rotated
|
|
|
+ * this event will be fired with ratotion
|
|
|
+ * angle so that we can rotate the video
|
|
|
+ * NOTE video only supportes vertical or horizontal
|
|
|
+ * in case of 270 degrees, the video could be
|
|
|
+ * up side down
|
|
|
+ */
|
|
|
+
|
|
|
+ // this.data.client.on("video-rotation", (e) => {
|
|
|
+ // console.log(`video rotated: ${e.rotation} ${e.uid}`)
|
|
|
+ // setTimeout(() => {
|
|
|
+ // const player = this.getPlayerComponent(e.uid);
|
|
|
+ // player && player.rotate(e.rotation);
|
|
|
+ // }, 1000);
|
|
|
+ // });
|
|
|
+ /**
|
|
|
+ * fired when new stream join the channel
|
|
|
+ */
|
|
|
+
|
|
|
+ this.data.client.on("stream-added", async (e) => {
|
|
|
+ let uid = e.uid;
|
|
|
+ const ts = new Date().getTime();
|
|
|
+ console.warn(`stream ${uid} added `);
|
|
|
+ /**
|
|
|
+ * subscribe to get corresponding url
|
|
|
+ */
|
|
|
+ const { url, rotation } = await this.data.client.subscribe(uid);
|
|
|
+
|
|
|
+ let media = this.data.media || [];
|
|
|
+ let matchItem = null;
|
|
|
+ for (let i = 0; i < media.length; i++) {
|
|
|
+ let item = this.data.media[i];
|
|
|
+ if (`${item.uid}` === `${uid}`) {
|
|
|
+ //if existing, record this as matchItem and break
|
|
|
+ matchItem = item;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!matchItem) {
|
|
|
+ //if not existing, add new media
|
|
|
+ this.addMedia(1, uid, url, {
|
|
|
+ key: ts,
|
|
|
+ rotation: rotation
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // if existing, update property
|
|
|
+ // change key property to refresh live-player
|
|
|
+ this.updateMedia(matchItem.uid, {
|
|
|
+ url: url,
|
|
|
+ key: ts,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * remove stream when it leaves the channel
|
|
|
+ */
|
|
|
+ this.data.client.on("stream-removed", e => {
|
|
|
+ let uid = e.uid;
|
|
|
+ console.warn(`stream ${uid} removed`);
|
|
|
+ this.removeMedia(uid);
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * when bad thing happens - we recommend you to do a
|
|
|
+ * full reconnect when meeting such error
|
|
|
+ * it's also recommended to wait for few seconds before
|
|
|
+ * reconnect attempt
|
|
|
+ */
|
|
|
+ this.data.client.on("error", err => {
|
|
|
+ let errObj = err || {};
|
|
|
+ let code = errObj.code || 0;
|
|
|
+ let reason = errObj.reason || "";
|
|
|
+ console.warn(`error: ${code}, reason: ${reason}`);
|
|
|
+ wx.showToast({
|
|
|
+ title: `argora有错误,code:${code}`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 5000
|
|
|
+ });
|
|
|
+ if (code === 501 || code === 904) {
|
|
|
+ this.reconnect();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * there are cases when server require you to update
|
|
|
+ * player url, when receiving such event, update url into
|
|
|
+ * corresponding live-player, REMEMBER to update key property
|
|
|
+ * so that live-player is properly refreshed
|
|
|
+ * NOTE you can ignore such event if it's for pusher or happens before
|
|
|
+ * stream-added
|
|
|
+ */
|
|
|
+ this.data.client.on('update-url', e => {
|
|
|
+ console.log(`update-url: ${JSON.stringify(e)}`);
|
|
|
+ let uid = e.uid;
|
|
|
+ let url = e.url;
|
|
|
+ let ts = new Date().getTime();
|
|
|
+ if (`${uid}` === `${this.data.uid}`) {
|
|
|
+ // if it's not pusher url, update
|
|
|
+ console.log(`ignore update-url`);
|
|
|
+ } else {
|
|
|
+ this.updateMedia(uid, {
|
|
|
+ url: url,
|
|
|
+ key: ts,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.data.client.on("token-privilege-will-expire", () => {
|
|
|
+ console.warn("当前 token 即将过期,请更新 token");
|
|
|
+ // this.data.client.renewToken();
|
|
|
+ this.reconnect();
|
|
|
+ });
|
|
|
+
|
|
|
+ this.data.client.on("token-privilege-did-expire", () => {
|
|
|
+ console.warn("当前 token 已过期,请更新 token 并重新加入频道");
|
|
|
+ this.reconnect();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|