国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

技術(shù)分享| 小程序?qū)崿F(xiàn)音視頻通話

這篇具有很好參考價(jià)值的文章主要介紹了技術(shù)分享| 小程序?qū)崿F(xiàn)音視頻通話。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

上一期我們把前期準(zhǔn)備工作做完了,這一期就帶大家實(shí)現(xiàn)音視頻通話!

sdk 二次封裝

為了更好的區(qū)分功能,我分成了六個(gè) js 文件

  • config.js 音視頻與呼叫邀請(qǐng)配置

  • store.js 實(shí)現(xiàn)音視頻通話的變量

  • rtc.js 音視頻邏輯封裝

  • live-code.js 微信推拉流狀態(tài)碼

  • rtm.js 呼叫邀請(qǐng)相關(guān)邏輯封裝

  • util.js 其他方法

config.js

配置 sdk 所需的 AppId,如需私有云可在此配置

  • RTC 音視頻相關(guān)

  • RTM 實(shí)時(shí)消息(呼叫邀請(qǐng))

    module.exports = {
    AppId: "",
    // RTC 私有云配置
      RTC_setParameters: {
        setParameters: {
        //   //配置私有云網(wǎng)關(guān)
        //   ConfPriCloudAddr: {
        //     ServerAdd: "",
        //     Port: ,
        //     Wss: true,
        //   },
        },
      },
    // RTM 私有云配置
    RTM_setParameters: {
        setParameters: {
            // //配置內(nèi)網(wǎng)網(wǎng)關(guān)
            // confPriCloudAddr: {
            //     ServerAdd: "",
            //     Port: ,
            //     Wss: true,
            // },
        },
    },
    }
    

store.js

整個(gè)通話系統(tǒng)使用的變量設(shè)置

module.exports = {
// 網(wǎng)絡(luò)狀態(tài)
networkType: "",
// rtm連接狀態(tài)
rtmNetWorkType: "",
// rtc連接狀態(tài)
rtcNetWorkType: "",
// 視頻通話0 語音通話1
Mode: 0,
// 當(dāng)前場(chǎng)景 0:首頁 1:呼叫頁面 2:通信頁面
State: 0,

// 本地用戶uid
userId: "",
// 遠(yuǎn)端用戶uid
peerUserId: "",
// 頻道房間
channelId: "",

// RTM 客戶端
rtmClient: null,
// RTC 客戶端
rtcClient: null,

// 本地錄制地址(小程序特有推流)
livePusherUrl: "",
// 遠(yuǎn)端播放(小程序特有拉流)
livePlayerUrl: "",

// 主叫邀請(qǐng)實(shí)例
localInvitation: null,
// 被叫收到的邀請(qǐng)實(shí)例
remoteInvitation: null,

// 是否正在通話
Calling: false,
// 是否是單人通話
Conference: false,

// 通話計(jì)時(shí)
callTime: 0,
callTimer: null,

// 30s 后無網(wǎng)絡(luò)取消通話
networkEndCall: null,
networkEndCallTime: 30*1000,

// 斷網(wǎng)發(fā)送查詢后檢測(cè)是否返回消息
networkSendInfoDetection: null,
networkSendInfoDetectionTime: 10*1000,
}

rtc.js

音視頻 sdk 二測(cè)封裝,方便調(diào)用

// 引入 RTC
const ArRTC = require("ar-rtc-miniapp");
// 引入 until
const Until = require("./util");
// 引入 store
let Store = require("./store");
// 引入 SDK 配置
const Config = require("./config");

// 初始化 RTC
const InItRTC = async () => {
    // 創(chuàng)建RTC客戶端 
    Store.rtcClient = new ArRTC.client();
    // 初始化
    await Store.rtcClient.init(Config.AppId);

    Config.RTC_setParameters.setParameters && await Store.rtcClient.setParameters(Config.RTC_setParameters.setParameters)
    // 已添加遠(yuǎn)端音視頻流
    Store.rtcClient.on('stream-added', rtcEvent.userPublished);
    // 已刪除遠(yuǎn)端音視頻流
    Store.rtcClient.on('stream-removed', rtcEvent.userUnpublished);
    // 通知應(yīng)用程序發(fā)生錯(cuò)誤
    Store.rtcClient.on('error', rtcEvent.error);
    // 更新 Url 地址
    Store.rtcClient.on('update-url', rtcEvent.updateUrl);
    // 遠(yuǎn)端視頻已旋轉(zhuǎn)
    Store.rtcClient.on('video-rotation', rtcEvent.videoRotation);
    // 遠(yuǎn)端用戶已停止發(fā)送音頻流
    Store.rtcClient.on('mute-audio', rtcEvent.muteAudio);
    // 遠(yuǎn)端用戶已停止發(fā)送視頻流
    Store.rtcClient.on('mute-video', rtcEvent.muteVideo);
    // 遠(yuǎn)端用戶已恢復(fù)發(fā)送音頻流
    Store.rtcClient.on('unmute-audio', rtcEvent.unmuteAudio);
    // 遠(yuǎn)端用戶已恢復(fù)發(fā)送視頻流
    Store.rtcClient.on('unmute-video', rtcEvent.unmuteAudio);
}

// RTC 監(jiān)聽事件處理
const rtcEvent = {
    // RTC SDK 監(jiān)聽用戶發(fā)布
    userPublished: ({
        uid
    }) => {
        console.log("RTC SDK 監(jiān)聽用戶發(fā)布", uid);
        Store.networkSendInfoDetection && clearTimeout(Store.networkSendInfoDetection);
        if (Store.Mode == 0) {
            wx.showLoading({
                title: '遠(yuǎn)端加載中',
                mask: true,
            })
        }

        // 訂閱遠(yuǎn)端用戶發(fā)布音視頻
        Store.rtcClient.subscribe(uid, (url) => {
            console.log("遠(yuǎn)端用戶發(fā)布音視頻", url);
            // 向視頻頁面發(fā)送遠(yuǎn)端拉流地址
            Until.emit("livePusherUrlEvent", {
                livePlayerUrl: url
            });
        }, (err) => {
            console.log("訂閱遠(yuǎn)端用戶發(fā)布音視頻失敗", err);
        })
    },
    // RTC SDK 監(jiān)聽用戶取消發(fā)布
    userUnpublished: ({
        uid
    }) => {
        console.log("RTC SDK 監(jiān)聽用戶取消發(fā)布", uid);
        Store.networkSendInfoDetection && clearTimeout(Store.networkSendInfoDetection);
        Store.networkSendInfoDetection = setTimeout(() => {
            wx.showToast({
                title: '對(duì)方網(wǎng)絡(luò)異常',
                icon: "error"
            });
            setTimeout(() => {
                rtcInternal.leaveChannel(false);
            }, 2000)

        }, Store.networkSendInfoDetectionTime);


    },
    // 更新 Url 地址
    updateUrl: ({
        uid,
        url
    }) => {
        console.log("包含遠(yuǎn)端用戶的 ID 和更新后的拉流地址", uid, url);
        // 向視頻頁面發(fā)送遠(yuǎn)端拉流地址
        Until.emit("livePusherUrlEvent", {
            livePlayerUrl: url
        });
    },
    // 視頻的旋轉(zhuǎn)信息以及遠(yuǎn)端用戶的 ID
    videoRotation: ({
        uid,
        rotation
    }) => {
        console.log("視頻的旋轉(zhuǎn)信息以及遠(yuǎn)端用戶的 ID", uid, rotation);
    },
    // 遠(yuǎn)端用戶已停止發(fā)送音頻流
    muteAudio: ({
        uid
    }) => {
        console.log("遠(yuǎn)端用戶已停止發(fā)送音頻流", uid);
    },
    // 遠(yuǎn)端用戶已停止發(fā)送視頻流
    muteVideo: ({
        uid
    }) => {
        console.log("遠(yuǎn)端用戶已停止發(fā)送視頻流", uid);
    },
    // 遠(yuǎn)端用戶已恢復(fù)發(fā)送音頻流
    unmuteAudio: ({
        uid
    }) => {
        console.log("遠(yuǎn)端用戶已恢復(fù)發(fā)送音頻流", uid);
    },
    // 遠(yuǎn)端用戶已恢復(fù)發(fā)送視頻流
    unmuteAudio: ({
        uid
    }) => {
        console.log("遠(yuǎn)端用戶已恢復(fù)發(fā)送視頻流", uid);
    },
    // 通知應(yīng)用程序發(fā)生錯(cuò)誤。 該回調(diào)中會(huì)包含詳細(xì)的錯(cuò)誤碼和錯(cuò)誤信息
    error: ({
        code,
        reason
    }) => {
        console.log("錯(cuò)誤碼:" + code, "錯(cuò)誤信息:" + reason);
    },
}

