using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; using Newtonsoft.Json; using RenderStreaming; using Stary.Evo; using Unity.RenderStreaming; using Unity.WebRTC; using UnityEngine; namespace Script.Recorder { public class ServerMixedRecorder : IVideoRecorder, IController { public bool IsRecording { get; } public Action OnStartedRecordingVideo { get; set; } public Action OnStoppedRecordingVideoAction { get; set; } private string recordingId; private readonly Dictionary recordingPeers = new(); // [Header("Tracks")] public RenderTexture sourceRenderTexture; // public string microphoneDeviceName = ""; // 留空使用默认麦克风 // public int microphoneSampleRate = 48000; // // private AudioSource microphoneAudioSource; // private bool microphoneStarted; private VideoStreamTrack videoTrack; private AudioStreamTrack audioTrack; public async void StartRecording() { await CreateLocalTracks(); var messageChannel = GameObject.FindObjectOfType(); messageChannel.OnRecordingPeerRequestReceived += OnRecordingPeerRequestReceived; messageChannel.OnRecordingAnswerReceived += OnRecordingAnswerReceived; messageChannel.OnRecordingCandidateReceived += OnRecordingCandidateReceived; messageChannel.OnRecordingStoppedReceived += OnRecordingStoppedReceived; var data = new { connectionId = this.GetSystem().GetConnectionId(), layout = "grid", format = "webm", }; var result = await WebRequestSystem.Post( this.GetSystem().IP + "/api/recording-sessions", JsonConvert.SerializeObject(data)); if (result != null) { RecordingSession session = JsonConvert.DeserializeObject(result); if (string.IsNullOrEmpty(session.success)) { Debug.LogError($"[ServerMixedRecorder] StartRecording 失败: {session}"); return; } recordingId = session.session.id; } } public async UniTask CreateLocalTracks() { var RenderStreaming =GameObject.Find("RenderStreaming"); VideoStreamSender videoStreamSender = RenderStreaming.GetComponent(); videoTrack = videoStreamSender.Track as VideoStreamTrack; AudioStreamSender audioStreamSender = RenderStreaming.GetComponent(); audioTrack = audioStreamSender.Track as AudioStreamTrack; // sourceRenderTexture = this.GetSystem().GetRenderStreamingTexture(); // if (sourceRenderTexture != null) // { // videoTrack = new VideoStreamTrack(sourceRenderTexture); // } // // // microphoneAudioSource = // GameObject.Find("RenderStreaming/microphoneAudioSource").GetComponent(); // microphoneAudioSource.loop = true; // microphoneAudioSource.mute = true; // // string device = string.IsNullOrEmpty(microphoneDeviceName) // ? null // : microphoneDeviceName; // // microphoneAudioSource.clip = Microphone.Start( // device, // true, // 1, // microphoneSampleRate // ); // // microphoneStarted = true; //await CreateMicrophoneTrackWhenReady(device); } // public async UniTask CreateMicrophoneTrackWhenReady(string device) // { // while (Microphone.GetPosition(device) <= 0) // await UniTask.NextFrame(); // // microphoneAudioSource.Play(); // audioTrack = new AudioStreamTrack(microphoneAudioSource); // } public async void OnRecordingPeerRequestReceived(RecordingRequest request) { if (string.IsNullOrEmpty(request.recordingId)) return; OnRecordingStoppedReceived(request.recordingId); var config = new RTCConfiguration { iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } } }; var pc = new RTCPeerConnection(ref config); recordingPeers[request.recordingId] = pc; pc.OnIceCandidate = candidate => { if (candidate == null) return; Send("recording-candidate", new RecordingCandidate { recordingId = request.recordingId, connectionId = request.connectionId, participantId = this.GetSystem().GetParticipantId(), candidate = candidate.Candidate, sdpMid = candidate.SdpMid, sdpMLineIndex = candidate.SdpMLineIndex ?? 0 }); }; if (videoTrack != null) pc.AddTransceiver(videoTrack, new RTCRtpTransceiverInit { direction = RTCRtpTransceiverDirection.SendOnly }); if (audioTrack != null) pc.AddTransceiver(audioTrack, new RTCRtpTransceiverInit { direction = RTCRtpTransceiverDirection.SendOnly }); var offerOp = pc.CreateOffer(); await offerOp; var offer = offerOp.Desc; var localOp = pc.SetLocalDescription(ref offer); await localOp; Send("recording-offer", new RecordingOffer { recordingId = request.recordingId, connectionId = request.connectionId, participantId = this.GetSystem().GetParticipantId(), sdp = offer.sdp }); } public async void OnRecordingAnswerReceived(RecordingAnswer answer) { if (!recordingPeers.TryGetValue(answer.recordingId, out var pc)) return; var desc = new RTCSessionDescription { type = RTCSdpType.Answer, sdp = answer.sdp }; var op = pc.SetRemoteDescription(ref desc); await op; } public void OnRecordingCandidateReceived(RecordingCandidate data) { if (!recordingPeers.TryGetValue(data.recordingId, out var pc)) return; var candidate = new RTCIceCandidate(new RTCIceCandidateInit { candidate = data.candidate, sdpMid = data.sdpMid, sdpMLineIndex = data.sdpMLineIndex }); pc.AddIceCandidate(candidate); } public void OnRecordingStoppedReceived(string recordingId) { if (!recordingPeers.TryGetValue(recordingId, out var pc)) return; pc.Close(); recordingPeers.Remove(recordingId); } void Send(string type, object data) { var connectionId = this.GetSystem().GetConnectionId(); var json = JsonConvert.SerializeObject(new Dictionary { ["type"] = "on-message", ["data"] = new Dictionary { ["connectionId"] = connectionId, ["message"] = new Dictionary { ["type"] = type, ["data"] = data } } }); SignalingMessageHelper.SendMessage(json); } public async void StopRecording() { await WebRequestSystem.Delete(this.GetSystem().IP, $"/api/recording-sessions/{recordingId}"); var messageChannel = GameObject.FindObjectOfType(); messageChannel.OnRecordingPeerRequestReceived -= OnRecordingPeerRequestReceived; messageChannel.OnRecordingAnswerReceived -= OnRecordingAnswerReceived; messageChannel.OnRecordingCandidateReceived -= OnRecordingCandidateReceived; messageChannel.OnRecordingStoppedReceived -= OnRecordingStoppedReceived; } void OnDestroy() { foreach (var pc in recordingPeers.Values) pc.Close(); recordingPeers.Clear(); videoTrack?.Dispose(); audioTrack?.Dispose(); // if (microphoneStarted) // { // string device = string.IsNullOrEmpty(microphoneDeviceName) // ? null // : microphoneDeviceName; // // Microphone.End(device); // } } public IArchitecture GetArchitecture() { return MainArchitecture.Interface; } } [Serializable] public class Session { /// /// /// public string id { get; set; } /// /// /// public string connectionId { get; set; } /// /// /// public string status { get; set; } /// /// /// public string layout { get; set; } /// /// /// public string format { get; set; } /// /// /// public string createdAt { get; set; } /// /// /// public string startedAt { get; set; } /// /// /// public string updatedAt { get; set; } } [Serializable] public class Agent { /// /// /// public string id { get; set; } /// /// /// public string recordingId { get; set; } /// /// /// public string connectionId { get; set; } /// /// /// public string status { get; set; } /// /// /// public string mediaMode { get; set; } /// /// /// public string createdAt { get; set; } /// /// /// public string updatedAt { get; set; } } [Serializable] public class RecordingSession { /// /// /// public string success { get; set; } /// /// /// public Session session { get; set; } /// /// /// public Agent agent { get; set; } /// /// /// public string notified { get; set; } /// /// /// public string peerRequestNotified { get; set; } } }