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 🙏

© 2026 – Pkg Stats / Ryan Hefner

easemob-emedia

v3.4.1

Published

webrtc

Readme

集成文档

[简介|功能说明|接入]

多人音视频Web SDK是以Websocket为通讯基础,WebRTC为协议,支持多种浏览器 即时多媒体通话功能。通过SDK可快速完成即时视频通话功能。特点概述:


接入

下载EMedia-3.0.0.x.zip并解压后。添加script标签引用。

集成

<script src="./webrtc/dist/EMedia_sdk-dev.js"></script>

创建

同一个标签页可支持创建多个Service。但几乎没有这种场景。所以不建议在销毁前创建多个service,尽管这是没有问题的。

var service = new emedia.Service({
    listeners: { //以下监听,this object == me == service.current
        //退出
        onMeExit: function (reason, failed) {
            reason = (reason || 0);
            switch (reason){
                case 0:
                    reason = "正常挂断";
                    break;
                case 1:
                    reason = "没响应";
                    break;
                case 2:
                    reason = "服务器拒绝";
                    break;
                case 3:
                    reason = "对方忙";
                    break;
                case 4:
                    reason = "失败,可能是网络或服务器拒绝";
                    if(failed === -9527){
                        reason = "失败,网络原因";
                    }
                    if(failed === -500){
                        reason = "Ticket失效";
                    }
                    if(failed === -502){
                        reason = "Ticket过期";
                    }
                    if(failed === -504){
                        reason = "链接已失效";
                    }
                    if(failed === -508){
                        reason = "会议无效";
                    }
                    if(failed === -510){
                        reason = "服务端限制";
                    }
                    break;
                case 5:
                    reason = "不支持";
                    break;
                case 10:
                    reason = "其他设备登录";
                    break;
                case 11:
                    reason = "会议关闭";
                    break;
            }
        },
        //member加入会议
        onAddMember: function (member) {
        },
        //member离开会议
        onRemoveMember: function (member) {
        },
        //收到一个业务流
        onAddStream: function (stream) {
        },
        //移除一个业务流
        onRemoveStream: function (stream) {
        },
        //业务流更新
        onUpdateStream: function (stream, update) {
        },
        //当前通话连接质量不佳
        onNetworkWeak: function () {
        },
        //各类事件
        onNotifyEvent: function (evt) {
            if(evt instanceof emedia.event.ServerRefuseEnter){ //服务端拒绝进入
            } else if(evt instanceof emedia.event.EnterSuccess){ //进入会议成功
            } else if(evt instanceof emedia.event.EnterFail){ //进入会议失败
            } else if(evt instanceof emedia.event.ICERemoteMediaStream) { //成功获取对方媒体数据 如:视频
            } else if(evt instanceof emedia.event.PushSuccess){ //推流成功。本地打开摄像头并将数据成功发送
            } else if(evt instanceof emedia.event.SubSuccess){ //订阅成功,可以看到对方了
            } else if(evt instanceof emedia.event.PushFail){ //推送失败
            } else if(evt instanceof emedia.event.SubFail){ //推送成功
            } else if(evt instanceof emedia.event.ShareDesktopExtensionNotFound){ //共享桌面插件未找到
            } else if(evt instanceof emedia.event.RemoteControlFail){ //远程控制失败,仅能web端控制sdk端
            }
        }
    }
});

初始化

加入会议的凭证 是 tkt,只要获取到tkt,便能工作

service.setup(tkt_string, {role: 'admin'}/*扩展字段*/);

member

...
onAddMember: function (member) {
},
onRemoveMember: function (member) {
},
...
{
    "ext":{ //service.setup(tkt_string, {role: 'admin'}/*扩展字段*/);
        "identity":"visitor",
        "nickname":"webim-visitor-9F72MEK793H97XQGFBKM",
        "avatarUrl":""
    },
    "nickName":"webim-visitor-9F72MEK793H97XQGFBKM",
    "id":"MS_X197721744293023744C19M197756407719972865VISITOR",
    "name":"a63ceeb6-64c2-426d-9803-a40dd5557d7c",
    "vcodes":[
        "VP8",
        "H264"
    ]
}

