/// <summary> /// Handler for Signaller's OnMessageFromPeer event. /// </summary> /// <param name="peerId">ID of the peer.</param> /// <param name="message">Message from the peer.</param> private void Signaller_OnMessageFromPeer(int peerId, string message) { Task.Run(async() => { Debug.Assert(_peerId == peerId || _peerId == -1); Debug.Assert(message.Length > 0); if (_peerId != peerId && _peerId != -1) { Debug.WriteLine("[Error] Conductor: Received a message from unknown peer while already in a conversation with a different peer."); return; } JsonObject jMessage; if (!JsonObject.TryParse(message, out jMessage)) { Debug.WriteLine("[Error] Conductor: Received unknown message." + message); return; } string type = jMessage.ContainsKey(kSessionDescriptionTypeName) ? jMessage.GetNamedString(kSessionDescriptionTypeName) : null; #if ORTCLIB bool created = false; #endif if (_peerConnection == null) { if (!IsNullOrEmpty(type)) { // Create the peer connection only when call is // about to get initiated. Otherwise ignore the // messages from peers which could be a result // of old (but not yet fully closed) connections. if (type == "offer" || type == "answer" || type == "json") { Debug.Assert(_peerId == -1); _peerId = peerId; IEnumerable <Peer> enumerablePeer = Peers.Where(x => x.Id == peerId); Peer = enumerablePeer.First(); #if ORTCLIB created = true; _signalingMode = Helper.SignalingModeForClientName(Peer.Name); #endif _connectToPeerCancelationTokenSource = new CancellationTokenSource(); _connectToPeerTask = CreatePeerConnection(_connectToPeerCancelationTokenSource.Token); bool connectResult = await _connectToPeerTask; _connectToPeerTask = null; _connectToPeerCancelationTokenSource.Dispose(); if (!connectResult) { Debug.WriteLine("[Error] Conductor: Failed to initialize our PeerConnection instance"); await Signaller.SignOut(); return; } else if (_peerId != peerId) { Debug.WriteLine("[Error] Conductor: Received a message from unknown peer while already in a conversation with a different peer."); return; } } } else { Debug.WriteLine("[Warn] Conductor: Received an untyped message after closing peer connection."); return; } } if (_peerConnection != null && !IsNullOrEmpty(type)) { // Data Message Intercept if (type == kMessageDataType) { OnPeerMessageDataReceived?.Invoke(peerId, message); } if (type == "offer-loopback") { // Loopback not supported Debug.Assert(false); } string sdp = null; #if ORTCLIB if (jMessage.ContainsKey(kSessionDescriptionJsonName)) { var containerObject = new JsonObject { { kSessionDescriptionJsonName, jMessage.GetNamedObject(kSessionDescriptionJsonName) } }; sdp = containerObject.Stringify(); } else if (jMessage.ContainsKey(kSessionDescriptionSdpName)) { sdp = jMessage.GetNamedString(kSessionDescriptionSdpName); } #else sdp = jMessage.ContainsKey(kSessionDescriptionSdpName) ? jMessage.GetNamedString(kSessionDescriptionSdpName) : null; #endif if (IsNullOrEmpty(sdp)) { Debug.WriteLine("[Error] Conductor: Can't parse received session description message."); return; } #if ORTCLIB RTCSessionDescriptionSignalingType messageType = RTCSessionDescriptionSignalingType.SdpOffer; switch (type) { case "json": messageType = RTCSessionDescriptionSignalingType.Json; break; case "offer": messageType = RTCSessionDescriptionSignalingType.SdpOffer; break; case "answer": messageType = RTCSessionDescriptionSignalingType.SdpAnswer; break; case "pranswer": messageType = RTCSessionDescriptionSignalingType.SdpPranswer; break; default: Debug.Assert(false, type); break; } #else RTCSdpType messageType = RTCSdpType.Offer; switch (type) { case "offer": messageType = RTCSdpType.Offer; break; case "answer": messageType = RTCSdpType.Answer; break; case "pranswer": messageType = RTCSdpType.Pranswer; break; default: Debug.Assert(false, type); break; } #endif Debug.WriteLine("Conductor: Received session description: " + message); await _peerConnection.SetRemoteDescription(new RTCSessionDescription(messageType, sdp)); #if ORTCLIB if ((messageType == RTCSessionDescriptionSignalingType.SdpOffer) || ((created) && (messageType == RTCSessionDescriptionSignalingType.Json))) #else if (messageType == RTCSdpType.Offer) #endif { var answer = await _peerConnection.CreateAnswer(); await _peerConnection.SetLocalDescription(answer); // Send answer SendSdp(answer); #if ORTCLIB OrtcStatsManager.Instance.StartCallWatch(SessionId, false); #endif } } else { RTCIceCandidate candidate = null; #if ORTCLIB if (RTCPeerConnectionSignalingMode.Json != _signalingMode) #endif { var sdpMid = jMessage.ContainsKey(kCandidateSdpMidName) ? jMessage.GetNamedString(kCandidateSdpMidName) : null; var sdpMlineIndex = jMessage.ContainsKey(kCandidateSdpMlineIndexName) ? jMessage.GetNamedNumber(kCandidateSdpMlineIndexName) : -1; var sdp = jMessage.ContainsKey(kCandidateSdpName) ? jMessage.GetNamedString(kCandidateSdpName) : null; //TODO: Check is this proper condition ((String.IsNullOrEmpty(sdpMid) && (sdpMlineIndex == -1)) || String.IsNullOrEmpty(sdp)) if (IsNullOrEmpty(sdpMid) || sdpMlineIndex == -1 || IsNullOrEmpty(sdp)) { Debug.WriteLine("[Error] Conductor: Can't parse received message.\n" + message); return; } #if ORTCLIB candidate = IsNullOrEmpty(sdpMid) ? RTCIceCandidate.FromSdpStringWithMLineIndex(sdp, (ushort)sdpMlineIndex) : RTCIceCandidate.FromSdpStringWithMid(sdp, sdpMid); #else candidate = new RTCIceCandidate(sdp, sdpMid, (ushort)sdpMlineIndex); #endif } #if ORTCLIB else { candidate = RTCIceCandidate.FromJsonString(message); } _peerConnection?.AddIceCandidate(candidate); #else await _peerConnection.AddIceCandidate(candidate); #endif Debug.WriteLine("Conductor: Received candidate : " + message); } }).Wait(); }
public void Initialize() { WebRTC.Initialize(_uiDispatcher); Conductor.Instance.ETWStatsEnabled = false; Cameras = new ObservableCollection <MediaDevice>(); Microphones = new ObservableCollection <MediaDevice>(); AudioPlayoutDevices = new ObservableCollection <MediaDevice>(); // WebRTCUWP M58 library does not support audio capture/playout devices //foreach (MediaDevice audioCaptureDevice in Conductor.Instance.Media.GetAudioCaptureDevices()) //{ // Microphones.Add(audioCaptureDevice); //} //foreach (MediaDevice audioPlayoutDevice in Conductor.Instance.Media.GetAudioPlayoutDevices()) //{ // AudioPlayoutDevices.Add(audioPlayoutDevice); //} // HACK Remove Automatic Device Assignment if (SelectedCamera == null && Cameras.Count > 0) { SelectedCamera = Cameras.First(); } if (SelectedMicrophone == null && Microphones.Count > 0) { SelectedMicrophone = Microphones.First(); } Debug.WriteLine("Device Status: SelectedCamera: {0} - SelectedMic: {1}", SelectedCamera == null ? "NULL" : "OK", SelectedMicrophone == null ? "NULL" : "OK"); if (SelectedAudioPlayoutDevice == null && AudioPlayoutDevices.Count > 0) { SelectedAudioPlayoutDevice = AudioPlayoutDevices.First(); } Conductor.Instance.Media.OnMediaDevicesChanged += OnMediaDevicesChanged; Conductor.Instance.Signaller.OnPeerConnected += (peerId, peerName) => { RunOnUiThread(() => { if (Peers == null) { Peers = new ObservableCollection <Peer>(); Conductor.Instance.Peers = Peers; } Peers.Add(new Peer { Id = peerId, Name = peerName }); }); }; Conductor.Instance.Signaller.OnPeerDisconnected += peerId => { RunOnUiThread(() => { var peerToRemove = Peers?.FirstOrDefault(p => p.Id == peerId); if (peerToRemove != null) { Peers.Remove(peerToRemove); } }); }; Conductor.Instance.Signaller.OnSignedIn += () => { RunOnUiThread(() => { IsConnected = true; IsMicrophoneEnabled = false; IsCameraEnabled = false; IsConnecting = false; OnStatusMessageUpdate?.Invoke("Signed-In"); }); }; Conductor.Instance.Signaller.OnServerConnectionFailure += (Exception ex) => { RunOnUiThread(() => { IsConnecting = false; OnStatusMessageUpdate?.Invoke("Server Connection Failure: " + ex.Message + "\n" + ex.StackTrace); }); }; Conductor.Instance.Signaller.OnDisconnected += () => { RunOnUiThread(() => { IsConnected = false; IsMicrophoneEnabled = false; IsCameraEnabled = false; IsDisconnecting = false; Peers?.Clear(); OnStatusMessageUpdate?.Invoke("Disconnected"); }); }; Conductor.Instance.Signaller.OnMessageFromPeer += (id, message) => { RunOnUiThread(() => { // TODO: Handles All Peer Messages (Signal Channel) }); }; Conductor.Instance.Signaller.OnPeerConnected += (id, name) => { RunOnUiThread(() => { SelectedPeer = Peers.First(x => x.Id == id); OnStatusMessageUpdate?.Invoke(string.Format("Connected Peer: {0}-{1}", SelectedPeer.Id, SelectedPeer.Name)); }); }; // TODO: Restore Event Handler in Utility Wrapper // Implemented in Unity Consumer due to Event Handling Issue // Conductor.Instance.OnAddRemoteStream += Conductor_OnAddRemoteStream does not propagate Conductor.Instance.OnRemoveRemoteStream += Conductor_OnRemoveRemoteStream; Conductor.Instance.OnAddLocalStream += Conductor_OnAddLocalStream; Conductor.Instance.OnConnectionHealthStats += Conductor_OnPeerConnectionHealthStats; Conductor.Instance.OnPeerConnectionCreated += () => { RunOnUiThread(() => { IsReadyToConnect = false; IsConnectedToPeer = true; IsReadyToDisconnect = false; IsMicrophoneEnabled = false; OnStatusMessageUpdate?.Invoke("Peer Connection Created"); }); }; Conductor.Instance.OnPeerConnectionClosed += () => { RunOnUiThread(() => { IsConnectedToPeer = false; _peerVideoTrack = null; _selfVideoTrack = null; IsMicrophoneEnabled = false; IsCameraEnabled = false; // TODO: Clean-up References //GC.Collect(); // Ensure all references are truly dropped. OnStatusMessageUpdate?.Invoke("Peer Connection Closed"); }); }; Conductor.Instance.OnPeerMessageDataReceived += (peerId, message) => { OnPeerMessageDataReceived?.Invoke(peerId, message); }; // DATA Channel Setup Conductor.Instance.OnPeerMessageDataReceived += (i, s) => { }; Conductor.Instance.OnReadyToConnect += () => { RunOnUiThread(() => { IsReadyToConnect = true; }); }; IceServers = new ObservableCollection <IceServer>(); NewIceServer = new IceServer(); AudioCodecs = new ObservableCollection <CodecInfo>(); var audioCodecList = WebRTC.GetAudioCodecs(); string[] incompatibleAudioCodecs = new string[] { "CN32000", "CN16000", "CN8000", "red8000", "telephone-event8000" }; VideoCodecs = new ObservableCollection <CodecInfo>(); // TODO: REMOVE DISPLAY LIST SUPPORT var videoCodecList = WebRTC.GetVideoCodecs().OrderBy(codec => { switch (codec.Name) { case "VP8": return(1); case "VP9": return(2); case "H264": return(3); default: return(99); } }); RunOnUiThread(() => { foreach (var audioCodec in audioCodecList) { if (!incompatibleAudioCodecs.Contains(audioCodec.Name + audioCodec.ClockRate)) { AudioCodecs.Add(audioCodec); } } if (AudioCodecs.Count > 0) { SelectedAudioCodec = AudioCodecs.FirstOrDefault(x => x.Name.Contains("PCMU")); } foreach (var videoCodec in videoCodecList) { VideoCodecs.Add(videoCodec); } if (VideoCodecs.Count > 0) { SelectedVideoCodec = VideoCodecs.FirstOrDefault(x => x.Name.Contains("H264")); } }); RunOnUiThread(() => { OnInitialized?.Invoke(); }); }
public void Initialize() { // WebRTCライブラリの初期化 // WebRTC.Initialize(_uiDispathcer); // Conductor.Instance.ETWStatsEnabled = false; /* * Cameras = new List<MediaDevice>(); * Microphones = new List<MediaDevice>(); * AudioPlayoutDevices = new List<MediaDevice>(); * // マシン上で使用できるメディアデバイスをすべて取得する * foreach(var videoCaptureDevice in Conductor.Instance.Media.GetVideoCaptureDevices()) * { * Cameras.Add(videoCaptureDevice); * } * foreach(var audioCaptureDevice in Conductor.Instance.Media.GetAudioCaptureDevices()) * { * Microphones.Add(audioCaptureDevice); * } * foreach(var audioPlayoutDevice in Conductor.Instance.Media.GetAudioPlayoutDevices()) * { * AudioPlayoutDevices.Add(audioPlayoutDevice); * } */ // 各種メディアデバイスはリストの先頭のものを使用する // Holoはいいけど、Immersiveの場合は考え直すべきです /* * if(SelectedCamera == null && Cameras.Count > 0) * { * SelectedCamera = Cameras.First(); * } * * if(SelectedMicrophone == null && Microphones.Count > 0) * { * SelectedMicrophone = Microphones.First(); * } * * if(SelectedAudioPlayoutDevice == null && AudioPlayoutDevices.Count >0) * { * SelectedAudioPlayoutDevice = AudioPlayoutDevices.First(); * } */ // ================================ // シグナリング関連のイベントハンドラ // ================================ // マシンに接続されたメディアデバイスが変更されたときのイベントハンドラ // Conductor.Instance.Media.OnMediaDevicesChanged += OnMediaDeviceChanged; // リモートユーザがシグナリングサーバに接続してきたときのハンドラ // 自分の初回ログイン、ポーリング時の新規ユーザ追加時にコールされる // TODO 接続ユーザの選択方法を工夫したいところ Conductor.Instance.Signaller.OnPeerConnected += (peerId, peerName) => { // リモートユーザのリストを行進する if (Peers == null) { Peers = new List <Peer>(); Conductor.Instance.Peers = Peers; } Peers.Add(new Peer { Id = peerId, Name = peerName }); // 接続してきたリモートユーザをPeer候補とする SelectedPeer = Peers.First(x => x.Id == peerId); }; // リモートユーザがシグナリングサーバからログアウトしたときのハンドラ Conductor.Instance.Signaller.OnPeerDisconnected += (peerId) => { var peerToRemove = Peers?.FirstOrDefault(p => p.Id == peerId); if (peerToRemove != null) { Peers.Remove(peerToRemove); } }; // シグナリングサーバへの接続が完了したときのハンドラ Conductor.Instance.Signaller.OnSignedIn += () => { IsConnected = true; IsMicrophoneEnabled = false; IsCameraEnabled = false; IsConnecting = false; OnStatusMessageUpdate?.Invoke("Signed in"); }; // シグナリングサーバへの接続が失敗したときのハンドラ Conductor.Instance.Signaller.OnServerConnectionFailure += () => { IsConnecting = false; OnStatusMessageUpdate?.Invoke("Server Connection Failure"); }; // シグナリングサーバからログアウトしたときのハンドラ Conductor.Instance.Signaller.OnDisconnected += () => { IsConnected = false; IsMicrophoneEnabled = false; IsCameraEnabled = false; IsDisconnecting = false; Peers?.Clear(); OnStatusMessageUpdate?.Invoke("Disconnected"); }; // Conductor.Instance.OnReadyToConnect += () => { IsReadyToConnect = true; }; // ============================= // Peerコネクション関連のイベントハンドラ // ============================= // Peerコネクションが生成されたときのイベントハンドラ(通話開始) Conductor.Instance.OnPeerConnectionCreated += () => { IsReadyToConnect = false; IsConnectedToPeer = true; IsReadyToDisconnect = false; IsCameraEnabled = true; IsMicrophoneEnabled = true; // ?? OnStatusMessageUpdate?.Invoke("Peer Connection Created"); }; // Peerコネクションが破棄されたときのイベントハンドラ Conductor.Instance.OnPeerConnectionClosed += () => { IsConnectedToPeer = false; _peerVideoTrack = null; _selfVideoTrack = null; IsMicrophoneEnabled = false; IsCameraEnabled = false; }; // Peer(リモートユーザ)からメッセージを受信したときのハンドラ Conductor.Instance.OnPeerMessageDataReceived += (peerId, message) => { OnPeerMessageDataReceived?.Invoke(peerId, message); }; // ============================= // コーデック設定 // ============================= /* * // オーディオコーデックの設定 * AudioCodecs = new List<CodecInfo>(); * var audioCodecList = WebRTC.GetAudioCodecs(); * string[] incompatibleAudioCodecs = new string[] { "CN32000", "CN16000", "CN8000", "red8000", "telephone-event8000" }; * * foreach (var audioCodec in audioCodecList) * { * if (!incompatibleAudioCodecs.Contains(audioCodec.Name + audioCodec.ClockRate)) * { * AudioCodecs.Add(audioCodec); * } * } * if (AudioCodecs.Count > 0) * { * SelectedAudioCodec = AudioCodecs.First(); * } * * // ビデオコーデックの設定。デフォルトはH.264を使う * VideoCodecs = new List<CodecInfo>(); * var videoCodecList = WebRTC.GetVideoCodecs().OrderBy(codec => * { * switch (codec.Name) * { * case "VP8": return 1; * case "VP9": return 2; * case "H264": return 3; * default: return 99; * } * }); * * foreach (var videoCodec in videoCodecList) * { * VideoCodecs.Add(videoCodec); * } * if (VideoCodecs.Count > 0) * { * SelectedVideoCodec = VideoCodecs.FirstOrDefault(codec => codec.Name.Contains("H264")); * } */ /* * // ============================= * // Iceサーバの設定 * // ============================= * IceServers = new List<IceServer>(); * NewIceServer = new IceServer(); * * IceServers.Add(new IceServer("stun.l.google.com:19302", IceServer.ServerType.STUN)); * IceServers.Add(new IceServer("stun1.l.google.com:19302", IceServer.ServerType.STUN)); * IceServers.Add(new IceServer("stun2.l.google.com:19302", IceServer.ServerType.STUN)); * IceServers.Add(new IceServer("stun3.l.google.com:19302", IceServer.ServerType.STUN)); * IceServers.Add(new IceServer("stun4.l.google.com:19302", IceServer.ServerType.STUN)); * * Conductor.Instance.ConfigureIceServers(IceServers); */ OnInitialized?.Invoke(); }