// RTC 內(nèi)部邏輯
const rtcInternal = {
    // 加入頻道
    joinChannel: () => {
        Store.rtcClient.join(undefined, Store.channelId, Store.userId, () => {
            console.log("加入頻道成功", Store.rtcClient);
            // 發(fā)布視頻
            rtcInternal.publishTrack();
            // 加入房間一定時(shí)間內(nèi)無人加入
            Store.networkSendInfoDetection && clearTimeout(Store.networkSendInfoDetection);
            Store.networkSendInfoDetection = setTimeout(() => {
                wx.showToast({
                    title: '對(duì)方網(wǎng)絡(luò)異常',
                    icon: "error"
                });
                setTimeout(() => {
                    rtcInternal.leaveChannel(false);
                }, 2000)

            }, Store.networkSendInfoDetectionTime);


        }, (err) => {
            console.log("加入頻道失敗");
        });
    },
    // 離開頻道
    leaveChannel: (sendfase = true) => {
        console.log("離開頻道", sendfase);
        console.log("RTC 離開頻道", Store);
        Store.networkSendInfoDetection && clearTimeout(Store.networkSendInfoDetection);
        if (Store.rtcClient) {
            // 引入 RTM
            const RTM = require("./rtm");
            Store.rtcClient.destroy(() => {
                console.log("離開頻道", RTM);
                if (sendfase) {
                    // 發(fā)送離開信息
                    RTM.rtmInternal.sendMessage(Store.peerUserId, {
                        Cmd: "EndCall",
                    })
                }
                Until.clearStore();
                // 返回首頁
                wx.reLaunch({
                    url: '../index/index',
                    success:function () {
                        wx.showToast({
                          title: '通話結(jié)束',
                          icon:'none'
                        })
                    }
                });
            }, (err) => {
                console.log("離開頻道失敗", err);
            })
        } else {
            Until.clearStore();
        }
    },
    // 發(fā)布本地音視頻
    publishTrack: () => {
        Store.rtcClient.publish((url) => {
            console.log("發(fā)布本地音視頻", url);
            // 本地錄制地址(小程序特有推流)
            Store.livePusherUrl = url;
            // 向視頻頁面發(fā)送本地推流地址
            Until.emit("livePusherUrlEvent", {
                livePusherUrl: url
            });
        }, ({
            code,
            reason
        }) => {
            console.log("發(fā)布本地音視頻失敗", code, reason);
        })
    },

    // 切換靜音
    switchAudio: (enableAudio = false) => {
        /**
         * muteLocal 停止發(fā)送本地用戶的音視頻流
         * unmuteLocal 恢復(fù)發(fā)送本地用戶的音視頻流
         */
        Store.rtcClient[enableAudio ? 'muteLocal' : 'unmuteLocal']('audio', () => {
            wx.showToast({
                title: enableAudio ? '關(guān)閉聲音' : '開啟聲音',
                icon: 'none',
                duration: 2000
            })
        }, ({
            code,
            reason
        }) => {
            console.log("發(fā)布本地音視頻失敗", code, reason);
        })
    },
}

module.exports = {
    InItRTC,
    rtcInternal,
}

live-code.js

微信推拉流狀態(tài)碼

module.exports = {
    1001: "已經(jīng)連接推流服務(wù)器",
    1002: "已經(jīng)與服務(wù)器握手完畢,開始推流",
    1003: "打開攝像頭成功",
    1004: "錄屏啟動(dòng)成功",
    1005: "推流動(dòng)態(tài)調(diào)整分辨率",
    1006: "推流動(dòng)態(tài)調(diào)整碼率",
    1007: "首幀畫面采集完成",
    1008: "編碼器啟動(dòng)",
    "-1301": "打開攝像頭失敗",
    "-1302": "打開麥克風(fēng)失敗",
    "-1303": "視頻編碼失敗",
    "-1304": "音頻編碼失敗",
    "-1305": "不支持的視頻分辨率",
    "-1306": "不支持的音頻采樣率",
    "-1307": "網(wǎng)絡(luò)斷連,且經(jīng)多次重連搶救無效,更多重試請(qǐng)自行重啟推流",
    "-1308": "開始錄屏失敗,可能是被用戶拒絕",
    "-1309": "錄屏失敗,不支持的Android系統(tǒng)版本,需要5.0以上的系統(tǒng)",
    "-1310": "錄屏被其他應(yīng)用打斷了",
    "-1311": "Android Mic打開成功,但是錄不到音頻數(shù)據(jù)",
    "-1312": "錄屏動(dòng)態(tài)切橫豎屏失敗",
    1101: "網(wǎng)絡(luò)狀況不佳:上行帶寬太小,上傳數(shù)據(jù)受阻",
    1102: "網(wǎng)絡(luò)斷連, 已啟動(dòng)自動(dòng)重連",
    1103: "硬編碼啟動(dòng)失敗,采用軟編碼",
    1104: "視頻編碼失敗",
    1105: "新美顏軟編碼啟動(dòng)失敗,采用老的軟編碼",
    1106: "新美顏軟編碼啟動(dòng)失敗,采用老的軟編碼",
    3001: "RTMP -DNS解析失敗",
    3002: "RTMP服務(wù)器連接失敗",
    3003: "RTMP服務(wù)器握手失敗",
    3004: "RTMP服務(wù)器主動(dòng)斷開,請(qǐng)檢查推流地址的合法性或防盜鏈有效期",
    3005: "RTMP 讀/寫失敗",
    2001: "已經(jīng)連接服務(wù)器",
    2002: "已經(jīng)連接 RTMP 服務(wù)器,開始拉流",
    2003: "網(wǎng)絡(luò)接收到首個(gè)視頻數(shù)據(jù)包(IDR)",
    2004: "視頻播放開始",
    2005: "視頻播放進(jìn)度",
    2006: "視頻播放結(jié)束",
    2007: "視頻播放Loading",
    2008: "解碼器啟動(dòng)",
    2009: "視頻分辨率改變",
    "-2301": "網(wǎng)絡(luò)斷連,且經(jīng)多次重連搶救無效,更多重試請(qǐng)自行重啟播放",
    "-2302": "獲取加速拉流地址失敗",
    2101: "當(dāng)前視頻幀解碼失敗",
    2102: "當(dāng)前音頻幀解碼失敗",
    2103: "網(wǎng)絡(luò)斷連, 已啟動(dòng)自動(dòng)重連",
    2104: "網(wǎng)絡(luò)來包不穩(wěn):可能是下行帶寬不足,或由于主播端出流不均勻",
    2105: "當(dāng)前視頻播放出現(xiàn)卡頓",
    2106: "硬解啟動(dòng)失敗,采用軟解",
    2107: "當(dāng)前視頻幀不連續(xù),可能丟幀",
    2108: "當(dāng)前流硬解第一個(gè)I幀失敗,SDK自動(dòng)切軟解",
};

