Newer
Older
safe_production_front / public / DispatchLib / DispatchRtcLib.js
wangxitong on 16 Aug 113 KB first commit

DispatchLib_add_scripts("adapter.js");
DispatchLib_add_scripts("tinySDP/tsdp_api.js");
DispatchLib_add_scripts("ffmpeg/RecordRTC.js");

//————————————————————————————————————— 配置 ——————————————————————————————————————————
var DispatchRtc_DebugVideo = false;
var DispatchRtc_RecordMp4 = true;

//————————————————————————————————————— DispatchRtcLib ——————————————————————————————————————————
function DispatchRtcLib() {
    this.index = 0;
    this.CallID = 0;
    this.CallType = -1;
    
    this.peerconnection = null;
    this.LocalVideoCandidates = new Array();
    this.LocalAudioCandidates = new Array();
        
    this.LocalSdp = null;           //本地sdp
    
    this.LocalAudioStream = null;	//本地音频媒体stream 
    this.LocalVideoStream = null;	//本地视频媒体stream
    this.LocalVideoLib = null;      //本地视频videolib
    
    this.RemoteStream = null;       //对端媒体stream
    this.RemoteSdp = null;          //对端sdp
    this.RemoteVideoLib = null;     //对端视频
    this.RemoteAudioLabel = null;   //audio标签 双向语音用 
    
    this.AudioRTCRtpSender = null;
    this.VideoRTCRtpSender = null;
    this.CurrentCaptureDev = "none";

    this.bytesReceived = 0;
    this.framesDecoded = 0;
    this.frameWidth = 0;
    this.frameHeight = 0;
    
    this.CandidateTimeout = null;
    
    this.CurrentMicphone = null;
}

var DispatchRtcLib_Array = new Array;


var DispatchRtcLib_MoniterVideo = 0;
var DispatchRtcLib_VoipAudio = 1;
var DispatchRtcLib_VoipVideo = 2;

var DispatchRtcLib_ConfAudio = 3;
var DispatchRtcLib_ConfVideoPush = 4;
var DispatchRtcLib_ConfVideoPull = 5;

//————————————————————————————————————— DispatchDevices ——————————————————————————————————————————
var DispatchDevices = {
    DevicesList: null,              //全部设备列表
    
    SpeakerList: new Array,              //扬声器列表
    MicphoneList: new Array,             //麦克风列表
    CameraList: new Array,               //摄像头
    
    UsingSpeaker: null,
    UsingMicphone: null,
    UsingCamera: null,

    CameraWidth: 1280,
    CameraHeight: 720,
    CameraFrameRate: 25,
};

//选择本地设备
DispatchDevices.setLocalDevice = function(Type, DevID)
{
    if (Type == 0)
    {
        //扬声器
        if (DispatchDevices.UsingSpeaker.deviceId == DevID)
        {
            return 0;
        }
        
        for (i = 0; i < DispatchDevices.SpeakerList.length; i++)
        {
            var speaker = DispatchDevices.SpeakerList[i];
            if (speaker.deviceId == DevID)
            {
                //扬声器
                DispatchDevices.UsingSpeaker = speaker;
                console.log("* 改变当前扬声器为" + DispatchDevices.UsingSpeaker.label);
                return 0;
            }
        }

        return -1;
    }
    else if	(Type == 1)
    {
        //麦克风
        if (DispatchDevices.UsingMicphone.deviceId == DevID)
        {
            return 0;
        }
        
        for (i = 0; i < DispatchDevices.MicphoneList.length; i++)
        {
            var micphone = DispatchDevices.MicphoneList[i];
            if (micphone.deviceId == DevID)
            {
                //扬声器
                DispatchDevices.UsingMicphone = micphone;
                console.log("* 改变当前麦克风为" + DispatchDevices.UsingMicphone.label);
                DispatchPocLib.MicphoneChanged();
                return 0;
            }
        }
        
        return -1;
    }
    else if	(Type == 2)
    {
        //摄像头
        if (DispatchDevices.UsingCamera.deviceId == DevID)
        {
            return 0;
        }
        
        for (var i = 0; i < DispatchDevices.CameraList.length; i++)
        {
            var camera = DispatchDevices.CameraList[i];
            if (camera.deviceId == DevID)
            {
                //扬声器
                DispatchDevices.UsingCamera = camera;
                console.log("* 改变当前摄像头为" + DispatchDevices.UsingCamera.label);
                return 0;
            }
        }
        
        return -1;
    }
    
    return -2;
}

//检测本地设备
DispatchDevices.checkLocalDevice = function(devicelist)
{
    DispatchDevices.DevicesList     = new Array;
    
    DispatchDevices.SpeakerList     = new Array;
    DispatchDevices.MicphoneList    = new Array;
    DispatchDevices.CameraList      = new Array;

    var getDevicesDone = function(devices)
    {
        DispatchDevices.DevicesList = devices;
        console.log("本地设备列表 开始 ——————————————————");
        
        var UsingSpeakerChanged = true;
        var UsingMicphoneChanged = true;
        var UsingCameraChanged = true;
        
        for (var i = 0; i < devices.length; i++) 
        {
            var thisdev = devices[i];
            var device = {
                deviceId: thisdev.deviceId,
                label: thisdev.label,
                groupId: thisdev.groupId,
                kind: thisdev.kind,
            };
            
            if (device.label.indexOf(" - ") == 0)
            {
                //客户端没有显示默认、通讯
                if (device.deviceId == "default")
                {
                    device.label = "默认" + device.label;
                }
                else if (device.deviceId == "communications")
                {
                    device.label = "通讯" + device.label;
                }
            }
            
            if (device.label.length > 11 &&
                device.label[device.label.length-1] == ")" &&
                device.label[device.label.length-6] == ":" &&
                device.label[device.label.length-11] == "(" &&
                device.label[device.label.length-12] == " ")
            {
                device.label = device.label.substring(0, device.label.length - 12);
            }
            
            if (device.kind == "audiooutput")
            {
                //扬声器
                if (device.deviceId != "communications")
                {
                    DispatchDevices.SpeakerList.push(device);
                }
                
                console.log("* 扬声器:" + device.label + " ID:" + device.deviceId);
                
                if (DispatchDevices.UsingSpeaker && 
                    device.label == DispatchDevices.UsingSpeaker.label)
                {
                    UsingSpeakerChanged = false;
                }
            }
            else if (device.kind == "audioinput")
            {
                //麦克风
                if (device.deviceId != "communications")
                {
                    DispatchDevices.MicphoneList.push(device);
                }

                console.log("* 麦克风:" + device.label + " ID:" + device.deviceId);
                
                if (DispatchDevices.UsingMicphone &&
                    device.label == DispatchDevices.UsingMicphone.label)
                {
                    UsingMicphoneChanged = false;
                }
            }
            else if (device.kind == "videoinput")
            {
                //摄像头
                
                DispatchDevices.CameraList.push(device);
                console.log("* 摄像头:" + device.label + " ID:" + device.deviceId);
                
                if (DispatchDevices.UsingCamera &&
                    device.label == DispatchDevices.UsingCamera.label)
                {
                    UsingCameraChanged = false;
                }
            }
        }

        if (UsingSpeakerChanged)    //扬声器
        {
            for (i = 0; i < DispatchDevices.SpeakerList.length; i++)
            {
                var speaker = DispatchDevices.SpeakerList[i];
                if (speaker.deviceId == "default")
                {                    
                    //设备变更
                    DispatchDevices.UsingSpeaker = speaker;
                    console.log("! 当前扬声器为" + DispatchDevices.UsingSpeaker.label);
                    break;
                }
            }
            
            if (DispatchDevices.SpeakerList.length == 0)
            {
                DispatchDevices.UsingSpeaker = null;
            }
            
            if (DispatchDevices.UsingSpeaker == null)
            {
                console.log("! 无可用扬声器");
            }
        }
        
        if (UsingMicphoneChanged)   //麦克风
        {
            for (i = 0; i < DispatchDevices.MicphoneList.length; i++)
            {
                var micro = DispatchDevices.MicphoneList[i];
                if (micro.deviceId == "default")
                {  
                    //设备变更
                    DispatchDevices.UsingMicphone = micro;
                    console.log("! 当前麦克风为" + DispatchDevices.UsingMicphone.label);
                    break;
                }
            }
            
            if (DispatchDevices.MicphoneList.length == 0)
            {
                DispatchDevices.UsingMicphone = null;
            }
            
            if (DispatchDevices.UsingMicphone == null)
            {
                console.log("! 无可用麦克风");
            }
        }
        
        if (UsingCameraChanged)
        {
            DispatchDevices.UsingCamera = null;
            if (DispatchDevices.CameraList.length == 0)
            {
                console.log("! 无可用摄像头");
            }
            else
            {
                DispatchDevices.UsingCamera = DispatchDevices.CameraList[0];
                console.log("! 当前摄像头为" + DispatchDevices.UsingCamera.label);
            }
        }
        
        console.log("本地设备列表 结束 ——————————————————");
        
        if (UsingMicphoneChanged) 
        {
            DispatchPocLib.MicphoneChanged();
        }
        
        if (DispatchDevices.SpeakerList.length > 0 &&
            DispatchDevices.UsingSpeaker == null)
        {
            //获取不到权限的情况,尝试打开设备再关闭
            var localmedia =
            {
                "audio": true,
                "video": true
            };
            
            if (DispatchDevices.MicphoneList.length == 0)
            {
                localmedia.audio = false;
            }
            
            if (DispatchDevices.CameraList.length == 0)
            {
                localmedia.video = false;
            }
            
            if (localmedia.audio || localmedia.video)
            {
                alert("初次加载,测试本地媒体设备,请赋予权限.");
                function success(stream) 
                {
                    for (var i = 0; i < stream.getTracks().length; i++)
                    {            
                        stream.getTracks()[i].stop();
                    }
                    
                    setTimeout(DispatchDevices.checkLocalDevice(), 1000);
                }
                    
                function error(error) 
                {
                    console.log("加载本地媒体设备失败,会影响使用.");
                }	
                
                navigator.getUserMedia(localmedia, success, error);
            }
    
        }
    }
    
    if (devicelist)
    {
        getDevicesDone(devicelist);
    }
    else
    {
        navigator.mediaDevices.enumerateDevices()
        .then(getDevicesDone)
        .catch(function(error)
        {
            console.log(error);
            
        });
    }
}

