anyRTC跨房间连麦代码实现

409 阅读2分钟

简介

anyrtc Web SDK 4.0 能实现基于 anyrtc SD-RTN™ 的实时音视频通信功能,支持语音通话、视频通话、音频互动直播、视频互动直播等场景。anyrtc Web SDK 是一个全量重构的版本,主要针对 API 的易用性和内部架构做了较大的调整。

如果你之前没有接触过 anyrtc Web SDK 相关的产品,anyrtc Web SDK 将会成为一个很好的起点。如果你之前接触过 anyrtc Web SDK,请注意 anyrtc Web SDK 在使用和开发步骤上和原先的 SDK 有较大的不同。最明显的不同之处在于,anyrtc Web SDK 删除了原来的 Stream 对象,通过 LocalTrack 和 RemoteTrack 对象来控制媒体流。

浏览器兼容性

anyrtc Web SDK 兼容大部分主流浏览器,如下表所示

准备工作

APPID获取地址: anyRTC;

SDK获取地址: webSDK;

定义变量

// 本地客户端
var client = null;
// 计时器
var timer = null;
// 跨房间配置对象
var configuration = null;
// 显示配置信息开关
var offon = false;
// 本地是否发布
var publised = false;
// 是否播放远端视频
var remotePlay = false;
// 本地音视频轨道
var track = {
    videoTrack: null,
    audioTrack: null,
};

初始化页面

ArRTM 是引入sdk后自动抛出的。

var init = async function () {
    client = ArRTC.createClient({ mode: "rtc", codec: "h264" }); // 创建sdk全局实例
};

加入频道

登录 anyrtc RTM 系统的用户 ID。该字符串不可超过 64 字节。以下为支持的字符集范围: 26 个小写英文字母 a-z

26 个大写英文字母 A-Z

10 个数字 0-9

空格

"!", "#", "$", "%", "&", "(", ")", "+", "-", ":", ";", "<", "=", ".", ">", "?", "@", "[", "]", "^", "_", " {", "}", "|", "~", ","

请不要将 uid 设为空、null,或字符串 "null"。

uid 不支持 number 类型。建议调用 toString() 方法转化非 string 型 uid。

var join = async function () {
    const appid = element.configBox.appid.value;
    const channel = element.configBox.channel.value;
    const token = element.configBox.token.value;
    if (!appid) {
        api.addMessage({ type: 'error', message: 'Please enter the appid' });
        return;
    };
    if (!channel) {
        api.addMessage({ type: 'error', message: 'Please enter the channel' });
        return;
    };
    // 获取摄像头
    const cameras =  await api.cameras(); // 核心代码
    // 获取麦克风
    const microphones = await api.microphones(); // 核心代码
    if (!cameras || !microphones) return;
    client.join(appid, channel, token || null).then(() => {
    	// 如果有音频对象就播放
        track.audioTrack && track.audioTrack.play(); // 核心代码
    	// 如果有视频对象就播放
        track.videoTrack && track.videoTrack.play(element.videoBox.localVideo, { fit: 'contain' }); // 核心代码
        api.addMessage({ type: 'success', message: 'Join Success' });
        // 开始监听回调函数
        api.callBack(); // 核心代码
    }, () => {
        api.addMessage({ type: 'error', message: 'Join Failed' });
    })
};

监听回调

var callBack = async function () {
    // 监听远端用户发布 (对方跨房间发布以后,可以在这里拿到对方发布的媒体流)
    client.on("user-published", async (user, mediaType) => {
        await client.subscribe(user, mediaType);
        if (mediaType === "video") {
            remotePlay = true;
            // 播放远端视频
            user.videoTrack.play(element.videoBox.remoteVideo, { fit: 'contain' }); // 核心代码
        };
        if (mediaType === "audio") {
            // 播放远端音频
            user.audioTrack.play(); // 核心代码
        };
    });
    // 监听远端用户取消发布
    client.on("user-unpublished", (user, mediaType) => {
        if (mediaType === "video") {
            remotePlay = false;
        };
    });
}; 

获取摄像头

 var cameras = async function () {
    const Cameras = await ArRTC.getCameras(); // 核心代码
    if (Cameras.length) {
        const ICameraVideoTrack = await ArRTC.createCameraVideoTrack();
        track.videoTrack = ICameraVideoTrack;
        return true;
    } else {
        api.addMessage({ type: 'error', message: 'No cameras are available' });
        return false;
    };
};

