录屏
This commit is contained in:
@@ -3588,7 +3588,7 @@ MonoBehaviour:
|
||||
onValueChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_IsOn: 1
|
||||
m_IsOn: 0
|
||||
--- !u!1 &3813444533319316158
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace Script
|
||||
private AudioStreamSender _audioStreamSender;
|
||||
private VideoStreamSender _videoStreamSender;
|
||||
|
||||
|
||||
private IVideoRecorder _recorder;
|
||||
|
||||
public void Initialize(GameObject panelGo, MainPanel mainPanel)
|
||||
{
|
||||
PanelGo = panelGo;
|
||||
@@ -54,8 +57,18 @@ namespace Script
|
||||
_hangUpTog = PanelGo.transform.Find("menuBar/hangUp").GetComponent<Button>();
|
||||
_audioStreamSender = GameObject.Find("RenderStreaming").GetComponent<AudioStreamSender>();
|
||||
_videoStreamSender = GameObject.Find("RenderStreaming").GetComponent<VideoStreamSender>();
|
||||
#if UNITY_EDITOR
|
||||
_recorder = new EditorGameViewRecorder();
|
||||
#elif UNITY_ANDROID
|
||||
_recorder = new XrealMixedRecorder();
|
||||
#else
|
||||
Debug.LogWarning("当前平台没有可用录制器");
|
||||
#endif
|
||||
_recorder.OnStartedRecordingVideo = OnStartedRecordingVideo;
|
||||
_recorder.OnStoppedRecordingVideoAction = OnStoppedRecordingVideoAction;
|
||||
}
|
||||
|
||||
|
||||
public void OnEnter()
|
||||
{
|
||||
LoadUsers();
|
||||
@@ -119,8 +132,38 @@ namespace Script
|
||||
|
||||
private void OnRecordTogValueChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
_recorder.StartRecording();
|
||||
else
|
||||
_recorder.StopRecording();
|
||||
}
|
||||
|
||||
private void OnStartedRecordingVideo()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnStoppedRecordingVideoAction(string path)
|
||||
{
|
||||
var participants = new List<MainPanel.UsersItem>();
|
||||
MainPanel.UsersItem host = null;
|
||||
foreach (var value in _meetingList.Values)
|
||||
if (value.item.role == "host")
|
||||
{
|
||||
host = value.item;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
participants.Add(value.item);
|
||||
}
|
||||
|
||||
|
||||
WebRequestSystem.Upload(path, this.GetSystem<IGlobalConfigSystem>().IP,
|
||||
this.GetSystem<IGlobalConfigSystem>().GetConnectionId(),
|
||||
this.GetSystem<IGlobalConfigSystem>().GetUserId(), host, participants);
|
||||
}
|
||||
|
||||
|
||||
private void OnHangUpTogValueChanged()
|
||||
{
|
||||
this.GetSystem<IRenderStreamingSystem>().HangUp();
|
||||
|
||||
8
Assets/Script/Recorder.meta
Normal file
8
Assets/Script/Recorder.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d0e1b8f02d02434c9f0d3e805a6ba04
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
99
Assets/Script/Recorder/EditorGameViewRecorder.cs
Normal file
99
Assets/Script/Recorder/EditorGameViewRecorder.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.IO;
|
||||
using RenderStreaming;
|
||||
using Stary.Evo;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Recorder;
|
||||
using UnityEditor.Recorder.Input;
|
||||
|
||||
public class EditorGameViewRecorder : IVideoRecorder, IController
|
||||
{
|
||||
private RecorderController recorderController;
|
||||
|
||||
public bool IsRecording => recorderController != null && recorderController.IsRecording();
|
||||
|
||||
/// <summary> Save the video to Application.persistentDataPath. </summary>
|
||||
/// <value> The full pathname of the video save file. </value>
|
||||
private string VideoSaveExtension => Path.Combine(Application.persistentDataPath, "Recording");
|
||||
|
||||
private string VideoSavePath
|
||||
{
|
||||
get
|
||||
{
|
||||
var timeStamp = DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss");
|
||||
var filename =
|
||||
$"meeting-recording-{this.GetSystem<IGlobalConfigSystem>().GetConnectionId()}-{timeStamp}";
|
||||
return Path.Combine(Application.persistentDataPath, VideoSaveExtension, filename);
|
||||
}
|
||||
}
|
||||
|
||||
private string _videoSavePath;
|
||||
public Action OnStartedRecordingVideo { get; set; }
|
||||
public Action<string> OnStoppedRecordingVideoAction { get; set; }
|
||||
|
||||
public void StartRecording()
|
||||
{
|
||||
if (IsRecording)
|
||||
return;
|
||||
_videoSavePath = VideoSavePath;
|
||||
|
||||
var controllerSettings = ScriptableObject.CreateInstance<RecorderControllerSettings>();
|
||||
recorderController = new RecorderController(controllerSettings);
|
||||
|
||||
var movieSettings = ScriptableObject.CreateInstance<MovieRecorderSettings>();
|
||||
movieSettings.name = "Game View MP4 Recorder";
|
||||
movieSettings.Enabled = true;
|
||||
movieSettings.OutputFormat = MovieRecorderSettings.VideoRecorderOutputFormat.MP4;
|
||||
movieSettings.OutputFile = _videoSavePath;
|
||||
movieSettings.CaptureAudio = false;
|
||||
|
||||
movieSettings.ImageInputSettings = new GameViewInputSettings
|
||||
{
|
||||
OutputWidth = 1920,
|
||||
OutputHeight = 1080
|
||||
};
|
||||
|
||||
controllerSettings.AddRecorderSettings(movieSettings);
|
||||
controllerSettings.SetRecordModeToManual();
|
||||
controllerSettings.FrameRate = 30.0f;
|
||||
controllerSettings.CapFrameRate = true;
|
||||
|
||||
recorderController.PrepareRecording();
|
||||
|
||||
var started = recorderController.StartRecording();
|
||||
if (!started)
|
||||
{
|
||||
Debug.LogError("Editor Recorder 启动失败,请确认已进入 Play Mode,并安装 com.unity.recorder");
|
||||
recorderController = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"Editor 开始录制: {_videoSavePath}");
|
||||
OnStartedRecordingVideo?.Invoke();
|
||||
}
|
||||
|
||||
public void StopRecording()
|
||||
{
|
||||
if (!IsRecording)
|
||||
{
|
||||
Debug.LogError("当前没有正在进行的 Editor 录制");
|
||||
return;
|
||||
}
|
||||
|
||||
recorderController.StopRecording();
|
||||
|
||||
if (!File.Exists(_videoSavePath))
|
||||
OnStoppedRecordingVideoAction?.Invoke(_videoSavePath + ".mp4");
|
||||
else
|
||||
Debug.LogError($"Editor MP4 文件不存在,可能还在写入或输出路径变化: {_videoSavePath}");
|
||||
|
||||
recorderController = null;
|
||||
}
|
||||
|
||||
public IArchitecture GetArchitecture()
|
||||
{
|
||||
return MainArchitecture.Interface;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
3
Assets/Script/Recorder/EditorGameViewRecorder.cs.meta
Normal file
3
Assets/Script/Recorder/EditorGameViewRecorder.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f7a8ef900894205803013925c8fbe95
|
||||
timeCreated: 1779783425
|
||||
10
Assets/Script/Recorder/IVideoRecorder.cs
Normal file
10
Assets/Script/Recorder/IVideoRecorder.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
public interface IVideoRecorder
|
||||
{
|
||||
bool IsRecording { get; }
|
||||
Action OnStartedRecordingVideo { get; set; }
|
||||
Action<string> OnStoppedRecordingVideoAction { get; set; }
|
||||
void StartRecording();
|
||||
void StopRecording();
|
||||
}
|
||||
11
Assets/Script/Recorder/IVideoRecorder.cs.meta
Normal file
11
Assets/Script/Recorder/IVideoRecorder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7807c2de252777049950ce1055c8f3a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
231
Assets/Script/Recorder/XrealMixedRecorder.cs
Normal file
231
Assets/Script/Recorder/XrealMixedRecorder.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
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 = 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<string> OnStoppedRecordingVideoAction { get; set; }
|
||||
|
||||
private GalleryDataProvider _galleryDataTool;
|
||||
|
||||
|
||||
/// <summary> Save the video to Application.persistentDataPath. </summary>
|
||||
/// <value> The full pathname of the video save file. </value>
|
||||
private string VideoSaveExtension => Path.Combine(Application.persistentDataPath, "Recording");
|
||||
|
||||
private string VideoSavePath
|
||||
{
|
||||
get
|
||||
{
|
||||
var timeStamp = Time.time.ToString().Replace(".", "").Replace(":", "");
|
||||
var filename =
|
||||
$"{this.GetSystem<IGlobalConfigSystem>().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();
|
||||
}
|
||||
|
||||
/// <summary> Stops video capture. </summary>
|
||||
public void StopVideoCapture()
|
||||
{
|
||||
if (_videoCapture == null || !_videoCapture.IsRecording)
|
||||
{
|
||||
Debug.LogWarning("Can not stop video capture!");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("Stop Video Capture!");
|
||||
_videoCapture.StopRecordingAsync(OnStoppedRecordingVideo);
|
||||
}
|
||||
|
||||
/// <summary> Executes the 'stopped recording video' action. </summary>
|
||||
/// <param name="result"> The result.</param>
|
||||
private void OnStoppedRecordingVideo(XREALVideoCapture.VideoCaptureResult result)
|
||||
{
|
||||
if (!result.success)
|
||||
{
|
||||
Debug.Log("Stopped Recording Video Faild!");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("Stopped Recording Video!");
|
||||
_videoCapture.StopVideoModeAsync(OnStoppedVideoCaptureMode);
|
||||
}
|
||||
|
||||
/// <summary> Executes the 'stopped video capture mode' action. </summary>
|
||||
/// <param name="result"> The result.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> 延迟将视频插入相册,确保视频文件已完全写入 </summary>
|
||||
/// <param name="originFilePath"> 视频文件路径 </param>
|
||||
/// <param name="displayName"> 显示名称 </param>
|
||||
/// <param name="folderName"> 文件夹名称 </param>
|
||||
private async UniTask DelayInsertVideoToGallery(string originFilePath, string displayName, string folderName)
|
||||
{
|
||||
await UniTask.DelayFrame(100);
|
||||
InsertVideoToGallery(originFilePath, displayName, folderName);
|
||||
}
|
||||
|
||||
/// <summary> 将视频插入到系统相册中 </summary>
|
||||
/// <param name="originFilePath"> 视频文件的原始路径 </param>
|
||||
/// <param name="displayName"> 视频在相册中显示的名称 </param>
|
||||
/// <param name="folderName"> 相册文件夹名称 </param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
3
Assets/Script/Recorder/XrealMixedRecorder.cs.meta
Normal file
3
Assets/Script/Recorder/XrealMixedRecorder.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a4221a28a1840a3aae5572edfa217be
|
||||
timeCreated: 1779783401
|
||||
@@ -5,6 +5,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Script;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
@@ -511,5 +512,37 @@ namespace Stary.Evo
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static async UniTask Upload(string filePath, string serverBaseUrl, string meetingId, string userId,
|
||||
MainPanel.UsersItem host,
|
||||
List<MainPanel.UsersItem> participants)
|
||||
{
|
||||
var bytes = File.ReadAllBytes(filePath);
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
var ext = Path.GetExtension(fileName).ToLowerInvariant();
|
||||
var mimeType = ext == ".webm" ? "video/webm" : "video/mp4";
|
||||
|
||||
|
||||
var form = new List<IMultipartFormSection>
|
||||
{
|
||||
new MultipartFormDataSection("meetingId", meetingId),
|
||||
new MultipartFormDataSection("userId", userId),
|
||||
new MultipartFormDataSection("filename", fileName),
|
||||
new MultipartFormFileSection("recording", bytes, fileName, mimeType),
|
||||
new MultipartFormDataSection("host", JsonConvert.SerializeObject(host)),
|
||||
new MultipartFormDataSection("participants", JsonConvert.SerializeObject(participants))
|
||||
};
|
||||
|
||||
using var request = UnityWebRequest.Post($"{serverBaseUrl}/api/recordings", form);
|
||||
request.timeout = 300;
|
||||
request.certificateHandler = new SelfSignedCertHandler(certificateData);
|
||||
await request.SendWebRequest();
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success)
|
||||
Debug.LogError($"上传失败: {request.error}, body={request.downloadHandler.text}");
|
||||
else
|
||||
Debug.Log($"上传成功: {request.downloadHandler.text}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"com.unity.ide.rider": "3.0.40",
|
||||
"com.unity.inputsystem": "1.19.0",
|
||||
"com.unity.nuget.newtonsoft-json": "3.2.2",
|
||||
"com.unity.recorder": "4.0.3",
|
||||
"com.unity.render-pipelines.universal": "17.3.0",
|
||||
"com.unity.test-framework": "1.1.33",
|
||||
"com.unity.ugui": "2.0.0",
|
||||
|
||||
@@ -91,6 +91,15 @@
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.recorder": {
|
||||
"version": "4.0.3",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.timeline": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.render-pipelines.core": {
|
||||
"version": "14.0.12",
|
||||
"depth": 1,
|
||||
@@ -167,6 +176,18 @@
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.timeline": {
|
||||
"version": "1.7.7",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.modules.audio": "1.0.0",
|
||||
"com.unity.modules.director": "1.0.0",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ugui": {
|
||||
"version": "2.0.0",
|
||||
"depth": 0,
|
||||
|
||||
Reference in New Issue
Block a user