//————————————————————————————————————— DispatchRtcLib ——————————————————————————————————————————
DispatchRtcLib.Init = function()
{    
    // 获取谷歌浏览器版本
    console.log("DispatchRtcLib.Init");
    
    function getChromeVersion() {
        var arr = navigator.userAgent.split(' '); 
        var chromeVersion = '';
        for(var i=0;i < arr.length;i++){
            if(/chrome/i.test(arr[i]))
            chromeVersion = arr[i]
        }
        if(chromeVersion){
            return Number(chromeVersion.split('/')[1].split('.')[0]);
        } else {
            return false;
        }
    }
    
    if(getChromeVersion()) {
        var version = getChromeVersion();
        console.log("Chrome " + version);
    }

    function InitLocalMedia() {

        //生成本地虚拟头像
        DispatchRtcLib.LocalPhotoStream = null;
        
        var LocalPhoto_canvas = document.createElement('canvas');
        if (LocalPhoto_canvas.captureStream)
        {
            LocalPhoto_canvas.width = 320;
            LocalPhoto_canvas.height = 240;
            LocalPhoto_canvas.style.display = "none";
            document.body.appendChild(LocalPhoto_canvas);
            
            var ctx = LocalPhoto_canvas.getContext('2d');
            var img = new Image();
            img.onload = function()
            {
                ctx.drawImage(img, 0, 0);
                DispatchRtcLib.LocalPhotoStream = LocalPhoto_canvas.captureStream();
                DispatchRtcLib.LocalPhotoStream.bLocalPhoto = true;
                DispatchRtcLib.LocalPhotoStream.getVideoTracks()[0].bLocalPhoto = true;
                
                window.setInterval(function() 
                {
                    if (DispatchRtcLib.LocalPhotoStream)
                    {
                        ctx.drawImage(img, 0, 0);
                    }
                }, 500);
            }
            img.src = DispatchLibPath+"image/LocalPhoto.jpg";
        }

        //生成静音采集
        DispatchRtcLib.LocalMuteAudio = document.createElement('audio');
        if (DispatchRtcLib.LocalMuteAudio)
        {
            DispatchRtcLib.LocalMuteAudio.autoplay = true;
            DispatchRtcLib.LocalMuteAudio.loop = true;
            DispatchRtcLib.LocalMuteAudio.src = DispatchLibPath+"image/mute.mp3";
            DispatchRtcLib.LocalMuteAudio.onplay=function(){
                console.log("DispatchRtcLib.LocalMuteAudio.onplay");

            };
            
            //DispatchRtcLib.LocalMuteAudio.currentTime;
        }
        
        //媒体流信息处理
        window.setInterval(function() {
            for (var i = 0; i < DispatchRtcLib_Array.length; i++)
            { 
                if (DispatchRtcLib_Array[i] != null &&
                    DispatchRtcLib_Array[i].peerconnection &&
                    DispatchRtcLib_Array[i].RemoteVideoLib &&
                    DispatchRtcLib_Array[i].RemoteStream)
                {
                    if (DispatchRtcLib_Array[i].CallType == DispatchRtcLib_MoniterVideo ||
                        DispatchRtc_DebugVideo)
                    {
                        let rtcLib = DispatchRtcLib_Array[i];
                        rtcLib.peerconnection.getStats(null).then( (stats) => {
                                
                            var bytes_sec = 0;
                            var frames_sec = 0;
                            var packetsLost = 0;
                            var packetsReceived = 0;
                        
                            stats.forEach(report => {               
                                if (report.type == "inbound-rtp" &&
                                    report.mediaType == "video")
                                {
                                    bytes_sec = report.bytesReceived - rtcLib.bytesReceived;
                                    frames_sec = report.framesDecoded - rtcLib.framesDecoded;
                                
                                    packetsLost = report.packetsLost;
                                    packetsReceived = report.packetsReceived;
                                    
                                    rtcLib.bytesReceived = report.bytesReceived;
                                    rtcLib.framesDecoded = report.framesDecoded;
                                } 
                                else if (report.type == "track" &&
                                         report.remoteSource &&
                                         report.kind == "video")
                                {
                                    rtcLib.frameWidth = report.frameWidth;
                                    rtcLib.frameHeight = report.frameHeight;
                                }
                            });
                            
                            if (rtcLib.frameWidth && rtcLib.frameHeight)
                            {
                                rtcLib.RemoteVideoLib.setVideoText("H264", frames_sec, 8*bytes_sec/1024, rtcLib.frameWidth, rtcLib.frameHeight);                                               
                            }
                                
                        });
                    }
                }
            }
        }, 1000);
    }
    
    //初始化媒体
    if (navigator.mediaDevices)
    {
        //尝试获取本地媒体
        navigator.mediaDevices.enumerateDevices()
        .then(function(devices)
        {
            console.log("enumerateDevices");
            
            DispatchDevices.checkLocalDevice(devices);
            InitLocalMedia();
            
            var msgData = new Object;
            msgData.Msg = "MediaInitNotify";
            msgData.Type = 1;
            msgData.Reason = "MediaInit Sucess";
            
            DispatchCtrlLib.FuncCallback(msgData);
            
            //监听本地设备更改事件
            navigator.mediaDevices.ondevicechange = function(event)
            {			
                console.log("本地设备更新");       
                DispatchDevices.checkLocalDevice();
            }
        })
        .catch(function(error)
        {
            console.log(error);
            
            var failmsg = new Object;
            failmsg.Msg = "MediaInitNotify";
            failmsg.Type = 0;
            failmsg.Reason = error;
            
            DispatchCtrlLib.FuncCallback(failmsg);
        });

    }
    else
    {
        console.log("no navigator.mediaDevices");
        
        var failmsg = new Object;
        failmsg.Msg = "MediaInitNotify";
        failmsg.Type = 0;
        failmsg.Reason = "browser no authority";
        
        DispatchCtrlLib.FuncCallback(failmsg);
        
        return;
    }

    
    return 0;
}

//用于会议接听来电
DispatchRtcLib.VoipLastIncomingCallInfo = null;
DispatchRtcLib.VoipLastIncomingCallSdp = null;
DispatchRtcLib.OnMsgCallback = function(Msg)
{
    var ret = true;
    switch (Msg.Msg)
    {
        case "WebrtcSdpNotify":
        {
            DispatchRtcLib.SetRemoteSdp(Msg.CallID, Msg.WebrtcSdp, Msg.CallType);
            ret = false;
            DispatchRtcLib.VoipLastIncomingCallSdp = Msg;
            break;
        }
        case "VoipCallStatusNotify":
        {
            if (CurrentConference.CallID == Msg.CallID)
            {
                DispatchConference.RecvVoipCallStatus(Msg);
                ret = false;
                break;
            }
            
            if (Msg.Status > 2)
            {
                DispatchRtcLib.Stop(Msg.CallID);
            }
            
            DispatchVideoLib.func_call_onStatus(Msg.CallID, Msg.Status);
            
            break;
        }
        case "VoipDispatchCallRsp":
        {
            if (Msg.Result == 0 && Msg.CallID!= 0)
            {
                DispatchCtrlLib.VoipHandleIncomingCall(Msg.CallID, 1);
                
                
            }
            break;
        }
        case "VoipIncomingCallNotify":
        {
            DispatchRtcLib.VoipLastIncomingCallInfo = Msg;
            
            if (Msg.ConferenceInvite)  //通用版本暂不升级到会议接口
            {
                // if (CurrentConference.ConfID) //当前已在会议中
                // {
                //     ret = false;
                //     DispatchCtrlLib.VoipHandleIncomingCall(Msg.CallID, 0); //直接拒接
                // }
                // else
                // {
                    ret = false;
                                   
                    CurrentConference.CallID = Msg.CallID;
                    CurrentConference.ConfID = Msg.ExtNum;
                    CurrentConference.Type = Msg.CallType;
                    
                    var msg = new Object;
                    msg.Msg = "ConferenceInviteNotify";
                    msg.ConfID = Msg.ExtNum;
                    msg.Type = Msg.CallType;
                    
                    DispatchCtrlLib.FuncCallback(msg);
                // }
            }
            break;
        }
        case "MonitorVideoStatusNotify":
        {
            if (Msg.Status > 2)
            {
                DispatchRtcLib.Stop(Msg.CallID);
            }
            break;
        }
        case "ConferenceWsInviteNotify":
        {
            ret = false;
            DispatchConference.RecvWsInviteNotify(Msg);			
            break;
        }
        default:
        {           
            break;
        }
    }
    return ret;
}



    

DispatchRtcLib.getRtcLib = function(CallID, ForceNew)
{
    var rtcLib = null;
    for (var i = 0; i < DispatchRtcLib_Array.length; i++)
    { 
        if (DispatchRtcLib_Array[i] != null &&
            DispatchRtcLib_Array[i].CallID == CallID)
        {
            rtcLib = DispatchRtcLib_Array[i];
            console.log("Get DispatchRtcLib_" + rtcLib.index + ", CallID:" + rtcLib.CallID);
            break;
        }
    }
    
    if (rtcLib == null && ForceNew)
    {
        rtcLib = new DispatchRtcLib;
        rtcLib.CallID = CallID;
        
        var index = -1;

        for (var i = 0; i < DispatchRtcLib_Array.length; i++) 
        { 
            if (!DispatchRtcLib_Array[i] == null)
            {
                index = i;
                break;
            }
        }
        
        if (index != -1)
        {
            rtcLib.index = index;
            DispatchRtcLib_Array[index] = rtcLib;
        }
        else
        {
            rtcLib.index = DispatchRtcLib_Array.length;
            DispatchRtcLib_Array.push(rtcLib);
        }
        
        console.log("New DispatchRtcLib_" + rtcLib.index + ", CallID:" + rtcLib.CallID);
    }
     
    return rtcLib;
}

var OnAddStream  = function(event) 
{   
    var rtcLib = event.currentTarget.rtcLib;
    rtcLib.RemoteStream = event.stream; 
    
    if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
    {
        //单向视频
        if (rtcLib.RemoteVideoLib)
        {
            rtcLib.RemoteVideoLib.video.srcObject = rtcLib.RemoteStream;
            
            if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
            {
                //默认静音
                //rtcLib.RemoteVideoLib.video.muted = true;
                rtcLib.RemoteVideoLib.video.volume = 0;
            }
        }

    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
             rtcLib.CallType == DispatchRtcLib_ConfAudio)
    {
        //双向语音
        rtcLib.RemoteAudioLabel = document.createElement('audio');
        rtcLib.RemoteAudioLabel.id="DispatchAudio_" + rtcLib.index;
        rtcLib.RemoteAudioLabel.autoplay = true;
        document.body.appendChild(rtcLib.RemoteAudioLabel);
        
        rtcLib.RemoteAudioLabel.srcObject = rtcLib.RemoteStream;
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipVideo)
    {
        //双向视频
        if (rtcLib.RemoteVideoLib)
        {
            rtcLib.RemoteVideoLib.video.srcObject = rtcLib.RemoteStream;
        }
    }
    else if (rtcLib.CallType == DispatchRtcLib_ConfVideoPull)
    {
        //双向视频
        if (rtcLib.RemoteVideoLib)
        {
            rtcLib.RemoteVideoLib.video.srcObject = rtcLib.RemoteStream;
        }
    }
}

function isIPv4(ip){  
    var exp=/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;  
    var flag = ip.match(exp);  
    if(flag != undefined && flag!=""){  
        return true;  
    } else {  
        return false;  
    }  
}