rtm.js

實(shí)時(shí)消息(呼叫邀請(qǐng))二次封裝。使用 p2p 消息發(fā)送接受(信令收發(fā)),呼叫邀請(qǐng)

// 引入 anyRTM 
const ArRTM = require("ar-rtm-sdk");
// 引入 until
const Until = require("./util");
// 引入 store
let Store = require("./store");
// 引入 SDK 配置
const Config = require("../utils/config");
// 引入 RTC
const RTC = require("./rtc");

// 本地 uid 隨機(jī)生成
Store.userId = Until.generateNumber(4) + '';


// 監(jiān)聽網(wǎng)絡(luò)狀態(tài)變化事件
wx.onNetworkStatusChange(function (res) {
    // 網(wǎng)絡(luò)狀態(tài)
    Store.networkType = res.networkType
    // 無網(wǎng)絡(luò)
    if (res.networkType == 'none') {
        wx.showLoading({
            title: '網(wǎng)絡(luò)掉線了',
            mask: true
        });
        Store.rtmNetWorkType = "";
        // 30s 無網(wǎng)絡(luò)連接結(jié)束當(dāng)前呼叫
        Store.networkEndCall && clearTimeout(Store.networkEndCall);
        Store.networkEndCall = setTimeout(() => {
            rtmInternal.networkEndCall();
        }, Store.networkEndCallTime);

    } else {
        Store.networkEndCall && clearTimeout(Store.networkEndCall);
        wx.hideLoading();
        if (!Store.rtmClient) {
            // 初始化
            InItRtm();
        } else {
            if (!Store.rtcClient) {
                // 呼叫階段
                let oRtmSetInterval = setInterval(() => {
                    // rtm 鏈接狀態(tài)
                    if (Store.rtmNetWorkType == "CONNECTED") {
                        clearInterval(oRtmSetInterval);
                        Store.networkSendInfoDetection && clearTimeout(Store.networkSendInfoDetection);
                        // 發(fā)送信息,查看對(duì)方狀態(tài)
                        rtmInternal.sendMessage(Store.peerUserId, {
                            Cmd: "CallState",
                        });
                        // 發(fā)送無響應(yīng)
                        Store.networkSendInfoDetection = setTimeout(() => {
                            rtmInternal.networkEndCall();
                        }, Store.networkEndCallTime);
                    }
                }, 500)
            }

        }
    }
});

// 初始化
const InItRtm = async () => {
    // 創(chuàng)建 RTM 客戶端
    Store.rtmClient = await ArRTM.createInstance(Config.AppId);
    Config.RTM_setParameters.setParameters && await Store.rtmClient.setParameters(Config.RTM_setParameters.setParameters)
    // RTM 版本
    console.log("RTM 版本", ArRTM.VERSION);
    wx.showLoading({
        title: '登錄中',
        mask: true
    })
    // 登錄 RTM
    await Store.rtmClient.login({
        token: "",
        uid: Store.userId
    }).then(() => {
        wx.hideLoading();
        wx.showToast({
            title: '登錄成功',
            icon: 'success',
            duration: 2000
        })
        console.log("登錄成功");
    }).catch((err) => {
        Store.userId = "";
        wx.hideLoading();
        wx.showToast({
            icon: 'error',
            title: 'RTM 登錄失敗',
            mask: true,
            duration: 2000
        });
        console.log("RTM 登錄失敗", err);
    });

    // 監(jiān)聽收到來自主叫的呼叫邀請(qǐng)
    Store.rtmClient.on(
        "RemoteInvitationReceived",
        rtmEvent.RemoteInvitationReceived
    );
    // 監(jiān)聽收到來自對(duì)端的點(diǎn)對(duì)點(diǎn)消息
    Store.rtmClient.on("MessageFromPeer", rtmEvent.MessageFromPeer);
    // 通知 SDK 與 RTM 系統(tǒng)的連接狀態(tài)發(fā)生了改變
    Store.rtmClient.on(
        "ConnectionStateChanged",
        rtmEvent.ConnectionStateChanged
    );

}