stream

...
//收到一个业务流
onAddStream: function (stream) {
},
//移除一个业务流
onRemoveStream: function (stream) {
},
//业务流更新
onUpdateStream: function (stream, update) {
    update.ifMediaStream(function (mediaStream) { //媒体数据发生变化
        video.srcObject = null;
        video.srcObject = mediaStream;
    });
    update.ifVoff(function (voff) { //对方 开启/关闭 视频
    });
    update.ifAoff(function (aoff) { //对方 开启/关闭 音频
    });
},
...
{
    "memId":"MS_X197721744293023744C19M197756407719972865VISITOR",
    "id":"RTC2__Of_C19M197756407719972865VISITOR",
    "voff":0, //1 视频关闭 
    "aoff":0, //1 音频关闭 
    "vcodes":[
        "VP8",
        "H264"
    ],
    "owner": member ,//member对象
    "rtcId":"RTC1",
    "subArgs":{
        "subSVideo":true,
        "subSAudio":true
    }
}

打开媒体设备:麦克风、摄像头

 //创建一个音视频流
var pubS = new service.AVPubstream({
    constaints: { //可缺省,麦克风 摄像头均打开;若设置,audio video 至少有一个为true
        audio: true|false, //默认true, 打开 麦克风
        video: true|false, //默认true, 打开 摄像头
    },
    aoff: 0, // 指定给对方 是否 关闭了音频, 默认0
    voff: 0, // 指定给对方 是否 关闭了视频, 默认0
    name: "video", // 视频名称(根据业务情况设定),可缺省
    ext: { //支持 媒体流 扩展描述,根据业务情况设定。此参数广播到其他人
        browser: app.appname
    }
});

//Chrome 浏览器
//桌面共享流,选装插件
//插件地址 https://turn2.easemob.com/simonweb/confr-http/rtc-share-desktop.crx
var pubS = new service.ShareDesktopPubstream({name: "共享桌面"}); 

//混音通话
var pubS = new service.AudioMixerPubstream({
    constaints: {
        video : "false" !== __video__,
    },
    aoff: 0
});

service.openUserMedia(pubS).then(function () {
    //打开媒体流成功
}, function fail(evt) { //打开媒体流失败
    //共享桌面抄件未找到
    if(evt instanceof emedia.event.ShareDesktopExtensionNotFound){ 
    }
    //设备可能不支持,比如 没有摄像头,或 被禁止访问摄像头
    if(evt instanceof emedia.event.OpenMediaError){ 
    }
});

加入会议

//在此之前 应该 service.setup(tkt_string, {role: 'admin'}/*扩展字段*/);
service.join(function onSuccess() {
});

加入会议,并推送媒体流

service.withpublish(pubS).join();
//在此之前应该openUserMedia。例如:
service.openUserMedia(pubS).then(
    function success(_user, stream) { //成功 video.srcObject = stream
        service.withpublish(pubS).join();
    },
    function fail(evt) {
        
    }
);

手动推送媒体流

service.push(pubS);

//在此之前应该openUserMedia 并且 join 成功。例如:
service.join(function onSuccess() {
});

service.openUserMedia(pubS).then(
    function success(_user, stream) { //成功 video.srcObject = stream
        service.push(pubS);
    },
    function fail(evt) {
        
    }
);

手动订阅对方媒体流

如果配置为自动订阅,可忽略,sdk会自动订阅 stream

onAddStream: function (stream) {
    service.subscribe(stream.id, function (_evt) {
        //订阅失败
    }, {subSVideo: true|false}); //true:订阅视频,false:订阅音频
},

开启/关闭自己摄像头