DispatchRtcLib.CreateOffer = function(CallID, Type, OnCreateOfferSucess, OnCreateOfferFail, target) {

    var rtcLib = DispatchRtcLib.getRtcLib(CallID, true);
    rtcLib.CallType = Type;
    if (target)
    {        
        rtcLib.target = target; 
    }
    
    var localmedia =
    {
        audio: false,
        video: false,
        autoGainControl: true,
        echoCancellation: true,
        noiseSuppression: true,
    };
    
    var OnCandidateDone  = function() {
        
        if (rtcLib.CandidateTimeout)
        {
            clearTimeout(rtcLib.CandidateTimeout);
            rtcLib.CandidateTimeout = null;
        }
        console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Done");

        if (rtcLib.localsdp.indexOf("m=video") != -1 && rtcLib.LocalVideoCandidates.length == 0)
        {
            var port = Math.floor((Math.random() * 20000) + 40000);
            var candidate = "a=candidate:1 1 udp 1 169.254.28.42 " + port + " typ host";
            rtcLib.LocalVideoCandidates.push(candidate);
            console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Done Add Video Candidates: " + candidate);
        }

        if (rtcLib.localsdp.indexOf("m=audio") != -1 && rtcLib.LocalAudioCandidates.length == 0)
        {
            var port = Math.floor((Math.random() * 20000) + 40000);
            var candidate = "a=candidate:1 1 udp 1 169.254.28.42 " + port + " typ host";
            rtcLib.LocalAudioCandidates.push(candidate);
            console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Done Add Audio Candidates: " + candidate);
        }

        if (Type == DispatchRtcLib_MoniterVideo)
        {
            //console.log("compressLocalSdp ");
            //console.log(rtcLib.localsdp);
            //console.log(changesdp(rtcLib.localsdp, rtcLib.LocalVideoCandidates, rtcLib.LocalAudioCandidates));
            //OnCreateOfferSucess(compressLocalSdp(rtcLib.localsdp, rtcLib.LocalVideoCandidates, rtcLib.LocalAudioCandidates));
            OnCreateOfferSucess(changesdp(rtcLib.localsdp, rtcLib.LocalVideoCandidates, rtcLib.LocalAudioCandidates));
        }
        else
        {
            OnCreateOfferSucess(changesdp(rtcLib.localsdp, rtcLib.LocalVideoCandidates, rtcLib.LocalAudioCandidates));
        }
    }
    
    var OnIceCandidate  = function(event) {
        if (event.candidate &&
            event.candidate.protocol == "udp" &&
            isIPv4(event.candidate.address)) 
        {
            var v_m = event.currentTarget.localDescription.sdp.indexOf("m=video");
            var a_m = event.currentTarget.localDescription.sdp.indexOf("m=audio");
            if (v_m == -1)
            {
                a_m = 0; 
            }
            else if (a_m == -1)
            {
                v_m = 0;
            }
            else
            {
                if (v_m < a_m)
                {
                    v_m = 0;
                    a_m = 1;
                }
                else
                {
                    v_m = 1;
                    a_m = 0;
                }
            }
            
            if (event.candidate.sdpMid == a_m)
            {
                var can = event.candidate.candidate.split(" ");
                var candidate_str = "";
                for (var i = 0; i < can.length; i++)
                {            
                    var line = can[i];
                    if (line)
                    {
                        if (i == 0)
                        {
                            line = "candidate:1";
                        }
                        
                        if (i == 1)
                        {
                            line = "1";
                        }
                        
                        if (i == 3)
                        {
                            line = "1";
                        }
                        
                        if (line == "generation")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        if (line == "ufrag")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        if (line == "network-id")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        if (line == "network-cost")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        candidate_str += line;
                        candidate_str += " ";
                        
                    }
                }
                candidate_str = candidate_str.substr(0, candidate_str.length-1)
                
                var a = "a=" + candidate_str;
                rtcLib.LocalAudioCandidates.push(a);
                
                console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Audio  " + a);
            }
            else if (event.candidate.sdpMid == v_m)
            {
                var can = event.candidate.candidate.split(" ");
                var candidate_str = "";
                for (var i = 0; i < can.length; i++)
                {            
                    var line = can[i];
                    if (line)
                    {
                        if (i == 0)
                        {
                            line = "candidate:1";
                        }
                        
                        if (i == 1)
                        {
                            line = "1";
                        }
                        
                        if (i == 3)
                        {
                            line = "1";
                        }
                        
                        if (line == "generation")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        if (line == "ufrag")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        if (line == "network-id")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        if (line == "network-cost")
                        {
                            can[i+1] = null;
                            continue;
                        }
                        
                        candidate_str += line;
                        candidate_str += " ";
                        
                    }
                }
                candidate_str = candidate_str.substr(0, candidate_str.length-1)
                
                var a = "a=" + candidate_str;
                rtcLib.LocalVideoCandidates.push(a);
                
                console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Video  " + a);
            }   		
        } 
        else if (event.candidate && event.candidate.protocol == undefined && event.candidate.address == undefined)
        {			
            if (event.candidate.sdpMid == "audio")
            {
                var a = "a=" + event.candidate.candidate;
                rtcLib.LocalAudioCandidates.push(a);
                
                console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Audio  " + a);
            }
            else if (event.candidate.sdpMid == "video")
            {
                var a = "a=" + event.candidate.candidate;
                rtcLib.LocalVideoCandidates.push(a);
                
                console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Video  " + a);
            }

            console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Video  " + a);
        }
        else if (event.candidate == null) 
        {   		
            if (rtcLib.CandidateTimeout)
            {
                OnCandidateDone();
            }
        }
    }

    var OnConnectionStatechange  = function(event) {
        console.log("DispatchRtcLib " + rtcLib.CallID + " OnConnectionStatechange " + rtcLib.peerconnection.connectionState);
        
    }

    rtcLib.peerconnection = new RTCPeerConnection();
    rtcLib.peerconnection.rtcLib = rtcLib;
    rtcLib.peerconnection.onicecandidate = OnIceCandidate;
    rtcLib.peerconnection.onaddstream = OnAddStream;
    rtcLib.peerconnection.onconnectionstatechange = OnConnectionStatechange;

    var OnGetUserMediaSucess = function(stream)
    {
        var constraints = null;
        
        if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
        {
            //单向视频
            rtcLib.LocalVideoStream = null;
            rtcLib.LocalAudioStream = null;
            
            rtcLib.peerconnection.addTransceiver("audio", {direction:"recvonly"});
            rtcLib.peerconnection.addTransceiver("video", {direction:"recvonly"});
        }
        else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
                 rtcLib.CallType == DispatchRtcLib_ConfAudio)
        {
            //双向语音
            rtcLib.LocalVideoStream = null;
            rtcLib.LocalAudioStream = stream;
            
            if (rtcLib.peerconnection.addTrack)
            {
                for (var i = 0; i < stream.getTracks().length; i++)
                {            
                    var Track = stream.getTracks()[i];
                    if (Track.kind == "audio")
                    {
                        rtcLib.AudioRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                        rtcLib.LocalAudioStream = stream;
                        rtcLib.CurrentMicphone = Track.label;
                        console.log("AudioDev Using " + Track.label);	
                    }
                }
            }
            else
            {
                rtcLib.peerconnection.addStream(stream);
            }
        }
        else if (rtcLib.CallType == DispatchRtcLib_VoipVideo)
        {
            //双向视频
            if (rtcLib.peerconnection.addTrack)
            {
                for (var i = 0; i < stream.getTracks().length; i++)
                {            
                    var Track = stream.getTracks()[i];
                    if (Track.kind == "audio")
                    {
                        rtcLib.AudioRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                        rtcLib.LocalAudioStream = stream;
                        rtcLib.CurrentMicphone = Track.label;
                        console.log("AudioDev Using " + Track.label);						
                    }
                    else if (Track.kind == "video")
                    {
                        rtcLib.VideoRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                        rtcLib.LocalVideoStream = stream;
                        rtcLib.CurrentCaptureDev = Track.label;
                        console.log("VideoDev Using " + Track.label);
                    }
                    
                }
                
                if (!localmedia.video)
                {
                    //无本地摄像头,使用头像代替
                    var Track = DispatchRtcLib.LocalPhotoStream.getVideoTracks()[0];
                    console.log("peerconnection.addTrack " + Track.kind);
                    rtcLib.VideoRTCRtpSender = rtcLib.peerconnection.addTrack(Track, DispatchRtcLib.LocalPhotoStream);

                    if (rtcLib.LocalVideoLib)
                    {
                        rtcLib.LocalVideoLib.video.srcObject = DispatchRtcLib.LocalPhotoStream;
                    }
                }
                else
                {              
                    if (rtcLib.LocalVideoLib)
                    {
                        rtcLib.LocalVideoLib.video.srcObject = rtcLib.LocalVideoStream;
                        rtcLib.LocalVideoLib.video.muted = true;
                    }
                    
                }				
            }
            else
            {
                rtcLib.LocalVideoStream = stream;
                rtcLib.peerconnection.addStream(stream);
                if (rtcLib.LocalVideoLib)
                {
                    rtcLib.LocalVideoLib.video.srcObject = rtcLib.LocalVideoStream;
                    rtcLib.LocalVideoLib.video.muted = true;
                }
            }

        }		
        else if (rtcLib.CallType == DispatchRtcLib_ConfVideoPush)
        {
            //会议视频推送
            for (var i = 0; i < stream.getTracks().length; i++)
            {            
                var Track = stream.getTracks()[i];
                if (Track.kind == "video")
                {
                    rtcLib.VideoRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                    rtcLib.LocalVideoStream = stream;
                    rtcLib.CurrentCaptureDev = Track.label;
                    console.log("VideoDev Using " + Track.label);
                }
                
            }
            
            if (rtcLib.LocalVideoLib)
            {
                rtcLib.LocalVideoLib.video.srcObject = rtcLib.LocalVideoStream;
                rtcLib.LocalVideoLib.video.muted = true;
            }
 
            constraints = {
                            offerToReceiveVideo:false,
                            offerToReceiveAudio:false
                         };
        }
        else if (rtcLib.CallType == DispatchRtcLib_ConfVideoPull)
        {
            rtcLib.LocalVideoStream = null;
            rtcLib.LocalAudioStream = null;
            
            //rtcLib.peerconnection.addTransceiver("video", {direction:"recvonly"});
            rtcLib.peerconnection.addTransceiver("video", {direction:"recvonly"});
        }
        
        var OnPcCreateOfferSucess = function(offer)
        {
            rtcLib.localsdp = offer.sdp;
            
            var OnSetLocalDescriptionSucess = function()
            {
                console.log("OnSetLocalDescriptionSucess");
            }
            
            var OnSetLocalDescriptionFail = function(exception)
            {
                console.log("OnSetLocalDescriptionFail " + exception);
                OnCreateOfferFail("初始化本地媒体失败");
            }
            
            rtcLib.peerconnection.setLocalDescription(offer, OnSetLocalDescriptionSucess, OnSetLocalDescriptionFail);
            rtcLib.CandidateTimeout = setTimeout(OnCandidateDone, 2000);
        }
        
        var OnPcCreateOfferFail = function(error)
        {
            console.log("OnCreateOfferFail " + error);
            OnCreateOfferFail("初始化本地媒体失败");
        }

        rtcLib.peerconnection.createOffer(OnPcCreateOfferSucess, OnPcCreateOfferFail, constraints);
    }
    
    var OnGetUserMediaFail = function(error)
    {
        console.log("打开本地设备失败 " + error.code + " : "+ error.message);
        OnCreateOfferFail("打开本地设备失败");
    }
    

    if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
    {
        //单向视频
        OnGetUserMediaSucess(null);    
        return CallID;
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
             rtcLib.CallType == DispatchRtcLib_ConfAudio)
    {
        //双向语音
        if (navigator.mediaDevices)
        {
            if (DispatchDevices.SpeakerList.length == 0)
            {
                //无麦克风或扬声器
                console.log("本地无可用扬声器");
                DispatchRtcLib_Array[rtcLib.index] = null;
                rtcLib = null;
                return -1;
            }
            
            if (DispatchDevices.MicphoneList.length == 0)
            {
                //无麦克风或扬声器
                console.log("本地无可用麦克风");
                DispatchRtcLib_Array[rtcLib.index] = null;
                rtcLib = null;
                return -2;
            }
        }

        localmedia.audio = {deviceId: DispatchDevices.UsingMicphone.deviceId};
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipVideo)
    {
        //双向视频
        if (navigator.mediaDevices)
        {
            if (DispatchDevices.SpeakerList.length == 0)
            {
                //无麦克风或扬声器
                console.log("本地无可用扬声器");
                DispatchRtcLib_Array[rtcLib.index] = null;
                rtcLib = null;
                return -1;
            }
            
            if (DispatchDevices.MicphoneList.length == 0)
            {
                //无麦克风或扬声器
                console.log("本地无可用麦克风");
                DispatchRtcLib_Array[rtcLib.index] = null;
                rtcLib = null;
                return -2;
            }
            
            localmedia.audio = {deviceId: DispatchDevices.UsingMicphone.deviceId};

            if (DispatchDevices.CameraList.length > 0)
            {
                //localmedia.video = true;
                localmedia.video = { 
                    width: DispatchDevices.CameraWidth, 
                    height: DispatchDevices.CameraHeight, 
                    frameRate: DispatchDevices.CameraFrameRate, 
                    deviceId: DispatchDevices.UsingCamera.deviceId
                };
            }
        }
        else
        {
            localmedia.audio = true;
            localmedia.video = true;
        }
    }
    else if (rtcLib.CallType == DispatchRtcLib_ConfVideoPush)
    {
        //视频推送
        if (navigator.mediaDevices)
        {
            if (DispatchDevices.CameraList.length > 0)
            {
                //localmedia.video = true;
                localmedia.video = { 
                    width: DispatchDevices.CameraWidth, 
                    height: DispatchDevices.CameraHeight, 
                    frameRate: DispatchDevices.CameraFrameRate, 
                    deviceId: DispatchDevices.UsingCamera.deviceId
                };
            }
            else
            {
                OnGetUserMediaSucess(DispatchRtcLib.LocalPhotoStream);
                return CallID;
            }
        }
        else
        {
            localmedia.audio = false;
            localmedia.video = true;
        }
    }
    if (rtcLib.CallType == DispatchRtcLib_ConfVideoPull)
    {
        //单向视频
        OnGetUserMediaSucess(null);    
        return CallID;
    }
    
    getUserMedia(localmedia, OnGetUserMediaSucess, OnGetUserMediaFail);
    console.log("尝试打开本地设备 " + localmedia.toString());
    
    return CallID;
}

DispatchRtcLib.ModifySdp = function(sdp, remoteip)
{
    if (remoteip.length == 0)
    {
        return sdp;
    }

    console.log("DispatchRtcLib.ModifySdp " + remoteip);
    
    var lineArray = sdp.split("\r\n");

    for (var i = 0; i < lineArray.length; i++)
    {            
        var line = lineArray[i];
        if (line.indexOf("a=candidate") == 0)
        {
            //       0      1  2       3           4         5    6   7 
            //a=candidate:1 1 UDP 2015363327 192.168.0.209 15149 typ host
            //a=candidate:3 1 TCP 1010827519 192.168.0.209 15025 typ host tcptype passive
            var candidateArray = line.split(" ");
            candidateArray[4] = remoteip;
            lineArray[i] = candidateArray.join(" ");
        }	
    }
    
    var sdp_after = lineArray.join("\r\n");
    return sdp_after;
}

