/// <summary> /// Constructor called internally by <see cref="PeerConnection.AddMediaLine(MediaKind)"/>. /// </summary> /// <param name="kind">Immutable value assigned to the <see cref="MediaKind"/> property on construction.</param> internal MediaLine(PeerConnection peer, MediaKind kind) { _peer = peer; _mediaKind = kind; }
/// <summary> /// Callback fired from the <see cref="PeerConnection"/> before it starts /// uninitializing itself and disposing of the underlying implementation object. /// </summary> /// <param name="peer">The peer connection about to be deinitialized</param> public void OnPeerUninitializing(PeerConnection peer) { // Unregister handlers for the SDP events //_nativePeer.IceCandidateReadytoSend -= OnIceCandiateReadyToSend_Listener; //_nativePeer.LocalSdpReadytoSend -= OnLocalSdpReadyToSend_Listener; }
public override void OnPeerInitialized(PeerConnection peer) { base.OnPeerInitialized(peer); _nativePeer?.CreateOffer(); }
protected async void OnEnable() { if (Source != null) { return; } #if PLATFORM_ANDROID // Ensure Android binding is initialized before accessing the native implementation Android.Initialize(); // Check for permission to access the camera if (!Permission.HasUserAuthorizedPermission(Permission.Camera)) { if (!_androidCameraRequestPending) { // Monitor the OnApplicationFocus(true) event during the next 5 minutes, // and check for permission again each time (see below why). _androidCameraRequestPending = true; _androidCameraRequestRetryUntilTime = Time.time + 300; // Display dialog requesting user permission. This will return immediately, // and unfortunately there's no good way to tell when this completes. As a rule // of thumb, application should lose focus, so check when focus resumes should // be sufficient without having to poll every frame. Permission.RequestUserPermission(Permission.Camera); } return; } #elif UNITY_WSA && !UNITY_EDITOR // Request UWP access to video capture. The OS may show some popup dialog to the // user to request permission. This will succeed only if the user grants permission. try { // Note that the UWP UI thread and the main Unity app thread are always different. // https://docs.unity3d.com/Manual/windowsstore-appcallbacks.html // We leave the code below as an example of generic handling in case this would be used in // some other place, and in case a future version of Unity decided to change that assumption, // but currently OnEnable() is always invoked from the main Unity app thread so here the first // branch is never taken. if (UnityEngine.WSA.Application.RunningOnUIThread()) { await RequestAccessAsync(); } else { UnityEngine.WSA.Application.InvokeOnUIThread(() => RequestAccessAsync(), waitUntilDone: true); } } catch (Exception ex) { // Log an error and prevent activation Debug.LogError($"Video access failure: {ex.Message}."); this.enabled = false; return; } #endif // Handle automatic capture format constraints string videoProfileId = VideoProfileId; var videoProfileKind = VideoProfileKind; int width = Constraints.width; int height = Constraints.height; double framerate = Constraints.framerate; #if ENABLE_WINMD_SUPPORT if (FormatMode == LocalVideoSourceFormatMode.Automatic) { // Do not constrain resolution by default, unless the device calls for it (see below). width = 0; // auto height = 0; // auto // Avoid constraining the framerate; this is generally not necessary (formats are listed // with higher framerates first) and is error-prone as some formats report 30.0 FPS while // others report 29.97 FPS. framerate = 0; // auto // For HoloLens, use video profile to reduce resolution and save power/CPU/bandwidth if (global::Windows.Graphics.Holographic.HolographicSpace.IsAvailable) { if (!global::Windows.Graphics.Holographic.HolographicDisplay.GetDefault().IsOpaque) { if (global::Windows.ApplicationModel.Package.Current.Id.Architecture == global::Windows.System.ProcessorArchitecture.X86) { // Holographic AR (transparent) x86 platform - Assume HoloLens 1 videoProfileKind = WebRTC.VideoProfileKind.VideoRecording; // No profile in VideoConferencing width = 896; // Target 896 x 504 } else { // Holographic AR (transparent) non-x86 platform - Assume HoloLens 2 videoProfileKind = WebRTC.VideoProfileKind.VideoConferencing; width = 960; // Target 960 x 540 } } } } #elif PLATFORM_ANDROID if (FormatMode == LocalVideoSourceFormatMode.Automatic) { // Avoid constraining the framerate; this is generally not necessary (formats are listed // with higher framerates first) and is error-prone as some formats report 30.0 FPS while // others report 29.97 FPS. framerate = 0; // auto string deviceId = WebcamDevice.id; if (string.IsNullOrEmpty(deviceId)) { List <VideoCaptureDevice> listedDevices = await PeerConnection.GetVideoCaptureDevicesAsync(); if (listedDevices.Count > 0) { deviceId = listedDevices[0].id; } } if (!string.IsNullOrEmpty(deviceId)) { // Find the closest format to 720x480, independent of framerate List <VideoCaptureFormat> formats = await DeviceVideoTrackSource.GetCaptureFormatsAsync(deviceId); double smallestDiff = double.MaxValue; bool hasFormat = false; foreach (var fmt in formats) { double diff = Math.Abs(fmt.width - 720) + Math.Abs(fmt.height - 480); if ((diff < smallestDiff) || !hasFormat) { hasFormat = true; smallestDiff = diff; width = (int)fmt.width; height = (int)fmt.height; } } if (hasFormat) { Debug.Log($"WebcamSource automated mode selected resolution {width}x{height} for Android video capture device #{deviceId}."); } } } #endif // TODO - Fix codec selection (was as below before change) // Force again PreferredVideoCodec right before starting the local capture, // so that modifications to the property done after OnPeerInitialized() are // accounted for. //< FIXME //PeerConnection.Peer.PreferredVideoCodec = PreferredVideoCodec; // Check H.264 requests on Desktop (not supported) //#if !ENABLE_WINMD_SUPPORT // if (PreferredVideoCodec == "H264") // { // Debug.LogError("H.264 encoding is not supported on Desktop platforms. Using VP8 instead."); // PreferredVideoCodec = "VP8"; // } //#endif // Create the track var deviceConfig = new LocalVideoDeviceInitConfig { videoDevice = WebcamDevice, videoProfileId = videoProfileId, videoProfileKind = videoProfileKind, width = (width > 0 ? (uint?)width : null), height = (height > 0 ? (uint?)height : null), framerate = (framerate > 0 ? (double?)framerate : null), enableMrc = EnableMixedRealityCapture, enableMrcRecordingIndicator = EnableMRCRecordingIndicator }; try { var source = await DeviceVideoTrackSource.CreateAsync(deviceConfig); AttachSource(source); } catch (Exception ex) { Debug.LogError($"Failed to create device track source for {nameof(WebcamSource)} component '{name}'."); Debug.LogException(ex, this); return; } }
/// <summary> /// Internal coroutine helper for receiving HTTP data from the DSS server using GET /// and processing it as needed /// </summary> /// <returns>the message</returns> private IEnumerator CO_GetAndProcessFromServer() { if (HttpServerAddress.Length == 0) { throw new InvalidOperationException("Cannot receive SDP messages from remote peer; invalid empty HTTP server address."); } if (LocalPeerId.Length == 0) { throw new InvalidOperationException("Cannot receive SDP messages from remote peer; invalid empty local peer ID."); } var www = UnityWebRequest.Get($"{HttpServerAddress}data/{LocalPeerId}"); yield return(www.SendWebRequest()); if (!www.isNetworkError && !www.isHttpError) { var json = www.downloadHandler.text; var msg = JsonUtility.FromJson <NodeDssMessage>(json); // if the message is good if (msg != null) { // depending on what type of message we get, we'll handle it differently // this is the "glue" that allows two peers to establish a connection. DebugLogLong($"Received SDP message: type={msg.MessageType} data={msg.Data}"); switch (msg.MessageType) { case NodeDssMessage.Type.Offer: // Apply the offer coming from the remote peer to the local peer var sdpOffer = new WebRTC.SdpMessage { Type = SdpMessageType.Offer, Content = msg.Data }; PeerConnection.HandleConnectionMessageAsync(sdpOffer).ContinueWith(_ => { // If the remote description was successfully applied then immediately send // back an answer to the remote peer to acccept the offer. _nativePeer.CreateAnswer(); }, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.RunContinuationsAsynchronously); break; case NodeDssMessage.Type.Answer: // No need to wait for completion; there is nothing interesting to do after it. var sdpAnswer = new WebRTC.SdpMessage { Type = SdpMessageType.Answer, Content = msg.Data }; _ = PeerConnection.HandleConnectionMessageAsync(sdpAnswer); break; case NodeDssMessage.Type.Ice: // this "parts" protocol is defined above, in OnIceCandidateReadyToSend listener _nativePeer.AddIceCandidate(msg.ToIceCandidate()); break; default: Debug.Log("Unknown message: " + msg.MessageType + ": " + msg.Data); break; } timeSincePollMs = PollTimeMs + 1f; //fast forward next request } else if (AutoLogErrors) { Debug.LogError($"Failed to deserialize JSON message : {json}"); } } else if (AutoLogErrors && www.isNetworkError) { Debug.LogError($"Network error trying to send data to {HttpServerAddress}: {www.error}"); } else { // This is very spammy because the node-dss protocol uses 404 as regular "no data yet" message, which is an HTTP error //Debug.LogError($"HTTP error: {www.error}"); } lastGetComplete = true; }
/// <summary> /// Internal coroutine helper for receiving HTTP data from the DSS server using GET /// and processing it as needed /// </summary> /// <returns>the message</returns> private IEnumerator CO_GetAndProcessFromServer() { if (HttpServerAddress.Length == 0) { throw new InvalidOperationException("Cannot receive SDP messages from remote peer; invalid empty HTTP server address."); } if (LocalPeerId.Length == 0) { throw new InvalidOperationException("Cannot receive SDP messages from remote peer; invalid empty local peer ID."); } var www = UnityWebRequest.Get($"{HttpServerAddress}data/{LocalPeerId}"); yield return(www.SendWebRequest()); if (!www.isNetworkError && !www.isHttpError) { var json = www.downloadHandler.text; NodeDssMessage msg; if (Encrypt) { EncryptedMessage encryptedMessage = JsonUtility.FromJson <EncryptedMessage>(json); msg = EncryptedMessage.DecryptMessage(encryptedMessage.Cipher, encryptedMessage.Hmac, this.Key, this.IV); } else { msg = JsonUtility.FromJson <NodeDssMessage>(json); } // {"Cipher":"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","Hmac":"b4302c1cc75d5916f009768e9396194d6bb7ff4fead75c62534779a778bb7f94"} // if the message is good if (msg != null) { // depending on what type of message we get, we'll handle it differently // this is the "glue" that allows two peers to establish a connection. DebugLogLong($"Received SDP message: type={msg.MessageType} data={msg.Data}"); switch (msg.MessageType) { case NodeDssMessage.Type.Offer: // Apply the offer coming from the remote peer to the local peer var sdpOffer = new WebRTC.SdpMessage { Type = SdpMessageType.Offer, Content = msg.Data }; PeerConnection.HandleConnectionMessageAsync(sdpOffer).ContinueWith(_ => { // If the remote description was successfully applied then immediately send // back an answer to the remote peer to acccept the offer. _nativePeer.CreateAnswer(); _nativePeer.DataChannelAdded += OnDataChannelAdded; }, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.RunContinuationsAsynchronously); break; case NodeDssMessage.Type.Answer: // No need to wait for completion; there is nothing interesting to do after it. var sdpAnswer = new WebRTC.SdpMessage { Type = SdpMessageType.Answer, Content = msg.Data }; _ = PeerConnection.HandleConnectionMessageAsync(sdpAnswer); break; case NodeDssMessage.Type.Ice: // this "parts" protocol is defined above, in OnIceCandidateReadyToSend listener var iceCandidate = msg.ToIceCandidate(); break; default: Debug.Log("Unknown message: " + msg.MessageType + ": " + msg.Data); break; } timeSincePollMs = PollTimeMs + 1f; //fast forward next request } else if (AutoLogErrors) { Debug.LogError($"Failed to deserialize JSON message : {json}"); } } else if (AutoLogErrors && www.isNetworkError) { Debug.LogError($"Network error trying to send data to {HttpServerAddress}: {www.error}"); } else { // This is very spammy because the node-dss protocol uses 404 as regular "no data yet" message, which is an HTTP error //Debug.LogError($"HTTP error: {www.error}"); } lastGetComplete = true; }
/// <inheritdoc/> protected override void Update() { // Do not forget to call the base class Update(), which processes events from background // threads to fire the callbacks implemented in this class. base.Update(); if (startConnection) { PeerConnection.StartConnection(); _nativePeer.RenegotiationNeeded += Connection_RenegotiationNeeded; _nativePeer.IceGatheringStateChanged += Connection_IceGatheringStateChanged; _nativePeer.IceStateChanged += Connection_IceStateChanged; startConnection = false; } //if there's a pending sdpanswer, then connect and consume it if (sdpAnswer != null) { //PeerConnection.HandleConnectionMessageAsync(sdpAnswer); // If i call this I publish my video but I'm not able to subscribe long i = idMessage++; // follow with a ReceiveVideoFrom on RPC RemotePeerId = joinRoomAnswer.result.value[0].id; string message = "{\"jsonrpc\": \"2.0\"," + "\"method\": \"receiveVideoFrom\"," + "\"params\": { \"sender\": \"" + joinRoomAnswer.result.value[0].streams[0].Id + "\"" + ",\"sdpOffer\": \"" + lastOffer.Content + "\"" + "},\"id\": " + i + " }"; Debug.Log("ReceiveVideoFrom : " + message); webSocket.SendText(message); messages.Add(i, OpenViduType.ReceiveVideoFrom); sdpAnswer = null; } // If we have not reached our PollTimeMs value... if (timeSincePollMs <= PollTimeMs) { // ...then we keep incrementing our local counter until we do. timeSincePollMs += Time.deltaTime * 1000.0f; return; } // If we have a pending request still going, don't queue another yet. if (!lastGetComplete) { return; } // When we have reached our PollTimeMs value... timeSincePollMs = 0f; // ...begin the poll and process. lastGetComplete = false; Ping(); }
public void OnReceivedData(object sender, EventArgs args) { if (args == null) { return; } // return early if wrong type of EventArgs var myArgs = args as TextEventArgs; if (myArgs == null) { Debug.Log("Got somethin elseg from ws:" + args.ToString()); return; } var json = myArgs.Text; var msg = JsonConvert.DeserializeObject <OpenViduMessageJson>(json); // if the message is good if (msg != null) { if (!String.IsNullOrEmpty(msg.Method)) { if (msg.Method.Equals("iceCandidate")) { OpenViduIceCandidateEvent msg2 = JsonConvert.DeserializeObject <OpenViduIceCandidateEvent>(json); var ic = new IceCandidate { SdpMid = msg2.Params.SdpMid, SdpMlineIndex = msg2.Params.SdpMLineIndex, Content = msg2.Params.Candidate, }; Debug.Log("<color=white>IceCandidate</color>(SdpMid=" + ic.SdpMid + ", SdpMlineIndex=" + ic.SdpMlineIndex + ", Content=" + ic.Content + ")"); _nativePeer.AddIceCandidate(ic); } else { Debug.Log("<color=red>" + json + "</color>"); } } else if (messages.Contains(msg.id)) { //var id = Int32.Parse(msg.Id); long id = msg.id; OpenViduType messageType = (OpenViduType)messages[id]; switch (messageType) { case OpenViduType.Ping: break; case OpenViduType.JoinRoom: joinRoomAnswer = JsonConvert.DeserializeObject <OpenViduJoinRoomAnswer>(json); LocalPeerId = joinRoomAnswer.result.id; startConnection = true; break; case OpenViduType.PublishVideo: Debug.Log("<color=yellow>" + json + "</color>"); var msg2 = JsonConvert.DeserializeObject <OpenViduPublishVideoAnswer>(json); sdpAnswer = new WebRTC.SdpMessage { Type = SdpMessageType.Answer, Content = msg2.Result.SdpAnswer }; break; case OpenViduType.ReceiveVideoFrom: Debug.Log("<color=yellow>" + json + "</color>"); var msg3 = JsonConvert.DeserializeObject <OpenViduReceiveVideoAnswer>(json); sdpAnswerReceiveVideo = new WebRTC.SdpMessage { Type = SdpMessageType.Answer, Content = msg3.Result.SdpAnswer }; _mainThreadWorkQueue.Enqueue(() => { PeerConnection.HandleConnectionMessageAsync(sdpAnswerReceiveVideo); /*PeerConnection.HandleConnectionMessageAsync(sdpAnswerReceiveVideo).ContinueWith(_ => * { * _nativePeer.CreateAnswer(); //this only works if local video is not published * }, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.RunContinuationsAsynchronously);*/ }); break; case OpenViduType.OnIceCandidate: msg = JsonConvert.DeserializeObject <OpenViduOnIceCandidateAnswer>(json); break; default: break; } timeSincePollMs = PollTimeMs + 1f; //fast forward next request } } else if (AutoLogErrors) { Debug.LogError($"Failed to deserialize JSON message : {json}"); } }
// This method might be run outside the app thread and should not access the Unity API. private static async Task <WebRTC.VideoTrackSource> CreateSourceAsync( LocalVideoSourceFormatMode formatMode, LocalVideoDeviceInitConfig deviceConfig) { // Handle automatic capture format constraints #if ENABLE_WINMD_SUPPORT if (formatMode == LocalVideoSourceFormatMode.Automatic) { // Do not constrain resolution by default, unless the device calls for it (see below). deviceConfig.width = 0; // auto deviceConfig.height = 0; // auto // Avoid constraining the framerate; this is generally not necessary (formats are listed // with higher framerates first) and is error-prone as some formats report 30.0 FPS while // others report 29.97 FPS. deviceConfig.framerate = 0; // auto // For HoloLens, use video profile to reduce resolution and save power/CPU/bandwidth if (global::Windows.Graphics.Holographic.HolographicSpace.IsAvailable) { if (!global::Windows.Graphics.Holographic.HolographicDisplay.GetDefault().IsOpaque) { if (global::Windows.ApplicationModel.Package.Current.Id.Architecture == global::Windows.System.ProcessorArchitecture.X86) { // Holographic AR (transparent) x86 platform - Assume HoloLens 1 deviceConfig.videoProfileKind = WebRTC.VideoProfileKind.VideoRecording; // No profile in VideoConferencing deviceConfig.width = 896; // Target 896 x 504 } else { // Holographic AR (transparent) non-x86 platform - Assume HoloLens 2 deviceConfig.videoProfileKind = WebRTC.VideoProfileKind.VideoConferencing; deviceConfig.width = 960; // Target 960 x 540 } } } } #elif !UNITY_EDITOR && UNITY_ANDROID if (formatMode == LocalVideoSourceFormatMode.Automatic) { // Avoid constraining the framerate; this is generally not necessary (formats are listed // with higher framerates first) and is error-prone as some formats report 30.0 FPS while // others report 29.97 FPS. deviceConfig.framerate = 0; // auto string deviceId = deviceConfig.videoDevice.id; if (string.IsNullOrEmpty(deviceId)) { // Continue the task outside the Unity app context, in order to avoid deadlock // if OnDisable waits on this task. IReadOnlyList <VideoCaptureDevice> listedDevices = await PeerConnection.GetVideoCaptureDevicesAsync() .ConfigureAwait(continueOnCapturedContext: false); if (listedDevices.Count > 0) { deviceId = listedDevices[0].id; } } if (!string.IsNullOrEmpty(deviceId)) { // Find the closest format to 720x480, independent of framerate // Continue the task outside the Unity app context, in order to avoid deadlock // if OnDisable waits on this task. IReadOnlyList <VideoCaptureFormat> formats = await DeviceVideoTrackSource.GetCaptureFormatsAsync(deviceId) .ConfigureAwait(continueOnCapturedContext: false); double smallestDiff = double.MaxValue; bool hasFormat = false; foreach (var fmt in formats) { double diff = Math.Abs(fmt.width - 720) + Math.Abs(fmt.height - 480); if ((diff < smallestDiff) || !hasFormat) { hasFormat = true; smallestDiff = diff; deviceConfig.width = fmt.width; deviceConfig.height = fmt.height; } } if (hasFormat) { Debug.Log($"WebcamSource automated mode selected resolution {deviceConfig.width}x{deviceConfig.height} for Android video capture device #{deviceId}."); } } } #endif // TODO - Fix codec selection (was as below before change) // Force again PreferredVideoCodec right before starting the local capture, // so that modifications to the property done after OnPeerInitialized() are // accounted for. //< FIXME //PeerConnection.Peer.PreferredVideoCodec = PreferredVideoCodec; // Check H.264 requests on Desktop (not supported) //#if !ENABLE_WINMD_SUPPORT // if (PreferredVideoCodec == "H264") // { // Debug.LogError("H.264 encoding is not supported on Desktop platforms. Using VP8 instead."); // PreferredVideoCodec = "VP8"; // } //#endif // Create the track var createTask = DeviceVideoTrackSource.CreateAsync(deviceConfig); // Continue the task outside the Unity app context, in order to avoid deadlock // if OnDisable waits on this task. return(await createTask.ConfigureAwait(continueOnCapturedContext : false)); }