service.voff(stream, true|false, function fail(evt) {
});

开启/关闭自己麦克风

service.aoff(stream, true|false, function fail(evt) {
});

控制手机SDK摄像头,放大缩小

service.zoomRemote(stream.id, 2|0.5, function fail(evt){// 放大2倍,缩小0.5
});

抓拍手机SDK摄像头

service.capturePictureRemote(stream.id, false, 
    function success(base64Pic) {
    }, 
    function fail(evt) {
    }
);

控制手机SDK摄像头 聚焦;在video标签上添加点击事件

video.onclick = function (event) {
    if (document.all) { // for IE
        window.event.returnValue = false;
    } else {
        event.preventDefault();
    }

    if(stream.located()){
        displayEvent("Web本地摄像头不支持聚焦");
        return;
    }
    service.focusExpoRemote(stream.id, video, event, function fail(evt) {
        _logger.error("Oh,no.", evt.message())
    });
}

控制手机SDK摄像头 图像定格/恢复。

//freezeFramed true:图像定格
service.freezeFrameRemote(stream.id, function success(freezeFramed) {
}, function fail(evt, freezeFramed) {
});

SDK增强,开启支持远程控制

emedia.ctrl.support(service,
    function onHasRemoteControl(stream, controler, controlRequest){
        var rtn = confirm("同意 来自<" + controler.memName + ">对流:" + stream.id + "控制申请吗?")
        if(rtn){ //同意被控制
            var htmlId = stream.getHtmlDOMID();
            var $div = $('#' + htmlId);

            var $video = $div.find("#videoTag");
            var $videoTrack = $div.find("#video_box #track");
            var $canvas = $div.find("#video_box canvas");

            $videoTrack.show();
            $canvas.show();

            controlRequest.accept($video[0], new MouseTrack({
                _target: $videoTrack[0],
                _canvas: $canvas[0],
                _video: $video[0]
            }), new KeyboardTrack());
        }else{ //拒绝
            controlRequest.reject();
        }
    },

    function onRemoteFreeControl(stream, controler, cId) {
    	...    
    }
);

SDK增强,发起控制

function onReject(stream) { //被控端拒绝
}

function onBusy(stream) { //被控端忙
}

function onNotAllowRemoteControl(stream){ //被控端忙
}

function onRemoteControlTimeout(stream){ //被控端超时
}

function onAccept(stream){ //被控端接受
}

function onDisControlled(stream){ //断开控制链接
}
    
if($(videoObj).hasClass('mirror')){
    emedia.ctrl.mirrorControlled(service, stream.id, videoObj, targetDiv,
        onDisControlled, onAccept, onNotAllowRemoteControl, onRemoteControlTimeout, onReject, onBusy);
}else{
    emedia.ctrl.controlled(service, stream.id, videoObj, targetDiv,
        onDisControlled, onAccept, onNotAllowRemoteControl, onRemoteControlTimeout, onReject, onBusy);
}

退出会议

//退出会议,会回调onMeExit
//退出会议后,可再次setup ticket.
//setup生命周期 起, exit生命周期止
service.exit();

配置emedia

emedia.config({
    logLevel: 0, //日志级别,>5 关闭SDK,日志。默认级别 0       
        // TRACE: 0,
        // DEBUG: 1,
        // INFO: 2,
        // WARN: 3,
        // ERROR: 4,
        // FATAL: 5
    autoSub: true, //当收到对方媒体流时,自动订阅, 默认值:true
    enterTimeout: 20000, //单位ms,join 20秒内未返回 join失败,默认值20000
});

//重要常量
emedia.config.version //sdk版本
emedia.LOG_LEVEL //sdk日志级别,可在运行时通过控制台命令行 手动设置,可实时打开日志
emedia.isFirefox //true 火狐
emedia.isChrome  //true Chrome
emedia.isSafari  //true Safari
emedia.isWebRTC //true支持webrtc false不支持webrtc