DispatchRtcLib.SetRemoteSdp = function(CallID, RemoteSdp, CallType) 
{   
    var rtcLib = DispatchRtcLib.getRtcLib(CallID, true);

    rtcLib.RemoteSdp = RemoteSdp;
    rtcLib.CallType = CallType;
    
    if (rtcLib.CallType == DispatchRtcLib_MoniterVideo ||
        rtcLib.CallType == DispatchRtcLib_ConfVideoPush ||
        rtcLib.CallType == DispatchRtcLib_ConfVideoPull)
    {
        rtcLib.RemoteSdp = DispatchRtcLib.ModifySdp(rtcLib.RemoteSdp, DispatchCtrlLib.msip); 
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
             rtcLib.CallType == DispatchRtcLib_VoipVideo ||
             rtcLib.CallType == DispatchRtcLib_ConfAudio)
    {
        rtcLib.RemoteSdp = DispatchRtcLib.ModifySdp(rtcLib.RemoteSdp, DispatchCtrlLib.fsip);
    }
    
    if (rtcLib.peerconnection)
    {
        var remotesdp = new RTCSessionDescription();
        remotesdp.type = "answer";
        
        remotesdp.sdp = rtcLib.RemoteSdp;
        
        var OnSetRemoteDescriptionSucess = function()
        {
            console.log("OnSetRemoteDescriptionSucess");
        }

        var OnSetRemoteDescriptionFail = function(exception)
        {
            console.log("OnSetRemoteDescriptionFail " + exception);
            
            if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
            {
                //单向视频                
                var failmsg = new Object;
                failmsg.Msg = "MonitorVideoStatusNotify";
                failmsg.CallID = CallID;
                failmsg.Status = 7;
                failmsg.Reason = exception.toString();
                
                DispatchCtrlLib.FuncCallback(failmsg);
                
                DispatchCtrlLib.MonitorVideoHangup(CallID);
                
                DispatchRtcLib.Stop(CallID);
            }
            else
            {
                //双向语音 双向视频
                var failmsg = new Object;
                failmsg.Msg = "VoipCallStatusNotify";
                failmsg.CallID = CallID;
                failmsg.Status = 7;
                failmsg.Reason = exception.toString();
                
                DispatchCtrlLib.FuncCallback(failmsg);
                
                DispatchCtrlLib.VoipHangupCall(CallID);
                
                DispatchRtcLib.Stop(CallID);
            }
        }

        rtcLib.peerconnection.setRemoteDescription(remotesdp, OnSetRemoteDescriptionSucess, OnSetRemoteDescriptionFail);
    }
}

DispatchRtcLib.CreateAnswer = function(CallID, OnCreateAnswerSucess, OnCreateAnswerFail) 
{  
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);

    if (rtcLib == null ||
        rtcLib.RemoteSdp == null)
    {
        console.log("初始化本地媒体失败");
        return -3;
    }

    var OnCandidateDone  = function() {
        
        if (rtcLib.CandidateTimeout)
        {
            clearTimeout(rtcLib.CandidateTimeout);
            rtcLib.CandidateTimeout = null;
        }
        console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Done");
        OnCreateAnswerSucess(changesdp(rtcLib.localsdp, rtcLib.LocalVideoCandidates, rtcLib.LocalAudioCandidates));
    }

    var OnIceCandidate = function(event) 
    {
        if (event.candidate &&
            event.candidate.protocol == "udp" &&
            isIPv4(event.candidate.address)) 
        {
            var v_m = event.currentTarget.localDescription.sdp.indexOf("m=video");
            var a_m = event.currentTarget.localDescription.sdp.indexOf("m=audio");
            if (v_m == -1)
            {
                a_m = 0; 
            }
            else if (a_m == -1)
            {
                v_m = 0;
            }
            else
            {
                if (v_m < a_m)
                {
                    v_m = 0;
                    a_m = 1;
                }
                else
                {
                    v_m = 1;
                    a_m = 0;
                }
            }
            
            if (event.candidate.sdpMid == a_m)
            {
                var a = "a=" + event.candidate.candidate;
                rtcLib.LocalAudioCandidates.push(a);
                
                console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Audio  " + a);
            }
            else if (event.candidate.sdpMid == v_m)
            {
                var a = "a=" + event.candidate.candidate;
                rtcLib.LocalVideoCandidates.push(a);
                
                console.log("DispatchRtcLib " + rtcLib.CallID + "OnIceCandidate Video  " + a);
            }
            
        } 
        else if (event.candidate == null) 
        {
            if (rtcLib.CandidateTimeout)
            {
                OnCandidateDone();
            }
        }
    }

    rtcLib.peerconnection = new RTCPeerConnection();
    rtcLib.peerconnection.rtcLib = rtcLib;
    rtcLib.peerconnection.onicecandidate = OnIceCandidate;
    rtcLib.peerconnection.onaddstream = OnAddStream;

    var localmedia =
    {
        audio: false,
        video: false,
        autoGainControl: true,
        echoCancellation: true,
        noiseSuppression: true,
    };

    var OnGetUserMediaSucess = function(stream)
    {
        if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
        {
            //单向视频
            rtcLib.LocalVideoStream = null;
            rtcLib.LocalAudioStream = null;
            
            rtcLib.peerconnection.addTransceiver("audio", {direction:"recvonly"});
            rtcLib.peerconnection.addTransceiver("video", {direction:"recvonly"});
        }
        else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
                 rtcLib.CallType == DispatchRtcLib_ConfAudio)
        {
            //双向语音
            rtcLib.LocalVideoStream = null;
            rtcLib.LocalAudioStream = stream;
            
            for (const Track of stream.getTracks())
            {            
                if (Track.kind == "audio")
                {
                    rtcLib.AudioRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                    rtcLib.LocalAudioStream = stream;
                    rtcLib.CurrentMicphone = Track.label;
                    console.log("AudioDev Using " + Track.label);	
                }
            }
        }
        else if (rtcLib.CallType == DispatchRtcLib_VoipVideo)
        {
            //双向视频
            for (const Track of stream.getTracks())
            {
                if (Track.kind == "audio")
                {
                    rtcLib.AudioRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                    rtcLib.LocalAudioStream = stream;
                    rtcLib.CurrentMicphone = Track.label;
                    console.log("AudioDev Using " + Track.label);
                }
                else if (Track.kind == "video")
                {
                    rtcLib.VideoRTCRtpSender = rtcLib.peerconnection.addTrack(Track, stream);
                    rtcLib.LocalVideoStream = stream;
                    rtcLib.CurrentCaptureDev = Track.label;
                    console.log("VideoDev Using " + Track.label);
                }
                
            }
            
            if (!localmedia.video)
            {
                //无本地摄像头,使用头像代替
                var Track = DispatchRtcLib.LocalPhotoStream.getVideoTracks()[0];
                console.log("peerconnection.addTrack " + Track.kind);
                rtcLib.VideoRTCRtpSender = rtcLib.peerconnection.addTrack(Track, DispatchRtcLib.LocalPhotoStream);
                
                if (rtcLib.LocalVideoLib)
                {
                    rtcLib.LocalVideoLib.video.srcObject = DispatchRtcLib.LocalPhotoStream;
                }
            }
            else
            {              
                if (rtcLib.LocalVideoLib)
                {
                    rtcLib.LocalVideoLib.video.srcObject = rtcLib.LocalVideoStream;
                    rtcLib.LocalVideoLib.video.muted = true;
                }
            }
            
        }
        
        var OnSetRemoteDescriptionSucess = function(offer)
        {
            var OnPcCreateAnswerSucess = function(answer)
            {
                console.log("OnPcCreateAnswerSucess");
                rtcLib.localsdp = answer.sdp;
    
                var OnSetLocalDescriptionSucess = function()
                {
                    console.log("OnSetLocalDescriptionSucess");
                }
                
                var OnSetLocalDescriptionFail = function(exception)
                {
                    console.log("OnSetLocalDescriptionFail " + exception.toString());
                    OnCreateAnswerFail(exception.toString());
                }

                console.log(answer.sdp);
                rtcLib.peerconnection.setLocalDescription(answer, OnSetLocalDescriptionSucess, OnSetLocalDescriptionFail);  
                rtcLib.CandidateTimeout = setTimeout(OnCandidateDone, 2000);
            }
            
            var OnPcCreateAnswerFail = function(exception)
            {
                console.log("OnPcCreateAnswerFail " + exception.toString());
                OnCreateAnswerFail(exception.toString());
            }
            
            rtcLib.peerconnection.createAnswer(OnPcCreateAnswerSucess, OnPcCreateAnswerFail);
        }
        
        var OnSetRemoteDescriptionFail = function(error)
        {
            console.log("OnSetRemoteDescriptionFail " + error.toString());
            OnCreateAnswerFail(error.toString());
        }
        
        rtcLib.RemoteSdp = changesdp_local(rtcLib.RemoteSdp);
        
        var remotesdp = new RTCSessionDescription();
        remotesdp.type = "offer";
        remotesdp.sdp = rtcLib.RemoteSdp;
        
        rtcLib.peerconnection.setRemoteDescription(remotesdp, OnSetRemoteDescriptionSucess, OnSetRemoteDescriptionFail);
    }
    
    var OnGetUserMediaFail = function(error)
    {
        console.log("打开本地设备失败 " + error.code + " : "+ error.message);
        OnCreateAnswerFail("打开本地设备失败");
    }

    if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
    {
        //单向视频
        OnGetUserMediaSucess(null);    
        return 0;
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
             rtcLib.CallType == DispatchRtcLib_ConfAudio)
    {
        //双向语音
        if (DispatchDevices.SpeakerList.length == 0)
        {
            //无麦克风或扬声器
            console.log("本地无可用扬声器");
            DispatchRtcLib_Array[rtcLib.index] = null;
            rtcLib = null;
            return -1;
        }
        
        if (DispatchDevices.MicphoneList.length == 0)
        {
            //无麦克风或扬声器
            console.log("本地无可用麦克风");
            DispatchRtcLib_Array[rtcLib.index] = null;
            rtcLib = null;
            return -2;
        }

        localmedia.audio = {deviceId: DispatchDevices.UsingMicphone.deviceId};
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipVideo)
    {
        //双向视频
        if (DispatchDevices.SpeakerList.length == 0)
        {
            //无麦克风或扬声器
            console.log("本地无可用扬声器");
            DispatchRtcLib_Array[rtcLib.index] = null;
            rtcLib = null;
            return -1;
        }
        
        if (DispatchDevices.MicphoneList.length == 0)
        {
            //无麦克风或扬声器
            console.log("本地无可用麦克风");
            DispatchRtcLib_Array[rtcLib.index] = null;
            rtcLib = null;
            return -2;
        }
        
        localmedia.audio = {deviceId: DispatchDevices.UsingMicphone.deviceId};

        if (DispatchDevices.CameraList.length > 0)
        {
            //localmedia.video = true;
            localmedia.video = { 
                width: DispatchDevices.CameraWidth, 
                height: DispatchDevices.CameraHeight, 
                frameRate: DispatchDevices.CameraFrameRate, 
                deviceId: DispatchDevices.UsingCamera.deviceId
            };
        }
    }
    
    getUserMedia(localmedia, OnGetUserMediaSucess, OnGetUserMediaFail);
}

DispatchRtcLib.DisplayVideo = function(CallID, VideoLib) 
{  
    if (CallID == -1)
    {
        for (var i = 0; i < DispatchRtcLib_Array.length; i++)
        { 
            if (DispatchRtcLib_Array[i] != null &&
               (DispatchRtcLib_Array[i].CallType == DispatchRtcLib_VoipVideo ||
                DispatchRtcLib_Array[i].CallType == DispatchRtcLib_ConfVideoPush))
            {
                DispatchRtcLib_Array[i].LocalVideoLib = VideoLib;
                
                if (DispatchRtcLib_Array[i].LocalVideoStream)
                {
                    VideoLib.video.srcObject = DispatchRtcLib_Array[i].LocalVideoStream;
                    VideoLib.video.muted = true;
                }
                else
                {
                    VideoLib.video.srcObject = DispatchRtcLib.LocalPhotoStream;
                } 
                if (DispatchRtcLib_Array[i].CallType == DispatchRtcLib_MoniterVideo)
                {
                    VideoLib.flag_bMoniterVideo = true;
                }
                VideoLib.rtclib = DispatchRtcLib_Array[i];
                break;
            }
        }
    }
    else
    {
        var rtcLib = DispatchRtcLib.getRtcLib(CallID);
        if (rtcLib)
        {
            rtcLib.RemoteVideoLib = VideoLib;
            
            if (rtcLib.RemoteStream)
            {
                VideoLib.video.srcObject = rtcLib.RemoteStream;
                
                if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
                {
                    //默认静音
                    //VideoLib.video.muted = true;
                    VideoLib.video.volume = 0;
                    VideoLib.flag_bMoniterVideo = true;
                }
            }   
            
            if (rtcLib.CallType == DispatchRtcLib_MoniterVideo)
            {
                VideoLib.flag_bMoniterVideo = true;
            }
            
            VideoLib.rtclib = rtcLib;
        }

    }
}