// RTM 監(jiān)聽事件
const rtmEvent = {
    // 主叫:被叫已收到呼叫邀請(qǐng)
    localInvitationReceivedByPeer: () => {
        console.log("主叫:被叫已收到呼叫邀請(qǐng)");
        // 跳轉(zhuǎn)至呼叫頁面
        wx.reLaunch({
            url: '../pageinvite/pageinvite?call=0'
        });

        wx.showToast({
            title: '被叫已收到呼叫邀請(qǐng)',
            icon: 'none',
            duration: 2000,
            mask: true,
        });

    },
    // 主叫:被叫已接受呼叫邀請(qǐng)
    localInvitationAccepted: async (response) => {
        console.log("主叫:被叫已接受呼叫邀請(qǐng)", response);
        try {
            const oInfo = JSON.parse(response);
            // 更改通話方式
            Store.Mode = oInfo.Mode;
            wx.showToast({
                title: '呼叫邀請(qǐng)成功',
                icon: 'success',
                duration: 2000
            });
            // anyRTC 初始化
            await RTC.InItRTC();
            // 加入 RTC 頻道
            await RTC.rtcInternal.joinChannel();

            // 進(jìn)入通話頁面
            wx.reLaunch({
                url: '../pagecall/pagecall',
            });
        } catch (error) {
            console.log("主叫:被叫已接受呼叫邀請(qǐng) 數(shù)據(jù)解析失敗", response);
        }

    },
    // 主叫:被叫拒絕了你的呼叫邀請(qǐng)
    localInvitationRefused: (response) => {
        try {
            const oInfo = JSON.parse(response);
            // 不同意邀請(qǐng)后返回首頁
            rtmInternal.crosslightgoBack(oInfo.Cmd == "Calling" ? "用戶正在通話中" : "用戶拒絕邀請(qǐng)");
        } catch (error) {
            rtmInternal.crosslightgoBack("用戶拒絕邀請(qǐng)")
        }
    },
    // 主叫:呼叫邀請(qǐng)進(jìn)程失敗
    localInvitationFailure: (response) => {
        console.log("主叫:呼叫邀請(qǐng)進(jìn)程失敗", response);
        // rtmInternal.crosslightgoBack("呼叫邀請(qǐng)進(jìn)程失敗");
    },
    // 主叫:呼叫邀請(qǐng)已被成功取消 (主動(dòng)掛斷)
    localInvitationCanceled: () => {
        console.log("主叫:呼叫邀請(qǐng)已被成功取消 (主動(dòng)掛斷)");
        // 不同意邀請(qǐng)后返回首頁
        rtmInternal.crosslightgoBack("已取消呼叫");
    },

    // 被叫:監(jiān)聽收到來自主叫的呼叫邀請(qǐng)
    RemoteInvitationReceived: async (remoteInvitation) => {
        if (Store.Calling) {
            // 正在通話中處理
            rtmInternal.callIng(remoteInvitation);
        } else {
            wx.showLoading({
                title: '收到呼叫邀請(qǐng)',
                mask: true,
            })

            // 解析主叫呼叫信息
            const invitationContent = await JSON.parse(remoteInvitation.content);
            if (invitationContent.Conference) {
                setTimeout(() => {
                    wx.hideLoading();
                    wx.showToast({
                        title: '暫不支持多人通話(如需添加,請(qǐng)自行添加相關(guān)邏輯)',
                        icon: 'none',
                        duration: 3000,
                        mask: true,
                    })
                    // 暫不支持多人通話(如需添加,請(qǐng)自行添加相關(guān)邏輯)
                    remoteInvitation.refuse();
                }, 1500);
            } else {
                wx.hideLoading();
                Store = await Object.assign(Store, {
                    // 通話方式
                    Mode: invitationContent.Mode,
                    // 頻道房間
                    channelId: invitationContent.ChanId,
                    // 存放被叫實(shí)例
                    remoteInvitation,
                    // 遠(yuǎn)端用戶
                    peerUserId: remoteInvitation.callerId,
                    // 標(biāo)識(shí)為正在通話中
                    Calling: true,
                    // 是否是單人通話
                    Conference: invitationContent.Conference,
                })

                // 跳轉(zhuǎn)至呼叫頁面
                wx.reLaunch({
                    url: '../pageinvite/pageinvite?call=1'
                });

                // 收到呼叫邀請(qǐng)?zhí)幚?/span>
                rtmInternal.inviteProcessing(remoteInvitation);
            }
        }
    },
    // 被叫:監(jiān)聽接受呼叫邀請(qǐng)
    RemoteInvitationAccepted: async () => {
        console.log("被叫 接受呼叫邀請(qǐng)", Store);
        wx.showLoading({
            title: '接受邀請(qǐng)',
            mask: true,
        })
        // anyRTC 初始化
        await RTC.InItRTC();
        // 加入 RTC 頻道
        await RTC.rtcInternal.joinChannel();
        wx.hideLoading()
        // 進(jìn)入通話頁面
        wx.reLaunch({
            url: '../pagecall/pagecall',
        });
    },
    // 被叫:監(jiān)聽拒絕呼叫邀請(qǐng)
    RemoteInvitationRefused: () => {
        console.log("被叫 拒絕呼叫邀請(qǐng)");
        // 不同意邀請(qǐng)后返回首頁
        rtmInternal.crosslightgoBack("成功拒絕邀請(qǐng)");
    },
    // 被叫:監(jiān)聽主叫取消呼叫邀請(qǐng)
    RemoteInvitationCanceled: () => {
        console.log("被叫 取消呼叫邀請(qǐng)");
        // 不同意邀請(qǐng)后返回首頁
        rtmInternal.crosslightgoBack("主叫取消呼叫邀請(qǐng)");
    },
    // 被叫:監(jiān)聽呼叫邀請(qǐng)進(jìn)程失敗
    RemoteInvitationFailure: () => {
        console.log("被叫 呼叫邀請(qǐng)進(jìn)程失敗");
        // 不同意邀請(qǐng)后返回首頁
        rtmInternal.crosslightgoBack("呼叫邀請(qǐng)進(jìn)程失敗");
    },


    // 收到來自對(duì)端的點(diǎn)對(duì)點(diǎn)消息
    MessageFromPeer: (message, peerId) => {
        console.log("收到來自對(duì)端的點(diǎn)對(duì)點(diǎn)消息", message, peerId);
        message.text = JSON.parse(message.text);
        switch (message.text.Cmd) {
            case "SwitchAudio":
                // 視頻通話頁面轉(zhuǎn)語音
                Until.emit("callModeChange", {
                    mode: 1
                });
                break;
            case "EndCall":
                // 掛斷
                RTC.rtcInternal.leaveChannel(false);
                break;
            case "CallState":
                // 對(duì)方查詢本地狀態(tài),返回給對(duì)方信息
                rtmInternal.sendMessage(peerId, {
                    Cmd: "CallStateResult",
                    state: Store.peerUserId !== peerId ? 0 : Store.State,
                    Mode: Store.Mode,
                })
                break;
            case "CallStateResult":
                // 遠(yuǎn)端用戶返回信息處理
                console.log("本地?cái)嗑W(wǎng)重連后對(duì)方狀態(tài)", message, peerId);
                Store.networkSendInfoDetection && clearTimeout(Store.networkSendInfoDetection);
                if (message.text.state == 0 && Store.State != 0) {
                    // 遠(yuǎn)端停止通話,本地還在通話
                    rtmInternal.networkEndCall();
                } else if (message.text.state == 2) {
                    Store.Mode = message.text.Mode;
                    // 遠(yuǎn)端 rtc 通話
                    if (Store.State == 1) {
                        // 本地 rtm 呼叫中進(jìn)入RTC
                        console.log("本地 rtm 呼叫中進(jìn)入RTC",Store);
                    } else if (Store.State == 2) {
                        // 本地 rtc 通話
                        if (message.text.Mode == 1) {
                            // 轉(zhuǎn)語音通話
                            Until.emit("callModeChange", {
                                mode: 1
                            });
                        }
                    }
                }
                break;
            default:
                console.log("收到來自對(duì)端的點(diǎn)對(duì)點(diǎn)消息", message, peerId);
                break;
        }
    },
    // 通知 SDK 與 RTM 系統(tǒng)的連接狀態(tài)發(fā)生了改變
    ConnectionStateChanged: (newState, reason) => {
        console.log("系統(tǒng)的連接狀態(tài)發(fā)生了改變", newState);
        Store.rtmNetWorkType = newState;
        switch (newState) {
            case "CONNECTED":
                wx.hideLoading();
                //  SDK 已登錄 RTM 系統(tǒng)
                wx.showToast({
                    title: 'RTM 連接成功',
                    icon: 'success',
                    mask: true,
                })
                break;
            case "ABORTED":
                wx.showToast({
                    title: 'RTM 停止登錄',
                    icon: 'error',
                    mask: true,
                });
                console.log("RTM 停止登錄,重新登錄");

                break;
            default:
                wx.showLoading({
                    title: 'RTM 連接中',
                    mask: true,
                })
                break;
        }
    }
}

