using System; using System.Collections.Generic; using System.IO; using System.Linq; using Cysharp.Threading.Tasks; using RenderStreaming; using Stary.Evo; using UnityEngine; using Unity.XR.XREAL; using CameraType = Unity.XR.XREAL.CameraType; #if UNITY_ANDROID && !UNITY_EDITOR using GalleryDataProvider = Unity.XR.XREAL.NativeGalleryDataProvider; #else using GalleryDataProvider = Unity.XR.XREAL.MockGalleryDataProvider; #endif public class XrealMixedRecorder : IVideoRecorder, IController { public enum ResolutionLevel { High, Middle, Low } public ResolutionLevel resolutionLevel = ResolutionLevel.High; public BlendMode blendMode = BlendMode.Blend; public AudioState audioState = AudioState.ApplicationAndMicAudio; public CaptureSide captureside = CaptureSide.Single; public bool useGreenBackGround = false; private XREALVideoCapture _videoCapture; public bool IsRecording => _videoCapture != null && _videoCapture.IsRecording; public Action OnStartedRecordingVideo { get; set; } public Action OnStoppedRecordingVideoAction { get; set; } private GalleryDataProvider _galleryDataTool; /// Save the video to Application.persistentDataPath. /// The full pathname of the video save file. private string VideoSaveExtension => Path.Combine(Application.persistentDataPath, "Recording"); private string VideoSavePath { get { var timeStamp = Time.time.ToString().Replace(".", "").Replace(":", ""); var filename = $"{this.GetSystem().GetConnectionId()}{timeStamp}.mp4"; return Path.Combine(Application.persistentDataPath, VideoSaveExtension, filename); } } private string _videoSavePath; public void StartRecording() { _videoSavePath = VideoSavePath; if (_videoCapture == null) CreateVideoCapture(StartVideoCapture); else if (_videoCapture.IsRecording) StopVideoCapture(); else StartVideoCapture(); } public void StopRecording() { if (_videoCapture == null || !_videoCapture.IsRecording) { Debug.LogError("当前没有正在进行的 XREAL 录制"); return; } StopVideoCapture(); } /// Stops video capture. public void StopVideoCapture() { if (_videoCapture == null || !_videoCapture.IsRecording) { Debug.LogWarning("Can not stop video capture!"); return; } Debug.Log("Stop Video Capture!"); _videoCapture.StopRecordingAsync(OnStoppedRecordingVideo); } /// Executes the 'stopped recording video' action. /// The result. private void OnStoppedRecordingVideo(XREALVideoCapture.VideoCaptureResult result) { if (!result.success) { Debug.Log("Stopped Recording Video Faild!"); return; } Debug.Log("Stopped Recording Video!"); _videoCapture.StopVideoModeAsync(OnStoppedVideoCaptureMode); } /// Executes the 'stopped video capture mode' action. /// The result. private async void OnStoppedVideoCaptureMode(XREALVideoCapture.VideoCaptureResult result) { Debug.Log("Stopped Video Capture Mode!"); var encoder = _videoCapture.GetContext().GetEncoder() as VideoEncoder; var path = encoder.EncodeConfig.outPutPath; var filename = string.Format("Xreal_Shot_Video_{0}.mp4", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()); await DelayInsertVideoToGallery(path, filename, "Record"); OnStoppedRecordingVideoAction?.Invoke(path); // Release video capture resource. _videoCapture.Dispose(); _videoCapture = null; } /// 延迟将视频插入相册,确保视频文件已完全写入 /// 视频文件路径 /// 显示名称 /// 文件夹名称 private async UniTask DelayInsertVideoToGallery(string originFilePath, string displayName, string folderName) { await UniTask.DelayFrame(100); InsertVideoToGallery(originFilePath, displayName, folderName); } /// 将视频插入到系统相册中 /// 视频文件的原始路径 /// 视频在相册中显示的名称 /// 相册文件夹名称 public void InsertVideoToGallery(string originFilePath, string displayName, string folderName) { Debug.LogFormat("InsertVideoToGallery: {0}, {1} => {2}", displayName, originFilePath, folderName); if (_galleryDataTool == null) _galleryDataTool = new GalleryDataProvider(); _galleryDataTool.InsertVideo(originFilePath, displayName, folderName); } private void CreateVideoCapture(Action callback) { XREALVideoCaptureUtility.CreateAsync(false, delegate(XREALVideoCapture videoCapture) { Debug.Log("Created VideoCapture Instance!"); if (videoCapture != null) { _videoCapture = videoCapture; callback?.Invoke(); } else { Debug.LogError("Failed to create VideoCapture Instance!"); } }); } public void StartVideoCapture() { if (_videoCapture.IsRecording) { Debug.LogWarning("Can not start video capture!"); return; } var cameraParameters = new CameraParameters(); var cameraResolution = GetResolutionByLevel(resolutionLevel); cameraParameters.cameraType = CameraType.RGB; cameraParameters.hologramOpacity = 0.0f; cameraParameters.frameRate = NativeConstants.RECORD_FPS_DEFAULT; cameraParameters.cameraResolutionWidth = cameraResolution.width; cameraParameters.cameraResolutionHeight = cameraResolution.height; cameraParameters.pixelFormat = CapturePixelFormat.PNG; cameraParameters.blendMode = blendMode; // Set audio state, audio record needs the permission of "android.permission.RECORD_AUDIO", // Add it to your "AndroidManifest.xml" file in "Assets/Plugin". cameraParameters.audioState = audioState; cameraParameters.captureSide = captureside; cameraParameters.backgroundColor = useGreenBackGround ? Color.green : Color.black; _videoCapture.StartVideoModeAsync(cameraParameters, OnStartedVideoCaptureMode, true); } private Resolution GetResolutionByLevel(ResolutionLevel level) { var resolutions = XREALVideoCaptureUtility.SupportedResolutions.OrderByDescending((res) => res.width * res.height); var resolution = new Resolution(); switch (level) { case ResolutionLevel.High: resolution = resolutions.ElementAt(0); break; case ResolutionLevel.Middle: resolution = resolutions.ElementAt(1); break; case ResolutionLevel.Low: resolution = resolutions.ElementAt(2); break; default: break; } return resolution; } private void OnStartedVideoCaptureMode(XREALVideoCapture.VideoCaptureResult result) { if (!result.success) { Debug.Log("Started Video Capture Mode faild!"); return; } Debug.Log("Started Video Capture Mode!"); _videoCapture.StartRecordingAsync(_videoSavePath, (a) => OnStartedRecordingVideo?.Invoke()); } public IArchitecture GetArchitecture() { return MainArchitecture.Interface; } }