DispatchRtcLib.Stop = function(CallID) 
{  
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null)
    {
        return;
    }
    
    if (rtcLib.RemoteStream)
    {       
        for (var i = 0; i < rtcLib.RemoteStream.getTracks().length; i++)
        {            
            rtcLib.RemoteStream.getTracks()[i].stop();
        }
        
        rtcLib.RemoteStream = null;
    }

    if (rtcLib.LocalVideoStream &&
        !rtcLib.LocalVideoStream.bLocalPhoto)
    {       
        for (var i = 0; i < rtcLib.LocalVideoStream.getTracks().length; i++)
        {            
            rtcLib.LocalVideoStream.getTracks()[i].stop();
        }
        
        rtcLib.LocalVideoStream = null;
    }
    
    if (rtcLib.LocalAudioStream)
    {       
        for (var i = 0; i < rtcLib.LocalAudioStream.getTracks().length; i++)
        {            
            rtcLib.LocalAudioStream.getTracks()[i].stop();
        }

        rtcLib.LocalAudioStream = null;
    }
    
    if (rtcLib.LocalVideoLib)
    {
        rtcLib.LocalVideoLib.StopDisplay();
        rtcLib.LocalVideoLib.video.muted = false;
        rtcLib.LocalVideoLib = null;
    }
    
    if (rtcLib.RemoteVideoLib)
    {
        rtcLib.RemoteVideoLib.StopDisplay();
        rtcLib.RemoteVideoLib.video.muted = false;
        rtcLib.RemoteVideoLib = null;
    }
    
    if (rtcLib.RemoteAudioLabel)
    {        
        document.body.removeChild(rtcLib.RemoteAudioLabel);
        rtcLib.RemoteAudioLabel = null;
    }

    if (rtcLib.peerconnection)
    {   
        rtcLib.peerconnection.close();
        rtcLib.peerconnection = null;
    }
    
    
    console.log("DispatchRtcLib.Stop " + rtcLib.index + " CallID: " + CallID);
    DispatchRtcLib_Array[rtcLib.index] = null;
    rtcLib = null;
}

DispatchRtcLib.VoipSetCamera = function(CallID, VideoSrc)
{
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null ||
        (rtcLib.CallType != DispatchRtcLib_VoipVideo && rtcLib.CallType != DispatchRtcLib_ConfVideoPush))
    {
        return -1;
    }
    
    if (rtcLib.CurrentCaptureDev == VideoSrc)
    {
        return 0;
    }

    var old_track = rtcLib.VideoRTCRtpSender.track;
    old_track.enabled = false;
    
    var Constraints = old_track.getConstraints();
    console.log(Constraints);
    
    var replaceVideoTrack = function(stream)
    {
        stream.getVideoTracks()[0].enabled = false;
        
        rtcLib.VideoRTCRtpSender.replaceTrack(stream.getVideoTracks()[0])
        .then(() =>
        {
            console.log("replaceTrack Sucess to " + VideoSrc);
            
            setTimeout(function(){
                stream.getVideoTracks()[0].enabled = true;
            }, 1000);

            
            if (rtcLib.CurrentCaptureDev == "screen")
            {               
                if (rtcLib.CallType == DispatchRtcLib_ConfVideoPush)
                {
                    var xml = 
                    "<VOIP>" +
                    "<Command-Name>ConferenceScreenShareEnd</Command-Name>" +
                    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
                    "<Command-ID></Command-ID>" +
                    "<Command-Info>" +
                        "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
                    "</Command-Info>" +
                    "</VOIP>\r\n";
                    
                    DispatchConference.WsSend(xml);
                }
                
                //防止OnDisplayMediaInactive
                rtcLib.LocalVideoStream.ForbiddenInactive = true;
            }
            else if (VideoSrc == "screen")
            {
                if (rtcLib.CallType == DispatchRtcLib_ConfVideoPush)
                {
                    var xml = 
                    "<VOIP>" +
                    "<Command-Name>ConferenceScreenShareStart</Command-Name>" +
                    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
                    "<Command-ID></Command-ID>" +
                    "<Command-Info>" +
                        "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
                    "</Command-Info>" +
                    "</VOIP>\r\n";
                    
                    DispatchConference.WsSend(xml);
                }
            }

            rtcLib.CurrentCaptureDev = VideoSrc;
            
            if (rtcLib.LocalVideoLib)
            {
                rtcLib.LocalVideoLib.video.srcObject = stream;
                rtcLib.LocalVideoLib.video.muted = true;
            }
            rtcLib.LocalVideoStream = stream;
            if (!old_track.bLocalPhoto)
            {
                old_track.stop();
            }
        })
        .catch(error => 
        {
            console.log(error);
        });
    }
    
    
    if (VideoSrc == "screen")
    {		        
        if (rtcLib.CallType == DispatchRtcLib_ConfVideoPush)
        {
            //屏幕共享时,判断会议中是否有人屏幕共享中
            if (CurrentConference.ScreenShare.length != 0)
            {
                console.log("会议中有其他人正在桌面共享 " + CurrentConference.ScreenShare);
                return -5;
            }
        }
        
        var OnGetDisplayMediaSucess = function(stream)
        {
            var OnDisplayMediaInactive = function(event)
            {
                console.log("停止桌面共享");
                
                if (rtcLib.LocalVideoStream.ForbiddenInactive)
                {
                    return;
                }
                
                if (DispatchDevices.CameraList.length == 0)
                {
                    setTimeout(DispatchRtcLib.VoipSetCamera(CallID, "none"), 1000);
                }
                else
                {
                    setTimeout(DispatchRtcLib.VoipSetCamera(CallID, DispatchDevices.CameraList[0].label), 1000);
                }
            }
            
            stream.addEventListener("inactive", OnDisplayMediaInactive);
             
            replaceVideoTrack(stream);
        }
        
        navigator.mediaDevices.getDisplayMedia({video:true})

        .then(stream =>
        {
            OnGetDisplayMediaSucess(stream);
        })
        .catch(error => 
        {
            console.log(error);
        });
    }
    else if (VideoSrc == "none")
    {
        replaceVideoTrack(DispatchRtcLib.LocalPhotoStream);
    }
    else
    {
        var device = null;
        for (var i = 0; i < DispatchDevices.CameraList.length; i++)
        {
            var camera = DispatchDevices.CameraList[i];
            if (camera.label == VideoSrc)
            {
                device = camera.deviceId;
                break;
            }
        }
        
        if (device)
        {
            var localmedia =
            {
                audio: false,
                video: false,
                autoGainControl: true,
                echoCancellation: true,
                noiseSuppression: true,
            };
            
            localmedia.video = { 
                width: DispatchDevices.CameraWidth, 
                height: DispatchDevices.CameraHeight, 
                frameRate: DispatchDevices.CameraFrameRate, 
                deviceId: device
            };
            
            var OnGetUserMediaSucess = function(stream)
            {
                replaceVideoTrack(stream);
            }
            
            var OnGetUserMediaFail = function(error)
            {
                console.log("打开本地设备失败 " + error.code + " : "+ error.message);
            }

            getUserMedia(localmedia, OnGetUserMediaSucess, OnGetUserMediaFail);
        }
        else
        {
            return -3;
        }
    }

    return 0;
}

DispatchRtcLib.VoipSetMicphone = function(CallID, Micphone)
{
    var device = null;
    var groupId = null;
    var old_groupId = null;
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    for (var i = 0; i < DispatchDevices.MicphoneList.length; i++)
    {
        if (DispatchDevices.MicphoneList[i].label == Micphone)
        {
            console.log("VoipSetMicphone to " + DispatchDevices.MicphoneList[i].label);
            device = DispatchDevices.MicphoneList[i].deviceId;
            groupId = DispatchDevices.MicphoneList[i].groupId;
        }
        
        if (DispatchDevices.MicphoneList[i].label == rtcLib.CurrentMicphone)
        {
            old_groupId = DispatchDevices.MicphoneList[i].groupId;
        }
    }
    
    if (!device)
    {
        return -1;
    }


    if (rtcLib.CurrentMicphone == Micphone ||
        groupId == old_groupId)
    {
        console.log("VoipSetMicphone nochange ");
        return 0;
    }
    
    var old_track = rtcLib.AudioRTCRtpSender.track;

    var localmedia =
    {
        audio: {deviceId: device},
        video: false,
        autoGainControl: true,
        echoCancellation: true,
        noiseSuppression: true,
    };

    var OnGetUserMediaSucess = function(stream)
    {
        rtcLib.AudioRTCRtpSender.replaceTrack(stream.getAudioTracks()[0])
        .then(() =>
        {
            console.log("replaceTrack Sucess to " + Micphone);
            rtcLib.CurrentMicphone = Micphone;
            
            old_track.stop();
        })
        .catch(error => 
        {
            console.log(error);
        });
    }
    
    var OnGetUserMediaFail = function(error)
    {
        console.log("打开本地设备失败 " + error.code + " : "+ error.message);
    }

    getUserMedia(localmedia, OnGetUserMediaSucess, OnGetUserMediaFail);
}

DispatchRtcLib.VoipSetCallVolume = function(CallID, Volume) 
{
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null)
    {
        return -1;
    }
    
    if (Volume < 0)
    {
        Volume = 0;
    }
    else if (Volume > 100)
    {
        Volume = 100;
    }
    
    Volume = Volume / 100.0;
    
    if (rtcLib.RemoteAudioLabel)
    {
        rtcLib.RemoteAudioLabel.volume = Volume;
    }
    
    if (rtcLib.RemoteVideoLib)
    {
        rtcLib.RemoteVideoLib.video.volume = Volume;
    }
    
    console.log("DispatchRtcLib.VoipSetCallVolume " + Volume);
    
    return 0;
}

DispatchRtcLib.VoipSetCallMicMute = function(CallID, Mute) 
{
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null)
    {
        return -1;
    }
    
    var bEnabled = true;
    if (Mute)
    {
        bEnabled = false;
    }
    
    if (rtcLib.LocalAudioStream)
    {       
        var AudioTracks = rtcLib.LocalAudioStream.getAudioTracks();
        for (var i = 0; i < AudioTracks.length; i++)
        {            
            AudioTracks[i].enabled = bEnabled;
        }
    }

    return 0;
}

DispatchRtcLib.VoipSetSpeaker = function(CallID, Speaker) 
{
    var deviceId = null;
    for (var i = 0; i < DispatchDevices.SpeakerList.length; i++)
    {            
        if (DispatchDevices.SpeakerList[i].label == Speaker)
        {
            console.log("VoipSetSpeaker to " + DispatchDevices.SpeakerList[i].label);
            deviceId = DispatchDevices.SpeakerList[i].deviceId;
            break;
        }
    }
    
    if (!deviceId)
    {
        return -3;
    }
    
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null)
    {
        return -1;
    }
    
    if (rtcLib.CallType == DispatchRtcLib_MoniterVideo ||
        rtcLib.CallType == DispatchRtcLib_VoipVideo)
    {
        //视频  rtcLib
        rtcLib.RemoteVideoLib.video.setSinkId(deviceId)
            .then(function()
            {
                console.log("VoipSetSpeaker Sucess");
            })
            .catch(function(error)
            {
                console.log("VoipSetSpeaker Fail");
                console.log(error)
            });
    }
    else if (rtcLib.CallType == DispatchRtcLib_VoipAudio ||
             rtcLib.CallType == DispatchRtcLib_ConfAudio)
    {
        //语音
        rtcLib.RemoteAudioLabel.setSinkId(deviceId)
            .then(function()
            {
                console.log("VoipSetSpeaker Sucess");
            })
            .catch(function(error)
            {
                console.log("VoipSetSpeaker Fail");
                console.log(error)
            });
    }

    return 0;
}

DispatchRtcLib.ConvertRecordWorker = null;
DispatchRtcLib.ConvertRecordWorkerLoaded = false;
DispatchRtcLib.ConvertRecordList = []; 
DispatchRtcLib.ConvertingRecord = null; //{name: , file: }

DispatchRtcLib.NewConvertRecordWorker = function() {
    if (DispatchRtcLib.ConvertRecordWorker == null)
    {
        var blob = URL.createObjectURL(new Blob(['importScripts("' + DispatchLibPath + 'ffmpeg/ffmpeg_asm.js");var now = Date.now;function print(text) {postMessage({"type" : "stdout","data" : text});};onmessage = function(event) {var message = event.data;if (message.type === "command") {var Module = {print: print,printErr: print,files: message.files || [],arguments: message.arguments || [],TOTAL_MEMORY: message.TOTAL_MEMORY || false};postMessage({"type" : "start","data" : Module.arguments.join(" ")});postMessage({"type" : "stdout","data" : "Received command: " +Module.arguments.join(" ") +((Module.TOTAL_MEMORY) ? ".  Processing with " + Module.TOTAL_MEMORY + " bits." : "")});var time = now();var result = ffmpeg_run(Module);var totalTime = now() - time;postMessage({"type" : "stdout","data" : "Finished processing (took " + totalTime + "ms)"});postMessage({"type" : "done","data" : result,"time" : totalTime});}};postMessage({"type" : "ready"});'], {
            type: 'application/javascript'
        }));
    
        DispatchRtcLib.ConvertRecordWorker = new Worker(blob);
        URL.revokeObjectURL(blob);

        DispatchRtcLib.ConvertRecordWorker.onmessage = function(event) {
            var message = event.data;
            if (message.type == "ready") {
                console.log("ffmpeg loaded");
                DispatchRtcLib.ConvertRecordWorkerLoaded = true;
                DispatchRtcLib.pushConvertRecordList(null);

            } else if (message.type == "stdout") {
                console.log("ffmpeg log:", message.data);
            } else if (message.type == "start") {
                console.log("ffmpeg start process");
            } else if (message.type == "done") {
                console.log("ffmpeg done process");

                var result = message.data[0];
                console.log(JSON.stringify(result));

                var filename = DispatchRtcLib.ConvertingRecord.name;
        
                var fullBlob = new File([result.data], filename, {
                    type: 'video/mp4'
                });

                var url = window.URL.createObjectURL(fullBlob);
                var a = document.createElement('a');  
                a.style.display = 'none';
                a.href = url;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                setTimeout(function () {
                    document.body.removeChild(a);
                    window.URL.revokeObjectURL(url);
                }, 1000);

                DispatchRtcLib.ConvertingRecord = null;

                DispatchRtcLib.pushConvertRecordList(null);
            }
        };
    }


}