// RTM 內(nèi)部邏輯
const rtmInternal = {
    // 查詢呼叫用戶是否在線
    peerUserQuery: async (uid) => {
        const oUserStatus = await Store.rtmClient.queryPeersOnlineStatus([uid]);
        if (!oUserStatus[uid]) {
            wx.showToast({
                title: '用戶不在線',
                icon: 'error',
                duration: 2000,
                mask: true,
            });
            return false;
        }
        return true;
    },

    // 主叫發(fā)起呼叫
    inviteSend: async (callMode) => {
        Store = await Object.assign(Store, {
            // 隨機(jī)生成頻道
            channelId: '' + Until.generateNumber(9),
            // 正在通話中
            Calling: true,
            // 通話方式
            Mode: callMode,
            // 創(chuàng)建呼叫邀請(qǐng)
            localInvitation: Store.rtmClient.createLocalInvitation(
                Store.peerUserId
            )
        })

        // 設(shè)置邀請(qǐng)內(nèi)容
        Store.localInvitation.content = JSON.stringify({
            Mode: callMode, // 呼叫類型 視頻通話 0 語音通話 1
            Conference: false, // 是否是多人會(huì)議
            ChanId: Store.channelId, // 頻道房間
            UserData: "",
            SipData: "",
            VidCodec: ["H264"],
            AudCodec: ["Opus"],
        });


        // 事件監(jiān)聽
        // 監(jiān)聽被叫已收到呼叫邀請(qǐng)
        Store.localInvitation.on(
            "LocalInvitationReceivedByPeer",
            rtmEvent.localInvitationReceivedByPeer
        );
        // 監(jiān)聽被叫已接受呼叫邀請(qǐng)
        Store.localInvitation.on(
            "LocalInvitationAccepted",
            rtmEvent.localInvitationAccepted
        );
        // 監(jiān)聽被叫拒絕了你的呼叫邀請(qǐng)
        Store.localInvitation.on(
            "LocalInvitationRefused",
            rtmEvent.localInvitationRefused
        );
        // 監(jiān)聽呼叫邀請(qǐng)進(jìn)程失敗
        Store.localInvitation.on(
            "LocalInvitationFailure",
            rtmEvent.localInvitationFailure
        );
        // 監(jiān)聽呼叫邀請(qǐng)已被成功取消
        Store.localInvitation.on(
            "LocalInvitationCanceled",
            rtmEvent.localInvitationCanceled
        );

        // 發(fā)送邀請(qǐng)
        Store.localInvitation.send();
    },
    // 被叫收到呼叫邀請(qǐng)?zhí)幚?給收到的邀請(qǐng)實(shí)例綁定事件)
    inviteProcessing: async (remoteInvitation) => {
        // 監(jiān)聽接受呼叫邀請(qǐng)
        remoteInvitation.on(
            "RemoteInvitationAccepted",
            rtmEvent.RemoteInvitationAccepted
        );
        // 監(jiān)聽拒絕呼叫邀請(qǐng)
        remoteInvitation.on(
            "RemoteInvitationRefused",
            rtmEvent.RemoteInvitationRefused
        );
        // 監(jiān)聽主叫取消呼叫邀請(qǐng)
        remoteInvitation.on(
            "RemoteInvitationCanceled",
            rtmEvent.RemoteInvitationCanceled
        );
        // 監(jiān)聽呼叫邀請(qǐng)進(jìn)程失敗
        remoteInvitation.on(
            "RemoteInvitationFailure",
            rtmEvent.RemoteInvitationFailure
        );
    },

    // 正在通話中處理
    callIng: async (remoteInvitation) => {
        remoteInvitation.response = await JSON.stringify({
            // Reason: "Calling",
            refuseId: Store.ownUserId,
            Reason: "calling",
            Cmd: "Calling",
        });
        await remoteInvitation.refuse();
    },

    // 不同意邀請(qǐng)后返回首頁
    crosslightgoBack: (message) => {
        // Store 重置
        Until.clearStore();
        // 返回首頁
        wx.reLaunch({
            url: '../index/index',
        });
        wx.showToast({
            title: message,
            icon: 'none',
            duration: 2000,
            mask: true,
        });
    },

    // 發(fā)送消息
    sendMessage: (uid, message) => {
        console.log("發(fā)送消息", uid, message);
        Store.rtmClient && Store.rtmClient.sendMessageToPeer({
            text: JSON.stringify(message)
        }, uid).catch(err => {
            console.log("發(fā)送消息失敗", err);
        });
    },
    // 無網(wǎng)絡(luò)連接結(jié)束當(dāng)前呼叫
    networkEndCall: () => {
        if (Store.rtcClient) {
            // RTC 掛斷
        } else {
            // 呼叫階段
            let oRtmSetInterval = setInterval(() => {
                // rtm 鏈接狀態(tài)
                if (Store.rtmNetWorkType == "CONNECTED") {
                    clearInterval(oRtmSetInterval);
                    // RTM 取消/拒絕呼叫
                    if (Store.localInvitation) {
                        // 主叫取消呼叫
                        Store.localInvitation.cancel();
                    } else if (Store.remoteInvitation) {
                        // 被叫拒絕呼叫
                        Store.remoteInvitation.refuse();
                    }
                }
            }, 500);
        }
    }
}

module.exports = {
    InItRtm,
    rtmInternal,
}

util.js

項(xiàng)目中使用的方法封裝:

  • 時(shí)間轉(zhuǎn)化

  • 生成隨機(jī)數(shù)

  • 音視頻通話變量置空

  • 計(jì)時(shí)器

  • 深克隆

  • 事件監(jiān)聽封裝,類似uniapp的 on,emit,remove(off)

 const formatTime = date => {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()

  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
}

const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : `0${n}`
}

// 隨機(jī)生成uid
const generateNumber = (len) => {
  const numLen = len || 8;
  const generateNum = Math.ceil(Math.random() * Math.pow(10, numLen));
  return generateNum < Math.pow(10, numLen - 1) ?
    generateNumber(numLen) :
    generateNum;
}

// 引入 store
let Store = require("./store");
// 本地清除
const clearStore = () => {
  // 通話計(jì)時(shí)器
  Store.callTimer && clearInterval(Store.callTimer);
  Store = Object.assign(Store, {
    // 視頻通話0 語音通話1
    Mode: 0,
    // 遠(yuǎn)端用戶uid
    peerUserId: "",
    // 頻道房間
    channelId: "",

    // 是否正在通話
    Calling: false,
    // 是否是單人通話
    Conference: false,

    // 通話計(jì)時(shí)
    callTime: 0,
    callTimer: null,
  })
}
// 計(jì)時(shí)器
const calculagraph = () => {
  Store.callTime++;
  let oMin = Math.floor(Store.callTime / 60);
  let oSec = Math.floor(Store.callTime % 60);
  oMin >= 10 ? oMin : (oMin = "0" + oMin);
  oSec >= 10 ? oSec : (oSec = "0" + oSec);
  return oMin + ":" + oSec;
}



// 深克隆
function deepClone(obj) {
  if (typeof obj !== 'object') {
    return obj;
  } else {
    const newObj = obj.constructor === Array ? [] : {};

    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] === 'object') {
          newObj[key] = deepClone(obj[key]);
        } else {
          newObj[key] = obj[key];
        }
      }
    }
    return newObj;
  }
}


/**
 * 事件傳遞
 */
// 用來保存所有綁定的事件
const events = {};

// 監(jiān)聽事件
function on(name, self, callback) {
  // self用來保存小程序page的this,方便調(diào)用this.setData()修改數(shù)據(jù)
  const tuple = [self, callback];
  const callbacks = events[name];
  let isCallback = null;
  // 判斷事件庫(kù)里面是否有對(duì)應(yīng)的事件
  if (Array.isArray(callbacks)) {
    // 相同的事件不要重復(fù)綁定
    const selfCallbacks = callbacks.filter(item => {
      return self === item[0];
    });
    if (selfCallbacks.length === 0) {
      callbacks.push(tuple);
    } else {
      for (const item of selfCallbacks) {
        if (callback.toString() !== item.toString()) {
          isCallback = true;
        }
      }!isCallback && selfCallbacks[0].push(callback);
    }
  } else {
    // 事件庫(kù)沒有對(duì)應(yīng)數(shù)據(jù),就將事件存進(jìn)去
    events[name] = [tuple];
  }
}

