jssip连freeswitch加webRtc相关控制

摘要:最近在做一个freeSwitch项目,前端需要通过sip协议完成音视频通话,把一些关键的核心api记录一下;因为网上找的一部分资料不一定准确,这个是实际操作过得具有一定的参考性;基本复制粘贴可快速完成直连freeSwitch的目的;更新日期2022-10-19;

前端sip这块使用的是jssip(版本3.9.1),详细api可以去官网查看;freeSwitch(1.10.7)这个我们只做参考;

注册
let ws = '你的freeSwitch直连地址';
let socket = new JsSIP.WebSocketInterface(ws);
let configuration = {
    sockets: [socket],
    uri: `sip:${username}@${serverip}`,
    password: '',
    authorizationUser: '',
    sessionTimersExpires: 5000
}
let UA = new JsSIP.UA(configuration);
// 存储当前session和connection
let currentSession = null;
let currentConnection = null;
绑定事件
 // 开始尝试连接
UA.on('connecting', () => {});
// 连接完毕
UA.on('connected', () => {});
// 主动取消注册或注册后定期重新注册失败
UA.on('unregistered', () => {});
// 注册失败
UA.on('registrationFailed', () => {});
// 注册成功
UA.on('registered', () => {});
// websocket 连接失败
UA.on('disconnected', () => {});
// 新消息接收和发送消息
UA.on('newMessage', (res) => {
    console.log('接收和发送消息', res, '消息字段', res.data);
    if (res.originator === 'remote') { // 远程消息
    } else if ( res.originator === 'local') { // 本地消息
    };
});
// 这一块处理webRTC音视频逻辑
UA.on('newRTCSession', (res) => {
    let { session, originator } = res;
    // 远程来电
    if (originator === 'remote') { // 处理接听逻辑
        handleAnswerWebRTCSession(session);
    } else if (originator === 'local') { // 处理呼叫逻辑
        handleCallWebRTCSession(session);
    };
});