DispatchRtcLib.pushConvertRecordList = function(record) 
{
    if (record)
    {
        DispatchRtcLib.ConvertRecordList.splice(DispatchRtcLib.ConvertRecordList.length, 0, record)
    }

    if (DispatchRtcLib.ConvertRecordWorkerLoaded)
    {
        //Worker加载完
        if (DispatchRtcLib.ConvertRecordList.length > 0 && DispatchRtcLib.ConvertingRecord == null) {
            DispatchRtcLib.ConvertingRecord = DispatchRtcLib.ConvertRecordList[0];
            DispatchRtcLib.ConvertRecordList.splice(0, 1);
    
            DispatchRtcLib.ConvertRecordWorker.postMessage({
                type: 'command',
                arguments: '-i video.webm -c:v copy -an -strict experimental output.mp4'.split(' '),
                //arguments: '-i video.webm -c:v copy -c:a copy -strict experimental output.mp4'.split(' '),
                files: [
                    {
                        data: new Uint8Array(DispatchRtcLib.ConvertingRecord.file),
                        name: 'video.webm'
                    }
                ]
            });
        }
    }
}


DispatchRtcLib.StartRecordStream = function(CallID) 
{
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null)
    {
        return -1;
    }
    
    if (!rtcLib.RemoteStream)
    {
        return -2;
    }

    DispatchRtcLib.NewConvertRecordWorker();

    rtcLib.RecorderStartTime = DispatchVideoLib.currentTime();

    rtcLib.recordVideo = RecordRTC(rtcLib.RemoteStream, {
        type: 'video',
        mimeType: 'video/webm;codecs=h264'
    });

    rtcLib.recordVideo.startRecording();
}

DispatchRtcLib.StopRecordStream = function(CallID) 
{
    var rtcLib = DispatchRtcLib.getRtcLib(CallID);
    if (rtcLib == null)
    {
        return -1;
    }
    
    if (!rtcLib.recordVideo)
    {
         return -2;
    }

    rtcLib.recordVideo.stopRecording(function(url) {
        var videoBlob = rtcLib.recordVideo.getBlob();

        var fileReader = new FileReader();
        fileReader.onload = function() {
            DispatchRtcLib.pushConvertRecordList({
                name: rtcLib.target.Number + "-[" + rtcLib.RecorderStartTime + "]-[" + DispatchVideoLib.currentTime() + "].mp4",
                file: this.result,
            });
        };
        fileReader.readAsArrayBuffer(videoBlob);

        rtcLib.recordVideo = null;
    });
}

DispatchRtcLib.VoipSetCameraParms = function(Width, Height, FrameRate) 
{
    if (DispatchDevices.UsingCamera)
    {
        return -1;
    }
    
    DispatchDevices.CameraWidth = Width;
    DispatchDevices.CameraHeight = Height;
    DispatchDevices.CameraFrameRate = FrameRate;

    return 0;
}

//————————————————————————————————————— DispatchConference ——————————————————————————————————————————
function DispatchConferenceMember() {
    this.Status = 0;  	//状态
    this.ExtNum = "";  	//分机号
    this.MemName = "";  //名称
    this.MemType = "";  //类型 1-调度台 2-手机 3-SIP话机 4-未知分机
    this.MemMute = 0;	//是否禁言  0-讲话中 1-禁言中
    this.MemVideo = 0;	//视频是否就绪  0-未就绪 1-就绪
};

function DispatchConference() {
    this.ConfID = null;
    this.MemberList = new Array;
    
    this.CallID = 0;		//对应呼叫ID
    this.CallStatus = 0;	//呼叫状态
    this.Type = 0;			//0-语音会议 1-视频会议

    this.websocket = null;
    
    this.VideoPush_CallID = 0;
};

var CurrentConference = new DispatchConference;

//当前会议信息
DispatchConference.CurrentConferenceInfo = function() 
{
    if (CurrentConference.ConfID)
    {
        return CurrentConference;
    }
    else
    {
        return null;
    }	
}

DispatchConference.ConferenceEnter = function(ConfID, Type, ConfPwd)
{
    if (CurrentConference.ConfID)
    {
        return -2;
    }
    
    var msg = new Object;
    msg.Msg = "VoipMakeAudioCallReq";
    msg.ExtNum = ConfID;
    if (typeof ConfPwd == "string")
    {
        msg.ExtNum = 'c' + ConfID + '_p' + ConfPwd;
    }
    msg.CallID = VoipCallID++;
    
    var OnMakeCallSucess = function(sdp)
    {
        msg.OfferSdp = sdp;
        
        console.log("DispatchConference.ConferenceEnter " + msg.CallID + " local_sdp:");
        console.log(sdp);

        DispatchCtrlLib.SendWorkerMsg(msg);
        
        CurrentConference.ConfID = ConfID;
        CurrentConference.CallID = msg.CallID;		
        CurrentConference.CallStatus = 1;
        CurrentConference.Type = Type;
    }
    
    var OnMakeCallFail = function(error)
    {
        console.log("DispatchConference.ConferenceEnter Fail:" + error);
    
        var failmsg = new Object;
        failmsg.Msg = "ConferenceEnterNotify";
        failmsg.ConfID = ConfID;
        failmsg.Status = 7;
        failmsg.Reason = error;
        
        DispatchCtrlLib.FuncCallback(failmsg);
    }
    
    return DispatchRtcLib.CreateOffer(msg.CallID, DispatchRtcLib_ConfAudio, OnMakeCallSucess, OnMakeCallFail);   
}

DispatchConference.RecvVoipCallStatus = function(msg)
{
    var failmsg = new Object;
    failmsg.Msg = "ConferenceEnterNotify";
    failmsg.ConfID = CurrentConference.ConfID;
    failmsg.Status = msg.Status;
    
    if (msg.Status > 2)
    {
        //会议停止
        DispatchConference.Stop();
    }
    
    DispatchCtrlLib.FuncCallback(failmsg);
}

DispatchConference.ConferenceExit = function() 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    var msg = new Object;
    msg.Msg = "VoipHangupCallReq";
    msg.CallID = CurrentConference.ConfID;
    
    DispatchCtrlLib.SendWorkerMsg(msg);
    
    DispatchConference.Stop();
}

DispatchConference.WsSend = function(Msg)
{
    if (CurrentConference.websocket)
    {
        console.log("DispatchConference.WsSend: " + Msg);
        CurrentConference.websocket.send(Msg);
    }
}

DispatchConference.RecvWsInviteNotify = function(msg)
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    //"ws://127.0.0.1:3701"    
    if (DispatchCtrlLib.msip.length > 0)
    {
        console.log("WebSocket Url: " + msg.Url + " before");

        if (window.location.protocol == 'https:') {
            //wss时使用nginx转发
            var port = DispatchCtrlLib.LoginInfo.NginxPort + 10;
            msg.Url = "wss://" + DispatchCtrlLib.LoginInfo.NginxIP + ":" + port + "/ms"
        } else {
            var urlArray = msg.Url.split(":");
            msg.Url = "ws://" + DispatchCtrlLib.msip + ":" + urlArray[2];
        }

        console.log("WebSocket Url: " + msg.Url + " after");
    }
    
    console.log("new WebSocket " + msg.Url);
    var websocket = new WebSocket(msg.Url);
    websocket.binaryType = 'arraybuffer';
    websocket.onopen = function(evt) {
        console.log("websocket.onopen");
        var xml = 
        "<VOIP>" +
        "<Command-Name>ConferenceWsInit</Command-Name>" +
        "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
        "<Command-ID></Command-ID>" +
        "<Command-Info>" +
            "<ExtNum>"+DispatchCtrlLib.extnumber+"</ExtNum>" +
        "</Command-Info>" +
        "</VOIP>";
        DispatchConference.WsSend(xml);
    }
    websocket.onclose = function(evt) {
        console.log("websocket.onclose");
        CurrentConference.websocket = null;
    }	
    websocket.onerror = function(evt) {
        console.log("websocket.onerror");
    }
    websocket.onmessage = DispatchConference.WebsocketOnMessage;
    
    window.setInterval(function () {
        var xml = 
        "<VOIP>" +
        "<Command-Name>ConferenceHeartbeat</Command-Name>" +
        "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
        "<Command-ID></Command-ID>" +
        "<Command-Info>" +
        "</Command-Info>" +
        "</VOIP>";
        DispatchConference.WsSend(xml);
    }, 30*1000);

    CurrentConference.websocket = websocket;
}