// 移除監(jiān)聽的事件
function remove(name, self) {
  const callbacks = events[name];
  if (Array.isArray(callbacks)) {
    events[name] = callbacks.filter(tuple => {
      return tuple[0] !== self;
    });
  }
}

// 觸發(fā)監(jiān)聽事件
function emit(name, data = {}) {
  const callbacks = events[name];
  if (Array.isArray(callbacks)) {
    callbacks.map(tuple => {
      const self = tuple[0];
      for (const callback of tuple) {
        if (typeof callback === 'function') {
          // 用call綁定函數(shù)調(diào)用的this,將數(shù)據(jù)傳遞過去
          callback.call(self, deepClone(data));
        }
      }
    });
  }
}

module.exports = {
  formatTime,
  generateNumber,
  clearStore,
  on,
  remove,
  emit,
  calculagraph
}

呼叫邀請(qǐng)頁面 pageinvite

pageinvite.wxml

<view class="container">
    <image class="icon_back" mode="scaleToFill" src="../img/icon_back.png" />
    <view class="details">
        <!-- 用戶 -->
        <view style="padding: 80px 0 0;display: flex;flex-direction: column;align-items: center;">
            <image class="head_portrait" src="../img/icon_head.png"></image>
            <text class="text_color">{{uid}}</text>
        </view>
        <!-- 加載中 -->
        <view class="loading">
            <image class="img_size" src="../img/animation.png"></image>
            <text class="text_color m">{{CallFlse ? '收到邀請(qǐng)' : '呼叫中'}} </text>
        </view>
        <!-- 操作 -->
        <view style="width: 100%;">
            <!-- 視頻操作 -->
            <view class="operate" wx:if="{{mode == 0 && CallFlse}}">
                <view style="visibility: hidden;">
                    <image class="img_size" src="../img/icon_switch_voice.png"></image>
                </view>
                <!-- 視頻轉(zhuǎn)語音 -->
                <view class="loading" bindtap="voiceCall">
                    <image class="img_size" src="../img/icon_switch_voice.png"></image>
                    <text class="text_color m">轉(zhuǎn)語音</text>
                </view>

            </view>
            <!-- 公共操作 -->
            <view class="operate m">
                <!-- 掛斷 -->
                <view class="loading" bindtap="cancelCall">
                    <image class="img_size" src="../img/icon_hangup.png"></image>
                    <text class="text_color m">{{CallFlse ?'掛斷':'取消'}}</text>
                </view>

                <!-- 接聽 -->
                <view class="loading" wx:if="{{CallFlse}}" bindtap="acceptCall">
                    <image class="img_size" src="../img/icon_accept.png"></image>
                    <text class="text_color m">接聽</text>
                </view>
            </view>
        </view>
    </view>
</view>

pageinvite.js(響鈴音樂自行添加)

響鈴音樂自行添加

// const RTM = require("../../utils/rtm")
const Store = require("../../utils/store");
const Until = require("../../utils/util");
// pages/p2ppage/p2ppage.js

// 響鈴
// const innerAudioContext = wx.createInnerAudioContext();
// let innerAudioContext = null;
Page({

    /**
     * 頁面的初始數(shù)據(jù)
     */
    data: {
        // 呼叫者
        uid: "",
        // 通話方式
        mode: 0,
        // 主叫/被叫
        CallFlse: false,
        // 響鈴
        innerAudioContext: null,
    },

    /**
     * 生命周期函數(shù)--監(jiān)聽頁面加載
     */
    onLoad: function (options) {
         // 響鈴音樂
        // const innerAudioContext = wx.createInnerAudioContext();
        // innerAudioContext.src = "/pages/audio/video_request.mp3";
        // innerAudioContext.autoplay = true;
        // innerAudioContext.loop = true;
        // innerAudioContext.play();
        Store.State = 1;
        this.setData({
            uid: Store.peerUserId,
            mode: Store.Mode,
            CallFlse: options.call == 0 ? false : true,
            innerAudioContext
        });
    },
    /**
     * 生命周期函數(shù)--監(jiān)聽頁面顯示
     */
    onShow: function () {
        wx.hideHomeButton();
    },
    onUnload: function () {
        console.log("銷毀");
        // 停止響鈴
        // this.data.innerAudioContext.destroy();
    },
    // 取消呼叫
    async cancelCall() {
        if (this.data.CallFlse) {
            // 被叫拒絕
            Store.remoteInvitation && await Store.remoteInvitation.refuse();
        } else {
            // 主叫取消
            Store.localInvitation && await Store.localInvitation.cancel();
        }
    },
    // 接受邀請(qǐng)
    async acceptCall() {
        if (Store.remoteInvitation) {
            console.log("接受邀請(qǐng)",Store.remoteInvitation);
            // 設(shè)置響應(yīng)模式
            Store.remoteInvitation.response = await JSON.stringify({
                Mode: this.data.mode,
                Conference: false,
                UserData: "",
                SipData: "",
            });
            // 本地模式
            Store.Mode = this.data.mode;
            // 接受邀請(qǐng)
            await Store.remoteInvitation.accept();
        }
    },
    // 語音接聽
    async voiceCall() {
        if (Store.remoteInvitation) {
            // 設(shè)置響應(yīng)模式
            Store.remoteInvitation.response = await JSON.stringify({
                Mode: 1,
                Conference: false,
                UserData: "",
                SipData: "",
            });
            // 本地模式
            Store.Mode = 1;
            // 接受邀請(qǐng)
            await Store.remoteInvitation.accept();
        }
    }
})

語音通話頁面 pagecall

pagecall.wxml

<!--pages/pagecall/pagecall.wxml-->
<!-- 視頻通話 -->
<view class="live" wx:if="{{mode === 0}}">
    <!-- 可移動(dòng) -->
    <movable-area class="movable-area">
        <movable-view direction="all" x="{{windowWidth-140}}" y="20" class="live-pusher">
            <!-- 本地錄制 -->
            <live-pusher v-if="{{livePusherUrl}}" url="{{livePusherUrl}}" mode="RTC" autopush bindstatechange="statechange" binderror="error" style="height: 100%;width: 100%;" />
        </movable-view>
    </movable-area>

    <!-- 遠(yuǎn)端播放 -->
    <view class="live-player">
        <live-player src="{{livePlayerUrl}}" mode="RTC" autoplay bindstatechange="statechange" binderror="error" style="height: 100%;width: 100%;position: absolute;z-index: -100;">
            <!-- 通話計(jì)時(shí) -->
            <cover-view class="calltime text_color">{{calltime}}</cover-view>
            <!-- 操作 -->
            <cover-view class="operate">
                <cover-view class="operate-item" bindtap="switchAudio">
                    <cover-image class="operate_img" src="../img/icon_switch_voice.png"></cover-image>
                    <cover-view class="text_color m">切換至語音</cover-view>
                </cover-view>
                <cover-view class="operate-item" bindtap="endCall">
                    <cover-image class="operate_img" src="../img/icon_hangup.png"></cover-image>
                    <cover-view class="text_color m">掛斷</cover-view>
                </cover-view>
                <cover-view class="operate-item" bindtap="switchCamera">
                    <cover-image class="operate_img" src="{{devicePosition == 'front' ? '../img/icon_switch.png':'../img/icon_switchs.png'}}"></cover-image>
                    <cover-view class="text_color m">
                        {{devicePosition == 'front' ? '前' : '后'}}攝像頭
                    </cover-view>
                </cover-view>
            </cover-view>
        </live-player>
        <!-- style="height: 100%;width: 100%;position: absolute;z-index: -100;"  -->
    </view>



