npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

janus-manager

v1.3.11

Published

An adapter for Janus client library.

Downloads

19

Readme

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:[email protected]: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>