DispatchConference.WebsocketOnMessage = function(evt) {
    
    var getNodeValue = function(xml, name)
    {
        for (var i = 0; i < xml.all.length; i++)
        {            
            if (xml.all[i].tagName == name)
            {
                return xml.all[i].textContent;
            }
        }
        return null;
    }
    
    var getNodeList = function(xml, name)
    {
        var list = new Array;
        for (var i = 0; i < xml.all.length; i++)
        {            
            if (xml.all[i].tagName == name)
            {
                var obj = {
                };
                for (var j = 0; j < xml.all[i].childNodes.length; j++)
                {
                    var tagName = xml.all[i].childNodes[j].tagName;
                    var textContent = xml.all[i].childNodes[j].textContent;
                    obj[tagName] = textContent;
                }
                list.push(obj);
            }
        }
        
        return list;
    }
    
    if (typeof(evt.data) == "string") 
    {
        console.log("DispatchConference.WebsocketOnMessage:  " + evt.data);

        var xml_end = evt.data.indexOf("</VOIP>") + "</VOIP>".length;
        var xml = evt.data.substring(0, xml_end);
        
        var doc = (new DOMParser()).parseFromString(xml, "text/xml");
        
        var ConfID = getNodeValue(doc, "Command-Arg");
        if (ConfID != CurrentConference.ConfID)
        {
            console.log("Command-Arg " + ConfID + " != CurrentConference.ConfID " + CurrentConference.ConfID);
            return;
        }
        
        var CommandName = getNodeValue(doc, "Command-Name");

        switch(CommandName)
        {
            case "ConferenceInfo":
            {
                CurrentConference.HostExtNum = getNodeValue(doc, "HostExtNum");
                CurrentConference.ScreenShare = getNodeValue(doc, "ScreenShare");
                
                var MemList = getNodeList(doc, "Member");
                
                for (var i = 0; i < MemList.length; i++)
                {
                    var Member = {
                        ExtNum: MemList[i].ExtNum,
                        MemName: MemList[i].MemName,
                        MemType: MemList[i].MemType,
                        Status: MemList[i].MemStatus,
                        MemMute: MemList[i].Mute,
                        MemVideo: MemList[i].Video,
                        PullCallID: 0
                    };
                    
                    CurrentConference.MemberList.push(Member);
                }
                
                var msg = new Object;
                msg.Msg = "ConferenceStatusNotify";
                msg.ConfID = CurrentConference.ConfID;
                msg.Status = -1;
                
                DispatchCtrlLib.FuncCallback(msg);
                
                if (CurrentConference.Type)
                {
                    //视频会议 开始推送视频
                    DispatchConference.VideoPushReq();
                }
                
                break;
            }
            case "ConferenceStatus":
            {

                var Status = parseInt(getNodeValue(doc, "Status"));
                var ExtNum = getNodeValue(doc, "ExtNum");
                var MemName = getNodeValue(doc, "MemName");
                var MemType = parseInt(getNodeValue(doc, "MemType"));
                var MemStatus = parseInt(getNodeValue(doc, "MemStatus"));
                var Mute = parseInt(getNodeValue(doc, "Mute"));
                var Video = parseInt(getNodeValue(doc, "Video"));
                
                var Index = -1;
                for (var i = 0 ; i < CurrentConference.MemberList.length; i++)
                {
                    if (CurrentConference.MemberList[i].ExtNum == ExtNum)
                    {
                        Index = i;
                        break;
                    }
                }
                
                if (ExtNum && Index >= 0)
                {
                    CurrentConference.MemberList[Index].ExtNum = ExtNum;
                    CurrentConference.MemberList[Index].MemName = MemName;
                    CurrentConference.MemberList[Index].MemType = parseInt(MemType);
                    CurrentConference.MemberList[Index].Status = parseInt(MemStatus);
                    CurrentConference.MemberList[Index].Mute = parseInt(Mute);
                    CurrentConference.MemberList[Index].Video = parseInt(Video);
                }
                /*
                    0-成员移除
                    1-成员已接入
                    2-成员未接入
                    3-成员连接中
                    4-会议结束
                    5-成员禁言 
                    6-成员禁言取消
                    7-成员开始讲话 
                    8-成员结束讲话
                    9-主持人变更
                    10-视频就绪
                    11-视频结束
                    12-屏幕共享开始
                    13-屏幕共享结束
                */
                switch(Status)
                {
                    case 0:
                    {
                        //0-成员移除
                        if (Index != -1)
                        {
                            CurrentConference.MemberList.slice(Index);
                        }
                        break;
                    }
                    case 1:
                    {
                        //1-成员已接入
                        if (Index == -1)
                        {
                            var Member = {
                                ExtNum: ExtNum,
                                MemName: MemName,
                                MemType: MemType,
                                Status: MemStatus,
                                MemMute: Mute,
                                MemVideo: Video,
                                PullCallID: 0
                            };
                            
                            CurrentConference.MemberList.push(Member);
                        }
                        break;
                    }
                    case 2:
                    {
                        //2-成员未接入
                        if (Index == -1)
                        {
                            var Member = {
                                ExtNum: ExtNum,
                                MemName: MemName,
                                MemType: MemType,
                                Status: MemStatus,
                                MemMute: Mute,
                                MemVideo: Video,
                                PullCallID: 0
                            };
                            
                            CurrentConference.MemberList.push(Member);
                        }
                        else
                        {
                            CurrentConference.MemberList[Index].Status = 2;
                            CurrentConference.MemberList[Index].PullCallID = 0;
                        }
                        break;
                    }
                    case 3:
                    {
                        //3-成员连接中
                        if (Index == -1)
                        {
                            var Member = {
                                ExtNum: ExtNum,
                                MemName: MemName,
                                MemType: MemType,
                                Status: MemStatus,
                                MemMute: Mute,
                                MemVideo: Video,
                                PullCallID: 0
                            };
                            
                            CurrentConference.MemberList.push(Member);
                        }
                        else
                        {
                            CurrentConference.MemberList[Index].Status = 3;
                        }
                        break;
                    }
                    case 4:
                    {
                        //4-会议结束
                        DispatchConference.Stop();
                        break;
                    }
                    case 9:
                    {
                        //9-主持人变更
                        CurrentConference.HostExtNum = ExtNum;
                        break;
                    }
                    case 12:
                    {
                        //12-屏幕共享开始
                        CurrentConference.ScreenShare = ExtNum;
                        
                        break;
                    }
                    case 13:
                    {
                        //13-屏幕共享结束
                        CurrentConference.ScreenShare = "";
                        break;
                    }
                    default:
                        break;
                }
                
                var cb_msg = {
                    Msg: "ConferenceStatusNotify",
                    ConfID: ConfID,
                    ExtNum: ExtNum,
                    MemName: MemName,
                    MemType: MemType,
                    Status: parseInt(Status),
                    MemMute: Mute,
                    MemVideo: Video
                };
                
                DispatchCtrlLib.FuncCallback(cb_msg);
                
                break;
            }
            case "ConferenceVideoPushRsp":
            {
                var CommandID = getNodeValue(doc, "Command-ID");
                var Result = getNodeValue(doc, "Result");
                
                if (Result == 0)
                {
                    var sdp_start = evt.data.indexOf("v=0");
                    var sdp = evt.data.substring(sdp_start);
                    
                    if (evt.data.indexOf("test666") == -1)
                    {
                        //测试
                        DispatchConference.VideoPushRsp(sdp);
                    }
                            
                    var xml =
                    "<VOIP>" +
                    "<Command-Name>ConferenceVideoPushAck</Command-Name>" +
                    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
                    "<Command-ID>"+CurrentConference.ConfID+"-"+DispatchCtrlLib.extnumber+"</Command-ID>" +
                    "<Command-Info>" +
                        "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
                    "</Command-Info>" +
                    "</VOIP>\r\n";

                    DispatchConference.WsSend(xml);

                }
                else
                {
                    console.log("ConferenceVideoPushRsp Result: " + Result);
                    DispatchRtcLib.Stop(CurrentConference.VideoPush_CallID);
                    CurrentConference.VideoPush_CallID = 0;
                }

                break;
            }
            case "ConferenceVideoPushEnd":
            {
                if (CurrentConference.VideoPush_CallID)
                {
                    DispatchRtcLib.Stop(CurrentConference.VideoPush_CallID);
                    CurrentConference.VideoPush_CallID = 0;
                }
                break;
            }
            case "ConferenceVideoPullRsp":
            {
                var CommandID = getNodeValue(doc, "Command-ID");
                var Result = getNodeValue(doc, "Result");
                var Target = getNodeValue(doc, "Target");
                
                if (Result == 0)
                {
                    var sdp_start = evt.data.indexOf("v=0");
                    var sdp = evt.data.substring(sdp_start);
                    
                    if (evt.data.indexOf("test666") == -1)
                    {
                        //测试
                        for (var i = 0; i < CurrentConference.MemberList.length; i++)
                        {
                            if (CurrentConference.MemberList[i].ExtNum == Target)
                            {								
                                DispatchConference.VideoPullRsp(CurrentConference.MemberList[i].PullCallID, sdp);
                                break;
                            }
                        }
                    }
                            
                    var xml =
                    "<VOIP>" +
                    "<Command-Name>ConferenceVideoPullAck</Command-Name>" +
                    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
                    "<Command-ID>"+CurrentConference.ConfID+"-"+DispatchCtrlLib.extnumber+"</Command-ID>" +
                    "<Command-Info>" +
                        "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
                    "</Command-Info>" +
                    "</VOIP>\r\n";

                    DispatchConference.WsSend(xml);

                }
                else
                {
                    console.log("ConferenceVideoPushRsp Result: " + Result);
                    DispatchRtcLib.Stop(CurrentConference.VideoPush_CallID);
                    CurrentConference.VideoPush_CallID = 0;
                }
                
                break;
            }
            case "ConferenceVideoPullEnd":
            {
                var CommandID = getNodeValue(doc, "Command-ID");
                var Target = getNodeValue(doc, "Target");
                
                for (var i = 0; i < CurrentConference.MemberList.length; i++)
                {
                    if (CurrentConference.MemberList[i].ExtNum == Target)
                    {								
                        DispatchRtcLib.Stop(CurrentConference.MemberList[i].PullCallID);
                        CurrentConference.MemberList[i].PullCallID = 0;
                        break;
                    }
                }
                
                break;
            }
            default:
                break;
        }

    }
}

//处理会议邀请
DispatchConference.ConferenceHandleInvite = function(Handle)
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    if (Handle)
    {
        //接听
        DispatchCtrlLib.VoipHandleIncomingCall(CurrentConference.CallID, 1);
    }
    else
    {
        DispatchCtrlLib.VoipHandleIncomingCall(CurrentConference.CallID, 0);
        CurrentConference={};
    }
}

//会议停止
DispatchConference.Stop = function()
{
    DispatchRtcLib.Stop(CurrentConference.CallID);
    
    if (CurrentConference.Type == 1)
    {
        //视频会议
        
    }
    
    if (CurrentConference.websocket)
    {
        CurrentConference.websocket.close();
        CurrentConference.websocket = null;
    }
    
    CurrentConference.CallStatus = 0;	
    CurrentConference.ConfID = null;
    CurrentConference.CallID = 0;
    CurrentConference.MemberList = new Array;
}

//离开会议
DispatchConference.ConferenceExit = function()
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    var msg = new Object;
    msg.Msg = "VoipHangupCallReq";
    msg.CallID = CurrentConference.CallID;
    DispatchCtrlLib.SendWorkerMsg(msg);
    
    DispatchConference.Stop();

    return 0;
}

//会议邀请
DispatchConference.ConferenceInvite = function(ExtNumList) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    var xml =
    "<VOIP>" +
    "<Command-Name>ConferenceInvite</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>";
    
    for (var i = 0; i < ExtNumList.length; i++)
    {
        xml = xml + "<ExtNumber>"+ExtNumList[i].ExtNum+"</ExtNumber>";
    }

    xml = xml + "</Command-Info>" +
                "</VOIP>";
                
    DispatchConference.WsSend(xml);
    
    return 0;
}

//会议结束
DispatchConference.ConferenceEnd = function() 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    if (CurrentConference.HostExtNum != DispatchCtrlLib.extnumber)
    {
        console.log("CurrentConference HostExtNum " + CurrentConference.HostExtNum + " is not you!");
        return -3;
    }
    
    var xml = 
    "<VOIP>" +
    "<Command-Name>ConferenceKick</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>" +
        "<ExtNumber>all</ExtNumber>" +
    "</Command-Info>" +
    "</VOIP>";

    DispatchConference.WsSend(xml);
    
    DispatchConference.Stop();
    
    return 0;
}

//会议踢人
DispatchConference.ConferenceKick = function(ExtNum) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    if (CurrentConference.HostExtNum != DispatchCtrlLib.extnumber)
    {
        console.log("CurrentConference HostExtNum " + CurrentConference.HostExtNum + " is not you!");
        return -3;
    }
    
    var xml = 
    "<VOIP>" +
    "<Command-Name>ConferenceKick</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>" +
        "<ExtNumber>" + ExtNum + "</ExtNumber>" +
    "</Command-Info>" +
    "</VOIP>";
    
    DispatchConference.WsSend(xml);
    
    return 0;
}

//会议成员禁言
DispatchConference.ConferenceMute = function(ExtNum, Mute) {
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    if (ExtNum != DispatchCtrlLib.extnumber && CurrentConference.HostExtNum != DispatchCtrlLib.extnumber)
    {
        console.log("CurrentConference HostExtNum " + CurrentConference.HostExtNum + " is not you!");
        return -3;
    }
    
    var xml = 
    "<VOIP>" +
    "<Command-Name>ConferenceMute</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>" +
        "<ExtNumber>" + ExtNum + "</ExtNumber>" +
        "<Mute>" + Mute + "</Mute>" +
    "</Command-Info>" +
    "</VOIP>";
    
    DispatchConference.WsSend(xml);
    
    return 0;
}

//会议设置音量
DispatchConference.ConferenceSetVolume = function(Volume) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    if (typeof Volume == "number") 
    {
        return DispatchRtcLib.VoipSetCallVolume(CurrentConference.CallID, Volume);
    }
    else
    {
        return -1;
    }
}

//会议切换本地扬声器
DispatchConference.ConferenceSetSpeaker = function(DevLabel) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    return DispatchRtcLib.VoipSetSpeaker(CurrentConference.CallID, DevLabel); 	
}

//会议切换本地麦克风
DispatchConference.ConferenceSetMicphone = function(DevLabel) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    return DispatchRtcLib.VoipSetMicphone(CurrentConference.CallID, DevLabel); 	
}

//会议切换本地摄像头
DispatchConference.ConferenceSetCamera = function(DevLabel) {
    
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    return DispatchRtcLib.VoipSetCamera(CurrentConference.VideoPush_CallID, DevLabel)
}

//本地禁言
DispatchConference.ConferenceLocalMicMute = function(Mute) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    var xml = 
    "<VOIP>" +
    "<Command-Name>ConferenceMute</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>" +
        "<ExtNumber>" + DispatchCtrlLib.extnumber + "</ExtNumber>" +
        "<Mute>" + Mute + "</Mute>" +
    "</Command-Info>" +
    "</VOIP>";
    
    DispatchConference.WsSend(xml);
    
    return 0;
}

//会议视频观看
DispatchConference.ConferenceWatch = function(ExtNum, VideoLib) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    for (var i = 0; i < CurrentConference.MemberList.length; i++)
    {
        if (CurrentConference.MemberList[i].ExtNum == ExtNum)
        {
            if (CurrentConference.MemberList[i].PullCallID == 0)
            {
                var CallID = VoipCallID++;
                
                var OnMakeCallSucess = function(sdp)
                {
                    CurrentConference.MemberList[i].PullCallID = CallID;
                    
                    var xml =
                    "<VOIP>" +
                    "<Command-Name>ConferenceVideoPullReq</Command-Name>" +
                    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
                    "<Command-ID>"+CurrentConference.ConfID+"-"+ExtNum+"-"+DispatchCtrlLib.extnumber+"</Command-ID>" +
                    "<Command-Info>" +
                        "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
                        "<Target>" + ExtNum + "</Target>" +
                    "</Command-Info>" +
                    "</VOIP>\r\n";
                    
                    xml = xml + sdp;
                
                    //CurrentConference.MemberList[i].VideoLib = VideoLib;
                    DispatchRtcLib.DisplayVideo(CallID, VideoLib);
                
                    DispatchConference.WsSend(xml);
                }
                
                var OnMakeCallFail = function(error)
                {
                    console.log("DispatchConference.PullVideo Fail:" + error);
                }
                
                return DispatchRtcLib.CreateOffer(CallID, DispatchRtcLib_ConfVideoPull, OnMakeCallSucess, OnMakeCallFail);
            }
            
            return -1;
        }
    }
    
    return -1;
}