</view>
<!-- 語音通話 -->
<view class="live" style="background-color: rgba(0, 0, 0, 0.5);" wx:else>
    <!-- 本地推流 關(guān)閉攝像頭-->
    <live-pusher style="width: 0px;height: 0px;" mode='RTC' enable-camera='{{false}}' url='{{ livePusherUrl }}' autopush></live-pusher>
    <!-- 遠(yuǎn)端拉流 -->
    <live-player v-if="{{livePlayerUrl}}" style="width: 0px;height: 0px;" autoplay mode='RTC' src='{{ livePlayerUrl }}' binderror="error" bindstatechange="statechange" sound-mode='{{soundMode}}'></live-player>

    <!-- 遠(yuǎn)端用戶信息 -->
    <view class="peerinfo">
        <image class="icon_head" src="../img/icon_head.png"></image>
        <text class="text_color m">{{peerid}}</text>
    </view>
    <!-- 通話計(jì)時(shí) -->
    <view class="calltime">
        <text class="text_color">{{calltime}}</text>
    </view>
    <!-- 操作 -->
    <view class="operate">
        <view class="operate-item" bindtap="muteAudio">
            <image class="operate_img" src="{{enableMic ? '../img/icon_closeaudio.png' : '../img/icon_openaudio.png' }}"></image>
            <text class="text_color m">靜音</text>
        </view>
        <view class="operate-item" bindtap="endCall">
            <image class="operate_img" src="../img/icon_hangup.png"></image>
            <text class="text_color m">掛斷</text>
        </view>
        <view class="operate-item" bindtap="handsFree">
            <image class="operate_img" src="{{soundMode == 'speaker' ? '../img/icon_speakers.png':'../img/icon_speaker.png'}}"></image>
            <text class="text_color m">免提</text>
        </view>
    </view>
</view>

pagecall.js

const Until = require("../../utils/util");
const Store = require("../../utils/store");
const RTC = require("../../utils/rtc");
const RTM = require("../../utils/rtm");
const liveCode = require("../../utils/live-code");
Page({

    /**
     * 頁面的初始數(shù)據(jù)
     */
    data: {
        // 可用寬度
        windowWidth: "",
        // 通話方式
        mode: 0,
        // 遠(yuǎn)端uid
        peerid: "",
        // 本地錄制地址(小程序特有推流)
        livePusherUrl: "",
        // 遠(yuǎn)端播放(小程序特有拉流)
        livePlayerUrl: "",

        // 前置或后置,值為front, back
        devicePosition: 'front',
        // 開啟或關(guān)閉麥克風(fēng)
        enableMic: false,
        // 開啟免提
        soundMode: 'speaker',

        calltime: "00:00"

    },
    // 微信組件狀態(tài)
    statechange(e) {
        if (e.detail.code == 2004) {
            wx.hideLoading();
        }
        if (e.detail.code != 1006 && e.detail.message) {
            wx.showToast({
                title: liveCode[e.detail.code] || e.detail.message,
                icon: 'none',
            })
        }

        console.log('live-pusher code:', e.detail)
    },
    // 微信組件錯(cuò)誤
    error(e) {
        console.log(e.detail);
        switch (e.detail.errCode) {
            case 10001:
                wx.showToast({
                    title: '用戶禁止使用攝像頭',
                    icon: 'none',
                    duration: 2000
                })
                break;
            case 10002:
                wx.showToast({
                    title: '用戶禁止使用錄音',
                    icon: 'none',
                    duration: 2000
                })
                break;
            default:
                break;
        }
    },
    /**
     * 生命周期函數(shù)--監(jiān)聽頁面加載
     */
    onLoad: function (options) {
        const _this = this;
        Store.State = 2;
        // 推拉流變更
        Until.on("livePusherUrlEvent", this, (data) => {
            _this.setData({
                livePusherUrl: data.livePusherUrl ? data.livePusherUrl : _this.data.livePusherUrl,
                livePlayerUrl: data.livePlayerUrl ? data.livePlayerUrl : _this.data.livePlayerUrl,
            })
        });
        // 通話模式變更
        Until.on("callModeChange", this, (data) => {
            _this.setData({
                mode: data.mode,
            });
            Store.Mode = data.mode;
        })
        // 可用寬度
        try {
            const oInfo = wx.getSystemInfoSync();
            this.setData({
                windowWidth: oInfo.windowWidth,
                mode: Store.Mode,
                // mode: 1,
                peerid: Store.peerUserId || '6666',
            })
            // 開啟通話計(jì)時(shí)
            Store.callTimer = setInterval(() => {
                _this.setData({
                    calltime: Until.calculagraph()
                })
            }, 1000)
        } catch (error) {
            console.log("error", error);
        }

    },

    /**
     * 生命周期函數(shù)--監(jiān)聽頁面卸載
     */
    onUnload: function () {
        Until.remove("livePusherUrlEvent", this);
        Until.remove("callModeChange",this);
    },
    // 切換至語音
    switchAudio() {
        this.setData({
            peerid: Store.peerUserId,
            mode: 1,
        });
        Store.Mode = 1;
        // 發(fā)送切換語音消息
        RTM.rtmInternal.sendMessage(Store.peerUserId, {
            Cmd: "SwitchAudio",
        })
    },
    // 掛斷
    endCall() {
        RTC.rtcInternal.leaveChannel(true);
    },
    // 翻轉(zhuǎn)攝像頭
    switchCamera() {
        wx.createLivePusherContext().switchCamera();
        this.setData({
            devicePosition: this.data.devicePosition == 'front' ? 'back' : 'front'
        })
    },
    // 靜音
    muteAudio() {
        this.setData({
            enableMic: this.data.enableMic ? false : true,
        });
        RTC.rtcInternal.switchAudio(this.data.enableMic);
    },
    // 免提
    handsFree() {
        this.setData({
            soundMode: this.data.soundMode == 'speaker' ? 'ear' : 'speaker',
        });
    },
})

體驗(yàn)地址

微信搜索anyRTC視頻云點(diǎn)擊AR 呼叫即可體驗(yàn)小程序版 ARCall
?

代碼地址

文件Call_watch

技術(shù)分享| 小程序?qū)崿F(xiàn)音視頻通話文章來源地址http://www.zghlxwxcb.cn/news/detail-430444.html