newRTCSession处理
// 处理接听newRTCSession
function handleAnswerWebRTCSession(session) {
    /** session 要单独存下,后面接听挂断需要
        挂断: session.terminate();
        接听:session.answer({'mediaConstraints': { 'audio': true, 'video': true }})
    */
    let {connection} = session;
    let currentSession = session;
    let currentConnection = connection;
    // 来电-被接听了
    session.on("accepted", () => {        
        handleStreamsSrcObject(connection);
    });
    session.on("peerconnection", () => {});
    // 来电=>自定义来电弹窗,让用户选择接听和挂断
    session.on("progress", () =>{});
    // 挂断-来电已挂断
    session.on("ended", () => {});
        // 当会话无法建立时触发
    session.on("failed", () => {});
}
// 处理呼叫newRTCSession
function handleCallWebRTCSession(session){
    /** session 要单独存下挂断需要
        挂断: session.terminate();
        connection 如果后面音视频做禁音,或者解绑轨道,或共享桌面需要
    */
    let {connection} = session;
    let currentSession = session;
    let currentConnection = connection;
    session.on('progress', () => { // 呼叫中,响铃中
    });
    session.on('confirmed', () => {
        handleStreamsSrcObject(connection);
    });
}
// 处理媒体流
handleStreamsSrcObject(connection) {
    if (connection.getRemoteStreams().length > 0) {
        // 获取远程媒体流
        let srcObject = connection.getRemoteStreams()[0];        
    };
    
    if (connection.getLocalStreams().length > 0) {
        // 获取本地媒体流
        let srcObject = connection.getLocalStreams()[0];
    };
}
判断当前session是否连接
 judgeSessionState() { // 判断当前会话状态
    return new Promise((resolve, reject) => {
        let curSession = this.session;
        if (curSession === null) {
            reject({code: 0, message: '会话未建立'});
            return;
        };
        if (curSession.isEnded()) {
            reject({code: 0, message: '会话已结束'});
            return;
        };
        // isEstablished_如果会话已建立; isInProgress_会话处于进行中状态;
        if (curSession.isEstablished() || curSession.isInProgress()) {
            resolve(curSession);
            return;
        };
        // 保底情况
        reject({code: 0, message: '会话超时或已被销毁'});
    });
}
挂断/接听/呼叫
hangUp() {
    // terminate({status_code: 603}); 可以指定status_code为603,收到来电拒绝可以发603
    currentSession.terminate();
},
answer(){
    currentSession.answer({'mediaConstraints': { 'audio': true, 'video': true }})
}
call(target, options = {isAudio: true, isVideo: true}, callback) {
    let url = `sip:${target}@${serverip}`;
    var eventHandlers = {
        // 在接收或生成对INVITE请求的1XX SIP类响应(> 100)时触发
        'progress': function(data){
            callback('呼叫中');
            // 如果成功发起会触发2次
            const statusCode = data.response.status_code;
            // 表示一个呼叫请求已经被接收,正在被处理,并且即将被传递给被叫方。
            if (statusCode === 180) {
            }
            // 是一种会话进展消息,表示会话已经建立,但媒体尚未准备好。
            if (statusCode === 183) {
            }
        },
        // 会议无法建立时触发
        'failed': function(data){
            callback('无法建立', data);
        },
        // 通话确认(ACK收/发)时触发
        'confirmed': function(data){
            callback('已接听', data);
        },
        // 当已建立的通话结束时触发
        'ended': function(){
            callback('通话结束了');
        }
    }
    this.UA.call(url, {
        'eventHandlers': eventHandlers,
        'mediaConstraints': { 'audio': options.isAudio, 'video': options.isVideo},
    });
}
会话无法建立归纳
/*呼叫篇*/
 'failed': function(data){ // 会议无法建立时触发
     const {originator, cause, message } = data;
     let errorText = '呼叫失败';
    let errorCode = 'CallFailed';
    // 本地错误
    if (originator === 'local') {
        if (cause === 'Canceled') {
            errorText = '呼叫已取消';
            errorCode = 'Canceled';
        } if (cause === 'User Denied Media Access') {
            errorText = '媒体设备权限被禁';
            errorCode = 'NoMediaPower';
        }
    }
    // 远端错误
    if (originator === 'remote') {
        const statusCode = message.status_code || '';
        /* 如果是别的端拒接状态码都不一致,所以做了个范围都算是对方拒绝了邀请;
            安卓_Linphone: Rejected; SIP 603
            // 挂断指定code可以解决:terminate({status_code: 603}); 
            WEB_JsSip:Unavailable;  SIP 480 
            软电话拒接接听:Busy; SIP 487 对方忙
        */
        if (statusCode === 603 || cause === 'Rejected') {
            errorText = '对方拒绝了你的通话请求';
            errorCode = 'Rejected';
        } else if (statusCode === 486) {
            errorText = '呼叫失败,对方在忙';
        } else if (statusCode === 480) {
            errorText = '呼叫失败,对方不在线';
        }
    }
 }