获取麦克风

 var microphones = async function () {
    const Microphones = await ArRTC.getMicrophones(); // 核心代码
    if (Microphones.length) {
        const IMicrophoneAudioTrack = await ArRTC.createMicrophoneAudioTrack();
        track.audioTrack = IMicrophoneAudioTrack;
        return true;
    } else {
        api.addMessage({ type: 'error', message: 'No microphones are available' });
        return false;
    };
};

发布本地音视频

 var publish = function () {
    // 发布本地音视频 (核心代码)
    client.publish([track.videoTrack, track.audioTrack]).then(() => {
        api.addMessage({ type: 'success', message: 'Publish Success' });
        publised = true;
    }, () => {
        api.addMessage({ type: 'error', message: 'Publish Failed' });
    });
};

开始跨房间

跨房间之前一定要先发布本地视频, 否则跨房间以后对方只能看到黑屏

var start = function () {
    const forChannel = element.configBox.forChannel.value;
    const channel = element.configBox.channel.value;
    if (forChannel === "") {
        api.addMessage({ type: 'error', message: 'Please enter the forChannel number' });
        return;
    };
    if (forChannel === channel) {
        api.addMessage({ type: 'error', message: 'forChannel and channel repeat' });
        return;
    };
    // 生成跨房间配置对象
    configuration = ArRTC.createChannelMediaRelayConfiguration(); // 核心代码
    // 设置本地房间信息 channel 是本地房间号
    configuration.setSrcChannelInfo({ channelName: channel }); // 核心代码
    // 设置目标房间信息 forChannel 是对方房间号
    configuration.addDestChannelInfo({ channelName: forChannel }); // 核心代码
    var timer = setTimeout(() => {
        api.addMessage({ type: 'error', message: 'Leave Failed' });
    }, 3000);
    // 开始跨房间
    client.startChannelMediaRelay(configuration); // 核心代码
    clearTimeout(timer);
    api.addMessage({ type: 'success', message: 'start rtmp success' });
};

获取音视频状态

var state = function (type) {
    // 获取本地或远端的音频状态(核心代码)
    var audioState = client[type === 'localVideo'? 'getLocalAudioStats' : 'getRemoteAudioStats']();
    // 获取本地或远端的视频状态(核心代码)
    var videoState = client[type === 'localVideo'? 'getLocalVideoStats' : 'getRemoteVideoStats']();
    var ele = document.getElementsByClassName(type)[0];
    ele && element.videoBox[type].removeChild(ele);
    var stateBox = document.createElement('div');
    stateBox.className = `state ${type}`;
    var videost = document.createElement('div');
    var audiost = document.createElement('div');
    [audioState, videoState].forEach((li, index) => {
        for (var key in li) {
            if (type === 'localVideo') {
                var div = document.createElement('div');
                div.innerHTML = `${key}: ${li[key]}`;
                (index === 0? audiost : videost).appendChild(div);
            } else {
                for (var item in li[key]) { 
                    var div = document.createElement('div');
                    div.innerHTML = `${item}: ${li[key][item]}`;
                    (index === 0? audiost : videost).appendChild(div);
                };
            };
        };
    })
    stateBox.appendChild(videost);
    stateBox.appendChild(audiost);
    element.videoBox[type].appendChild(stateBox);
};

停止跨房间

var stop = function () {
    // 停止跨房间
    client.stopChannelMediaRelay(); // 核心代码
    element.configBox.forChannel.value = '';
};

取消发布本地音视频

var unPublish = function () {
    // 取消发布本地音视频(核心代码)
    client.unpublish([track.videoTrack, track.audioTrack]).then(() => {
        api.addMessage({ type: 'success', message: 'UnPublish Success' });
    }, () => {
        api.addMessage({ type: 'error', message: 'UnPublish Failed' });
    });
};

退出频道

var leave = async function () {
    // 离开频道 (核心代码)
    client.leave().then(() => {
        api.addMessage({ type: 'success', message: 'Leave Success' });
        element.operationBox.join.disabled = false;
        element.operationBox.join.classList.remove('disabled');
        for (var key in element.operationBox) {
            if (key !== 'join') {
                element.operationBox[key].disabled = true;
                element.operationBox[key].classList.add('disabled');
            };
        };
        for (var key in element.configBox) {
            element.configBox[key].value = '';
        };
        track.videoTrack.stop();
        track.audioTrack.stop();
        configuration = null;
        offon = false;
        publised = false;
        remotePlay = false;
        track = {
            videoTrack: null,
            audioTrack: null,
        }
    }, () => {
        api.addMessage({ type: 'error', message: 'Leave Failed' });
    });
};