到了這里,關(guān)于技術(shù)分享| 小程序?qū)崿F(xiàn)音視頻通話的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • WebRTC音視頻通話-實(shí)現(xiàn)GPUImage視頻美顏濾鏡效果iOS

    WebRTC音視頻通話-實(shí)現(xiàn)GPUImage視頻美顏濾鏡效果iOS

    WebRTC音視頻通話-實(shí)現(xiàn)GPUImage視頻美顏濾鏡效果 在WebRTC音視頻通話的GPUImage美顏效果圖如下 可以看下 之前搭建ossrs服務(wù),可以查看:https://blog.csdn.net/gloryFlow/article/details/132257196 之前實(shí)現(xiàn)iOS端調(diào)用ossrs音視頻通話,可以查看:https://blog.csdn.net/gloryFlow/article/details/132262724 之前WebR

    2024年02月12日
    瀏覽(24)
  • iOS 端實(shí)現(xiàn)1對(duì)1音視頻實(shí)時(shí)通話

    iOS 端實(shí)現(xiàn)1對(duì)1音視頻實(shí)時(shí)通話

    首先,我們來看一下 iOS 端是如何獲取訪問音視頻設(shè)備權(quán)限的。相比 Android 端而言,iOS端獲取相關(guān)權(quán)限要容易很多。其步驟如下: 打開項(xiàng)目,點(diǎn)擊左側(cè)目錄中的項(xiàng)目。 在左側(cè)目錄找到 info.plist ,并將其打開。 點(diǎn)擊 右側(cè) 看到 “+” 號(hào)的地方。 添加 Camera 和 Microphone 訪問權(quán)限。

    2024年02月15日
    瀏覽(26)
  • WebRTC實(shí)戰(zhàn)-第二章-使用WebRTC實(shí)現(xiàn)音視頻通話

    WebRTC實(shí)戰(zhàn)-第二章-使用WebRTC實(shí)現(xiàn)音視頻通話

    、 什么是WebRTC|WebRTC入門到精通必看|快速學(xué)會(huì)音視頻通話原理|WebRTC超全資料分享FFmpeg/rtmp/hls/rtsp/SRS WebRTC **WebRTC詳細(xì)指南** http://www.vue5.com/webrtc/webrtc.html WEBRTC三種類型(Mesh、MCU 和 SFU)的多方通信架構(gòu) WebRTC API包括媒體捕獲,音頻和視頻編碼和解碼,傳輸層和會(huì)話管理 。 假設(shè)

    2023年04月12日
    瀏覽(20)
  • 《保姆級(jí)教程》基于Agora SDK實(shí)現(xiàn)音視頻通話及屏幕共享

    《保姆級(jí)教程》基于Agora SDK實(shí)現(xiàn)音視頻通話及屏幕共享

    ??作者簡(jiǎn)介: 小曾同學(xué).com,一個(gè)致力于測(cè)試開發(fā)的博主??,主要職責(zé):測(cè)試開發(fā)、CI/CD 如果文章知識(shí)點(diǎn)有錯(cuò)誤的地方,還請(qǐng)大家指正,讓我們一起學(xué)習(xí),一起進(jìn)步。?? 座右銘:不想當(dāng)開發(fā)的測(cè)試,不是一個(gè)好測(cè)試??。 如果感覺博主的文章還不錯(cuò)的話,還請(qǐng)點(diǎn)贊、收藏哦

    2024年02月12日
    瀏覽(21)
  • WebRTC音視頻通話(二)簡(jiǎn)單音視頻通話

    WebRTC音視頻通話(二)簡(jiǎn)單音視頻通話

    本篇不詳細(xì)介紹websocket,只針對(duì)websocket整合rtc。 webrtc是P2P通信,也就是實(shí)際交流的 只有兩個(gè)人 ,而要建立通信,這兩個(gè)人需要 交換一些信息來保證通信安全 。而且, webrtc必須通過ssh加密 ,也就是使用https協(xié)議、wss協(xié)議。 借用一幅圖 1.1 創(chuàng)建端點(diǎn)的解析 以下解析不包括we

    2023年04月09日
    瀏覽(29)
  • 基于webrtc的音視頻通話,實(shí)現(xiàn)相機(jī)流識(shí)別人臉的功能

    這幾天研究了一下webRTC的基礎(chǔ)能力,在此基礎(chǔ)之上能實(shí)現(xiàn)的視頻通話,互動(dòng)等更多實(shí)用功能。項(xiàng)目中使用的是阿里的rtc,我研究的是聲網(wǎng)的是否符合功能,后續(xù)會(huì)總結(jié)和記錄一下應(yīng)用到的幾個(gè)功能實(shí)現(xiàn)方法。 今天要記錄的功能是項(xiàng)目流識(shí)別人臉的功能,其實(shí)類似功能很常見了

    2024年04月28日
    瀏覽(27)
  • 保姆級(jí)教程!基于聲網(wǎng) Web SDK實(shí)現(xiàn)音視頻通話及屏幕共享

    保姆級(jí)教程!基于聲網(wǎng) Web SDK實(shí)現(xiàn)音視頻通話及屏幕共享

    本篇文章主要給小伙伴們分享如何使用聲網(wǎng) SDK 實(shí)現(xiàn) Web 端音視頻通話及屏幕共享功能,其中也會(huì)涵蓋在實(shí)踐過程中遇到的一些問題,以此記錄防止小伙伴們踩坑,同時(shí)也希望通過從 0 到 1 實(shí)戰(zhàn)的分享,能夠幫助更多的小伙伴。

    2024年02月16日
    瀏覽(28)
  • 技術(shù)分享| anyRTC音視頻混流技術(shù)解析

    技術(shù)分享| anyRTC音視頻混流技術(shù)解析

    在視頻通訊場(chǎng)景中,比如會(huì)議、直播等經(jīng)常能看到圖像合成的場(chǎng)景。圖像合成是在指定的一塊畫面區(qū)域,在這個(gè)區(qū)域內(nèi),按畫面的位置(坐標(biāo))布局,將區(qū)域中的每個(gè)視頻畫面的像素混合計(jì)算成一個(gè)像素(RGB)。比如以下是anyRTC的H323合成畫面: 如圖所示,一幅圖像或畫面是由很

    2024年02月07日
    瀏覽(25)
  • Unity Metaverse(八)、RTC Engine 基于Agora聲網(wǎng)SDK實(shí)現(xiàn)音視頻通話

    Unity Metaverse(八)、RTC Engine 基于Agora聲網(wǎng)SDK實(shí)現(xiàn)音視頻通話

    本文介紹如何在Unity中接入聲網(wǎng)SDK,它可以應(yīng)用的場(chǎng)景有許多,例如直播、電商、游戲、社交等,音視頻通話是其實(shí)時(shí)互動(dòng)的基礎(chǔ)能力。 如下圖所示,可以在官網(wǎng)中選擇Unity SDK進(jìn)行下載,也可以到 Unity Asset Store 資源商店中搜索 Agora SDK 進(jìn)行下載導(dǎo)入。 在官網(wǎng)中前往 Console 控制

    2024年02月09日
    瀏覽(27)
  • WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務(wù)搭建

    WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務(wù)搭建

    iOS開發(fā)-ossrs服務(wù)WebRTC本地視頻通話服務(wù)搭建 之前開發(fā)中使用到了ossrs,這里記錄一下ossrs支持的WebRTC本地服務(wù)搭建。 ossrs是什么呢? SRS(Simple Realtime Server)是一個(gè)簡(jiǎn)單高效的實(shí)時(shí)視頻服務(wù)器,支持RTMP、WebRTC、HLS、HTTP-FLV、SRT等多種實(shí)時(shí)流媒體協(xié)議。 官網(wǎng)地址:https://ossrs.net/lt

    2024年02月12日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包