//会议视频聚焦
DispatchConference.ConferenceFocus = function(ExtNum) 
{
    if (!CurrentConference.ConfID)
    {
        return -2;
    }
    
    var xml = 
    "<VOIP>" +
    "<Command-Name>ConferenceFocus</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>" +
        "<ExtNumber>" + DispatchCtrlLib.extnumber + "</ExtNumber>" +
    "</Command-Info>" +
    "</VOIP>";
    
    DispatchConference.WsSend(xml);
}

//会议开始推送
DispatchConference.VideoPushReq = function() {
    var CallID = VoipCallID++;
    
    var OnMakeCallSucess = function(sdp)
    {
        CurrentConference.VideoPush_CallID = CallID;
        
        var xml =
        "<VOIP>" +
        "<Command-Name>ConferenceVideoPushReq</Command-Name>" +
        "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
        "<Command-ID>"+CurrentConference.ConfID+"-"+DispatchCtrlLib.extnumber+"</Command-ID>" +
        "<Command-Info>" +
            "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
        "</Command-Info>" +
        "</VOIP>\r\n";
        
        xml = xml + sdp;

        DispatchConference.WsSend(xml);
    }
    
    var OnMakeCallFail = function(error)
    {
        console.log("DispatchConference.PushLocalVideo Fail:" + error);
    }
    
    return DispatchRtcLib.CreateOffer(CallID, DispatchRtcLib_ConfVideoPush, OnMakeCallSucess, OnMakeCallFail);
}

//视频推送应答
DispatchConference.VideoPushRsp = function(answer) 
{
    DispatchRtcLib.SetRemoteSdp(CurrentConference.VideoPush_CallID, answer, DispatchRtcLib_ConfVideoPush); 	
}

//视频拉取应答
DispatchConference.VideoPullRsp = function(CallID, answer) 
{
    DispatchRtcLib.SetRemoteSdp(CallID, answer, DispatchRtcLib_ConfVideoPull); 	
}

//会议移交主持人
DispatchConference.ChangeHost = function(ExtNum) 
{
    if (CurrentConference.HostExtNum != DispatchCtrlLib.extnumber)
    {
        console.log("CurrentConference HostExtNum " + CurrentConference.HostExtNum + " is not you!");
        return -2;
    }
    
    var xml =
    "<VOIP>" +
    "<Command-Name>ConferenceHostChange</Command-Name>" +
    "<Command-Arg>"+CurrentConference.ConfID+"</Command-Arg>" +
    "<Command-ID></Command-ID>" +
    "<Command-Info>" +
        "<ExtNum>" + DispatchCtrlLib.extnumber + "</ExtNum>" +
        "<Host>" + ExtNum + "</Host>" +
    "</Command-Info>" +
    "</VOIP>\r\n";

    DispatchConference.WsSend(xml);
    
    return 0;
}

//————————————————————————————————————— DispatchPocLib ——————————————————————————————————————————
function DispatchPocLib() {};

DispatchPocLib.audioContext = null;

DispatchPocLib.AudioWorkletNode = null;
DispatchPocLib.AudioWorkletNodeSrc = null;

DispatchPocLib.AudioMicphoneStream = null;


//共享内存模式相关
const Shared_BufferLen_640size = 8; //80ms
DispatchPocLib.Shared_BufferLen = 640*Shared_BufferLen_640size;

DispatchPocLib.Shared_Play_Left = null;
DispatchPocLib.Shared_Play_Right = null;
DispatchPocLib.Shared_Play_Write_Index = null;
DispatchPocLib.Shared_Play_Read_Index = null;
DispatchPocLib.Shared_Play_Length = null;
DispatchPocLib.Shared_Capture = null;
DispatchPocLib.Shared_Capture_Flag = null;
DispatchPocLib.Shared_Capture_Write_Index = null;
DispatchPocLib.Shared_Capture_Read_Index = null;
DispatchPocLib.Shared_Capture_Length = null;

//消息转发模式相关
const Message_BufferLen_640size = 2; //80ms*2
DispatchPocLib.Message_BufferLen = 640*Message_BufferLen_640size;


DispatchPocLib.OnAudioWorkerMessage = function(event) {
    if (event && event.data && event.data.Msg)
    {
        if (event.data.Msg == "AudioWorkletInit")
        {
            if (typeof SharedArrayBuffer === "undefined") {
                msg = {
                    Msg: "AudioBufferInit",
                    Mode: "Int16Array",
                    BufferLen: DispatchPocLib.Shared_BufferLen, 
                    Message_BufferLen: DispatchPocLib.Message_BufferLen, 
                };

                DispatchPocLib.AudioWorkletNode.port.postMessage(msg)

                msg.Msg = "AudioBufferInit"
                DispatchCtrlLib.SendWorkerMsg(msg);

                console.log("------ Audio Message Mode ------");

            } else {
                msg = {
                    Msg: "AudioBufferInit",
                    Mode: "SharedArrayBuffer",
                    BufferLen: DispatchPocLib.Shared_BufferLen,
                    Message_BufferLen: DispatchPocLib.Message_BufferLen, 

                    Shared_Play_Left: DispatchPocLib.Shared_Play_Left,
                    Shared_Play_Right: DispatchPocLib.Shared_Play_Right,
                    Shared_Play_Write_Index: DispatchPocLib.Shared_Play_Write_Index,
                    Shared_Play_Read_Index: DispatchPocLib.Shared_Play_Read_Index,
                    Shared_Play_Length: DispatchPocLib.Shared_Play_Length,
            
                    Shared_Capture: DispatchPocLib.Shared_Capture,
                    Shared_Capture_Flag: DispatchPocLib.Shared_Capture_Flag,
                    Shared_Capture_Write_Index: DispatchPocLib.Shared_Capture_Write_Index,
                    Shared_Capture_Read_Index: DispatchPocLib.Shared_Capture_Read_Index,
                    Shared_Capture_Length: DispatchPocLib.Shared_Capture_Length,
                };
    
                DispatchPocLib.AudioWorkletNode.port.postMessage(msg)

                msg.Msg = "AudioBufferInit"
                DispatchCtrlLib.SendWorkerMsg(msg);

                console.log("------ Audio Shared Mode ------");
            }
        } 
        else if (event.data.Msg == "AudioCapBuffer")
        {
            msg = {
                Msg: "AudioCapBuffer",
                Buffer:   event.data.Buffer,  
            }
            if (DispatchCtrlLib.WsWorker) {
                DispatchCtrlLib.WsWorker.postMessage(msg);
            }
        }
    }
}


DispatchPocLib.Start = function() 
{
    if (DispatchPocLib.audioContext) {
        return;
    }
    
    if (DispatchPocLib.Shared_Play_Left == null && typeof SharedArrayBuffer != "undefined") {

        //640*Float32Array(80ms) = 160*Float32Array(20ms)*4 = 128*Float32Array*5  
        //Float32Array = 4bytes
        //buffer_ms = Shared_BufferLen*80ms

        DispatchPocLib.Shared_Play_Left = new SharedArrayBuffer(Int16Array.BYTES_PER_ELEMENT*DispatchPocLib.Shared_BufferLen);
        DispatchPocLib.Shared_Play_Right = new SharedArrayBuffer(Int16Array.BYTES_PER_ELEMENT*DispatchPocLib.Shared_BufferLen);

        DispatchPocLib.Shared_Play_Write_Index = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Audio_Play_Write_Index = new Uint32Array(DispatchPocLib.Shared_Play_Write_Index);
 
        DispatchPocLib.Shared_Play_Read_Index = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Audio_Play_Read_Index = new Uint32Array(DispatchPocLib.Shared_Play_Read_Index);

        DispatchPocLib.Shared_Play_Length = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Shared_Play_Length = new Uint32Array(DispatchPocLib.Shared_Play_Length);

        DispatchPocLib.Shared_Capture = new SharedArrayBuffer(Int16Array.BYTES_PER_ELEMENT*DispatchPocLib.Shared_BufferLen);
 
        DispatchPocLib.Shared_Capture_Flag = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Audio_Capture_Flag = new Uint32Array(DispatchPocLib.Shared_Capture_Flag);

        DispatchPocLib.Shared_Capture_Write_Index = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Audio_Capture_Write_Index = new Uint32Array(DispatchPocLib.Shared_Capture_Write_Index);
        
        DispatchPocLib.Shared_Capture_Read_Index = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Audio_Capture_Read_Index = new Uint32Array(DispatchPocLib.Shared_Capture_Read_Index);
        
        DispatchPocLib.Shared_Capture_Length = new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT);
        const Shared_Capture_Length = new Uint32Array(DispatchPocLib.Shared_Capture_Length);

        Audio_Play_Write_Index[0] = 640;
        Audio_Play_Read_Index[0] = 0;

        Audio_Capture_Write_Index[0] = 640;
        Audio_Capture_Read_Index[0] = 0;

        Audio_Capture_Flag[0] = 0;

        Shared_Play_Length[0] = 0;
        Shared_Capture_Length[0] = 0;
    }

    var StartAudioWorklet = async function(stream) 
    {
        var audioContext = new AudioContext({sampleRate: 8000});
        await audioContext.audioWorklet.addModule(DispatchLibPath+'DispatchAudioWorklet.js');

        let microphone = null;
        if (stream)
        {
            microphone = audioContext.createMediaStreamSource(stream);
            DispatchPocLib.AudioMicphoneStream = stream;
        }
        else
        {
            microphone = audioContext.createMediaElementSource(DispatchRtcLib.LocalMuteAudio);
        }

        DispatchPocLib.AudioWorkletNode = new AudioWorkletNode(audioContext, 'DispatchAudioWorklet')
        DispatchPocLib.AudioWorkletNode.port.onmessage = DispatchPocLib.OnAudioWorkerMessage;

        var dest = null;
        if (DispatchDevices.SpeakerList.length == 0)
        {
            console.log("本地无可用扬声器");
        }
        dest = audioContext.destination;

        microphone.connect(DispatchPocLib.AudioWorkletNode).connect(dest)
        
        DispatchPocLib.AudioWorkletNodeSrc = microphone;

        console.log("Starting AudioWorkletNode Mute");

        DispatchPocLib.audioContext = audioContext;
    }
    DispatchRtcLib.LocalMuteAudio.play();
    if (DispatchDevices.MicphoneList.length == 0)
    {
        console.log("本地无可用麦克风");
        
        StartAudioWorklet();

        return;
    }

    var OnGetUserMediaSucess = function(stream)
    {
        StartAudioWorklet(stream);
    }
    
    var OnGetUserMediaFail = function(stream)
    {
        console.log("GetUserMedia Fail");
    }
    
    console.log("打开麦克风" + DispatchDevices.UsingMicphone.label);
    navigator.getUserMedia(
        { 
            audio: {
                deviceId: DispatchDevices.UsingMicphone.deviceId
            }, 
            video: false,
            autoGainControl: true,
            echoCancellation: true,
            noiseSuppression: true,
        },
        OnGetUserMediaSucess,
        OnGetUserMediaFail
    );
}

DispatchPocLib.MicphoneChanged = function() 
{
    if (DispatchPocLib.audioContext == null)
    {
        return;
    }

    var audioContext = DispatchPocLib.audioContext;
    
    var StartAudioWorklet = async function(stream) 
    {
        DispatchPocLib.AudioMicphoneStream.getAudioTracks()[0].stop();

        DispatchPocLib.AudioWorkletNodeSrc.disconnect();

        let microphone = null;
        if (stream)
        {
            microphone = audioContext.createMediaStreamSource(stream);
            DispatchPocLib.AudioMicphoneStream = stream;
        }
        else
        {
            microphone = audioContext.createMediaElementSource(DispatchRtcLib.LocalMuteAudio);
        }

        microphone.connect(DispatchPocLib.AudioWorkletNode);
        
        DispatchPocLib.AudioWorkletNodeSrc = microphone;

        console.log("Starting AudioWorkletNode Mute");
    }

    if (DispatchDevices.MicphoneList.length == 0)
    {
        console.log("本地无可用麦克风");
        
        StartAudioWorklet();

        return;
    }

    var OnGetUserMediaSucess = function(stream)
    {
        StartAudioWorklet(stream);
    }
    
    var OnGetUserMediaFail = function(stream)
    {
        console.log("GetUserMedia Fail");
    }
    console.log("打开麦克风" + DispatchDevices.UsingMicphone.label);
    navigator.getUserMedia(
        { 
            audio: {
                deviceId: DispatchDevices.UsingMicphone.deviceId
            }, 
            video: false,
            autoGainControl: true,
            echoCancellation: true,
            noiseSuppression: true,
        },
        OnGetUserMediaSucess,
        OnGetUserMediaFail
    );

}