/*接听篇*/
'failed': function(data){ // 会议无法建立时触发
    const {originator, cause, message } = data;
    let errorText = '接听失败';
    let errorCode = 'AnswerFailed';
    if (originator === 'local') {
        if (cause === 'Rejected') {
            errorCode = 'Rejected';
            errorText = '自己拒绝接听';
        } else if (cause === 'User Denied Media Access') {
            errorText = '媒体设备权限被禁';
            errorCode = 'NoMediaPower';
        }
    }
    if (data.originator === 'remote') {
        if (cause === 'Canceled') {
            errorText = '对方已取消';
            errorCode = 'Canceled';
        }
    } 
}
屏幕共享-结束共享后切换原摄像头
// 共享桌面
desktopSharing(){
    let peerConnection = currentConnection;
    navigator.mediaDevices.getDisplayMedia({video: true, audio: true}).then((disStream) => {
        // 把disStream存起来手动结束时候用得到
        this.desktopMediaStream = disStream;
        let srcObject = disStream;            
        peerConnection.getSenders().forEach((sender) => {
            if (sender.track.kind == 'video') {
                var res = sender.replaceTrack(disStream.getVideoTracks()[0]);
                console.log(res);
            };
        });
        /*
         注意`onended`事件仅适用于轨道对象(例如视频轨道),而`oninactive`事件适用于整个媒体流对象
        */
        // 1.监听屏幕结束方案1,也可以写成disStream.getVideoTracks()[0].onended = () => {}
        disStream.getVideoTracks()[0].addEventListener('ended', () => {
            // '屏幕共享结束, 准备切换为本地摄像头
            this.switchLocalCamera(peerConnection);
        });
        // 2.监听屏幕共享结束方案2,项目中我们用的是这种,因为有时候需要手动结束屏幕共享;
        disStream.oninactive = () => {
        }
    }).catch((error) => {
        console.error('屏幕共享失败,失败原因:', error);
    });
}
// 手动结束屏幕共享
endDesktopSharing() {
   const desktopMediaStream = this.desktopMediaStream;
    if (desktopMediaStream === null) {
        return;
    }
    desktopMediaStream.getTracks().forEach((track) => { track.stop(); })
    this.desktopMediaStream = null;
}
// 切换为本地摄像头
switchLocalCamera() {
    let peerConnection = currentConnection;
    let localStreams = curSession.connection.getLocalStreams();
    if (localStreams.length) {
        let localVideoTracks = localStreams[0].getVideoTracks();
        let localAudioTracks = localStreams[0].getAudioTracks();
        peerConnection.getSenders().forEach((sender) => {
            if (sender.track && sender.track.kind == 'video') {
                if (localVideoTracks.length) {
                    sender.replaceTrack(localVideoTracks[0]);
                }
            };
            if (sender.track && sender.track.kind == 'audio') {
                if (localAudioTracks) {
                    sender.replaceTrack(localAudioTracks[0]);
                }
            };
        });
    };
}
视频上传分辨率
// 视频上传分辨率
settingsScreenSize(constraints = { width: {exact: 50}, height: {exact: 50}}) {
    let tmpLocal = currentConnection.getLocalStreams()[0];
    const videoTrack = tmpLocal.getVideoTracks()[0];
    videoTrack.applyConstraints(constraints).then(() => {
        console.log('动态改变分辨率');
    });
}
开启/关闭音频
// 禁音
mute(){
    let pc = currentConnection;
    if (pc.getSenders) {
       pc.getSenders().forEach((sender) => {
          // 如果是视频的话 kind === 'video'
          if (sender.track.kind === 'audio') {
              sender.track.enabled = false
          }
       })
    } else {
       pc.getLocalStreams().forEach((stream) => {
           stream.getAudioTracks().forEach((track) => {
                // 如果是视频的话 kind === 'video'
               if (track.kind === 'audio') {
                   track.enabled = false;
               }
           })
       });
    }
}
// 解除禁音
unmute(){
    var pc = currentConnection;
    if (pc.getSenders) {
        pc.getSenders().forEach(function (sender) {
            // 如果是视频的话 kind === 'video'
            if (sender.track.kind === 'audio') {
                sender.track.enabled = true;
            }            
        });
    } else {
        pc.getLocalStreams().forEach(function (stream) {
            stream.getAudioTracks().forEach(function (track) {
                // 如果是视频的话 kind === 'video'
                if (track.kind === 'audio') {
                    track.enabled = true;
                }
            });
        });
    }
}
保持取消保持
// 接听
hold() { // 会话保持
    return new Promise((resolve, reject) => {
        if (!this.session) {
            reject(false);
        };
        let isHold = this.session.hold({useUpdate: false}, () => {
            resolve(true);
        });
        if (isHold) { // true 表示保持成功,你可以停止桌面共享,切换未本地摄像头
            //  this.switchLocalCamera();
            return;
        };
        return reject({code: 0, message: '保持失败'});
    });
}
unhold() { // 取消保持
    return new Promise((resolve, reject) => {
        if (!this.session) {
            reject(false);
        };
        let isUnHold = this.session.unhold({useUpdate: false}, () => {
            resolve(true);
        });
        if (isUnHold) {
            return;
        };
        return reject({code: 0, message: '取消保持失败'});
    });
}
结束是解除摄像头或麦克风占用
let peerConnection = currentConnection;
// 获取本地MediaStream对象
let localStreams = curSession.connection.getLocalStreams();
// 获取本地所有轨道
const curTracks = localStreams[0].getTracks();
// 解除轨道占用
curTracks.forEach(track => {
    track.stop();
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值