janus-manager
TypeScript icon, indicating that this package has built-in type declarations

1.3.11 • Public • Published

janus-manager

An adapter manager for Janus client library.

Janus 原文档地址可参考

基于 janus-manager Vue实现的 janus-connector 组件库可用于快速构建通讯应用。

Usage

  • install
npm install janus-manager --save
  • step 1:引入类库,初始化
import JanusManager from "janus-manager";

const manager = new JanusManager();
// 查看当前版本
const version = JanusManager.version;
  • step 2:服务器初始化,建立服务器连接
// 传入服务器地址,进行服务连接初始化,返回连接上下文对象
const context = await manager.init({ 
    server: this.server,
    // 设置连接超时时间,默认6000,60s
    longPollTimeout: 60000,
    // iceServers config
    /* iceServers: [{
        "urls": ["turn:xxxxx:xxxx"],
        "username": "username",
        "credential": "credential"
    }]*/
}).catch((err) => { 
    // 可处理兼容性及连接异常错误
    throw err 
});
  • step 3:传入 handle 插件配置,通过已建立的连接通道,将插件与通过建立关联,建立插件状态Session
// 创建一个可用于拨打电话的插件处理器,将插件与服务器建立关联,返回插件处理器对象,并进行消息监听
const handler = await manager.createCallHandle({
    // plugin options
});
handler.on("message", (msg, jsep) => {
    // handle events. 
    // eg: registered / hangup / accepted / progress / hangup | incomingcall

    // 处理接通事件
    const event = msg?.result?.event;
    if (event === "progress" || event === "accepted") {
        jsep && handler.handleRemoteJsep(jsep);
    }
}

// 处理音频流播放事件
handler.on("remotestream", (stream) => {
    manager.attachMediaStream(remoteMediaDom, stream);
});
  • step 4:发送注册消息,建立webrtc通讯能力
handler.sendMessage("register", {
    authuser: this.authuser,
    proxy: this.sipServer,
    secret: this.password,
    username: this.username,
    display_name: this.displayname,
});
  • step 5:拨打/接听/挂断/拒接 电话
// 拨打电话,通话地址 格式: sip:{phone}@ip:port
handler.makeCall(uri);

// 发送数字键盘命令信息
// 参数: tones - DTMF tones 
handler.sendDtmf({ tones: '1'});

// 挂断电话
handler.hangup();

// 接听电话 - 监听 incomingcall 事件
handler.on("message", (msg, jsep) => {
    const event = msg?.result?.event;
    if (event === "incomingcall") {
        handler.answerCall(jsep, { audio: true });
    }
}

// 拒接电话
handler.declineCall();

// 呼出端禁用音频输入
handler.muteAudio();
// 呼出端重新启用音频输入
handler.unmuteAudio();
// 判断呼出端是否启用音频输入,返回boolean
handler.isAudioMuted(); // true/false
  • step 6:页面退出前,销毁管理器实例
// 等同于  manager.destory();
context.destory();

其他说明

音视频环境调用检测

const isSupported = manager.isSupportedUserMedia();

可用于在通话前判断当前系统兼容性,是否支持音视频调用

异步方法使用

在如下方法调用时,注意为异步操作,同时根据逻辑决定是否需要捕获异常,若不catch,执行错误时,会默认抛出异常

  • 连接上下文创建:
await manager.init({ }).catch()
  • 通话对象创建:
await manager.createCallHandle({ }).catch()
  • 发送消息:
await handler.sendMessage({ }).catch()
  • 通话相关操作
// 拨打电话
await handler.makeCall({ }).catch()
// 挂断通话
await handler.hangup({ }).catch()
// 拒绝来电
await handler.declineCall({ }).catch()
// 接听来电
await handler.answerCall({ }).catch()

异常处理

  • 常规异常:所有Promise类型的方法调用异常,都可使用 catch 来捕获
  • 特殊异常:处理janus运行时的异常,如超时异常
const context = await manager.init({ 
    server: this.server,
    // 设置连接超时时间,默认6000,60s
    longPollTimeout: 60000,
})
.catch(error => {
    // 可处理兼容性及连接异常错误
});

context.error = (err: Error) => {
    // 一般网络连接超时,可在此处处理
}

调用主流程

  • 初始化管理器对象manager
  • 通过管理器对象创建服务连接上下文对象context
  • 使用manager创建业务操作对象handler
  • 使用操作对象handler进行通话操作(可多次调用相关方法)
  • 流程结束,释放相关连接资源 context.destorymanager.destory

以上操作除对handler对象进行相关操作外,其他流程只需要执行一次。

兼容性问题

  • 麦克风权限

    • Android中创建webview时,需先获取android.permission.RECORD_AUDIOandroid.permission.MODIFY_AUDIO_SETTINGS权限
    • 同样IOS中也需获取麦克风权限,可通过 AVAudioSession.sharedInstance().recordPermission 获取麦克风权限
  • 音视频自动播放

    • Android中可禁用自动播放需人工操作 webView.getSettings().setMediaPlaybackRequiresUserGesture(false)
    • IOS中也可进行相关配置,WKWebViewConfiguration().mediaTypesRequiringUserActionForPlayback 根据客户端使用语言或版本设置为 []WKAudiovisualMediaTypeNonfalse参考
  • 因系统支持问题,在Android系统版本 >=7.0 及 IOS系统版本 >=14.3才支持在webview中获取系统mediaDevices,才可正常使用库基础功能, 可通过实例方法 isSupportedUserMedia 来检测。

Demo

see call-demo

<template>
    <div class="page">
        <div class="home-container">
            <!-- 第一部分 启动服务 -->
            <div class="start-btn">
                <button type="primary" @click="start">启动服务</button>
            </div>

            <!-- 第二部分 注册服务配置 -->
            <div v-if="running" class="register-infor">
                <div>
                    <input
                        v-model="sipServer"
                        type="text"
                        placeholder="SIP服务器(例如, sip:10.10.10.10:5688)"
                    />
                </div>
                <div>
                    <input
                        v-model="username"
                        type="text"
                        placeholder="SIP身份(例如,sip:0000@10.10.10.10:5688)"
                    />
                </div>
                <div>
                    <input
                        v-model="authuser"
                        type="text"
                        placeholder="SIP号(例如, 1001)"
                    />
                </div>
                <div>
                    <input
                        v-model="password"
                        type="password"
                        placeholder="SIP密码"
                    />
                </div>
                <div>
                    <input
                        v-model="displayname"
                        type="text"
                        placeholder="显示名称 (例如, 806100)"
                    />
                </div>

                <div class="start-btn">
                    <button type="primary" @click="connect">建立连接</button>
                </div>
            </div>

            <!-- 第三部分 通话 -->
            <div v-if="connected" class="register-infor">
                <div>
                    <input
                        placeholder="请输入手机号 如: 13688886666"
                        v-model="phoneNumber"
                    />
                </div>
                <div class="start-btn">
                    <button
                        :class="{ primary: !calling, danger: calling }"
                        type="primary"
                        @click="callHandler"
                    >
                        {{ calling ? "挂断" : "拨打" }}
                    </button>
                </div>
            </div>

            <!-- 远程音频,接听电话时音频输入,需在 remotestream 事件中处理 -->
            <video ref="remoteMedia" class="hide" autoplay playsinline />
        </div>
    </div>
</template>
<script lang="ts">
import Vue from "vue";
import JanusManager from "janus-manager";

const manager = new JanusManager();

export default Vue.extend({
    data() {
        return {
            running: false,

            server: [
                "https://servername.com/janus",
                "wss://servername.com/janus",
            ],
            sipServer: "sip:server_ip:port", // SIP服务器
            username: "sip:user_name@server_ip:port", // SIP身份
            authuser: "user", // SIP号
            password: "password",
            displayname: "", // 显示名称

            connected: false,
            calling: false,
            phoneNumber: "",
        };
    },
    computed: {
        callNumber() {
            return `sip:${this.phoneNumber}@server_ip:port`;
        },
    },
    methods: {
        async start() {
            const context = await manager
                .init({ server: this.server })
                .catch((err) => {
                    console.error(err);
                    this.running = false;
                    throw err;
                });
            this.context = context;
            this.running = true;
        },
        async connect() {
            const handler = await manager.createCallHandle({
                mediaState: function (medium, on) {
                    const status = on ? "started" : "stopped";
                    const msg = `Janus ${status}  receiving our ${medium}`;
                    console.info("Janus mediaState:", msg);
                },
                webrtcState: function (on) {
                    const status = on ? "up" : "down";
                    const msg = `Janus says our WebRTC PeerConnection is ${status}`;
                    console.info("Janus webrtcState:", msg);
                },
                iceState: function (state) {
                    // state: 'connected' | 'failed'
                    console.info("iceState", state);
                },
            });

            handler.on("message", (msg, jsep) => {
                console.info("message event:", msg?.result?.event, jsep);
                const event = msg && msg.result && msg.result.event;
                // 注册成功
                if (event === "registered") {
                    this.connected = true;
                } else if (event === "hangup") {
                    // 通话挂断
                    console.warn("通话已挂断:", msg, jsep);
                    this.calling = false;
                    handler.hangup();
                } else if (event === "progress" || event === "accepted") {
                    // 接听
                    jsep && handler.handleRemoteJsep(jsep);
                }
            });

            handler.on("remotestream", (stream) => {
                manager.attachMediaStream(this.$refs.remoteMedia, stream);
            });

            console.log("connect success....");
            this.handler = handler;

            handler.sendMessage("register", {
                authuser: this.authuser,
                proxy: this.sipServer,
                secret: this.password,
                username: this.username,
                display_name: this.displayname,
            });
        },
        callHandler() {
            if (this.calling) {
                this.handler.hangup();
                this.calling = false;
                return;
            }

            if (this.phoneNumber === "") return;
            this.handler.makeCall(this.callNumber).catch(error => {
                throw error;
            });
        },
    },
    beforeDestroy() {
        this.context && this.context.destroy();
    },
});
</script>
<style lang="less" scoped>
.page {
    text-align: center;
    padding: 20px;
    display: flex;
    justify-content: center;

    .text-center {
        text-align: center;
    }

    .home-container {
        text-align: left;
        width: 300px;

        .hide {
            display: none;
        }

        input {
            height: 32px;
            line-height: 32px;
            border-radius: 4px;
            border: 1px solid #dcdfe6;
            padding: 0 15px;
            box-sizing: border-box;
            width: 100%;
        }
        button {
            border: none;
            color: #fff;
            padding: 10px 20px;
            border-radius: 4px;
            background-color: #357bff;
            cursor: pointer;
            &:focus {
                outline: none;
            }
            &:active {
                background-color: #2671fc;
            }
        }

        .primary {
            background-color: #357bff;
        }

        .danger {
            background-color: #f8483b;
        }

        .register-infor {
            margin-top: 10px;
            > div {
                margin-bottom: 10px;
            }
        }
    }
}
</style>

Readme

Keywords

Package Sidebar

Install

npm i janus-manager

Weekly Downloads

5

Version

1.3.11

License

ISC

Unpacked Size

739 kB

Total Files

10

Last publish

Collaborators

  • elicc