Created
June 12, 2025 06:09
-
-
Save yosun/e260d59702a110c24785d447afd63cc1 to your computer and use it in GitHub Desktop.
fix for editor recording test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* VideoKit | |
* Copyright © 2024 Yusuf Olokoba. All Rights Reserved. | |
*/ | |
#nullable enable | |
namespace VideoKit { | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Threading.Tasks; | |
using UnityEngine; | |
using UnityEngine.Events; | |
using UnityEngine.Serialization; | |
using Unity.Collections; | |
using Clocks; | |
using Sources; | |
using MediaFormat = MediaRecorder.Format; | |
using MediaType = MediaAsset.MediaType; | |
/// <summary> | |
/// VideoKit recorder for recording videos. | |
/// </summary> | |
[Tooltip(@"VideoKit recorder for recording videos.")] | |
[HelpURL(@"https://videokit.ai/reference/videokitrecorder")] | |
[DisallowMultipleComponent] | |
public sealed partial class VideoKitRecorder : MonoBehaviour { | |
#region --Enumerations-- | |
/// <summary> | |
/// Video recording mode. | |
/// </summary> | |
public enum VideoMode : int { | |
/// <summary> | |
/// Don't record video. | |
/// </summary> | |
None = 0, | |
/// <summary> | |
/// Record video frames from one or more game cameras. | |
/// </summary> | |
Camera = 1, | |
/// <summary> | |
/// Record video frames from the screen. | |
/// </summary> | |
Screen = 2, | |
/// <summary> | |
/// Record video frames from a texture. | |
/// </summary> | |
Texture = 3, | |
/// <summary> | |
/// Record video frames from a camera device. | |
/// </summary> | |
CameraDevice = 4, | |
} | |
/// <summary> | |
/// Audio recording mode. | |
/// </summary> | |
public enum AudioMode : int { | |
/// <summary> | |
/// Don't record audio. | |
/// </summary> | |
None = 0b0000, | |
/// <summary> | |
/// Record audio from an audio device. | |
/// </summary> | |
AudioDevice = 0b0010, | |
/// <summary> | |
/// Record audio from an audio listener. | |
/// </summary> | |
AudioListener = 0b0001, | |
/// <summary> | |
/// Record audio from an audio source. | |
/// </summary> | |
AudioSource = 0b0100, | |
} | |
/// <summary> | |
/// Video recording resolution presets. | |
/// </summary> | |
public enum Resolution : int { | |
/// <summary> | |
/// QVGA resolution. | |
/// </summary> | |
_240xAuto = 11, | |
/// <summary> | |
/// QVGA resolution. | |
/// </summary> | |
_320xAuto = 5, | |
/// <summary> | |
/// Portrait SD resolution. | |
/// </summary> | |
[InspectorName(@"480p Portrait")] | |
_480xAuto = 6, | |
/// <summary> | |
/// SD resolution. | |
/// </summary> | |
[InspectorName(@"480p Landscape")] | |
_640xAuto = 0, | |
/// <summary> | |
/// Potrait HD resolution. | |
/// </summary> | |
[InspectorName(@"720p Portrait")] | |
_720xAuto = 7, | |
/// <summary> | |
/// HD resolution. | |
/// </summary> | |
[InspectorName(@"720p Landscape")] | |
_1280xAuto = 1, | |
/// <summary> | |
/// Portrait Full HD resolution. | |
/// </summary> | |
[InspectorName(@"1080p Portrait")] | |
_1080xAuto = 12, | |
/// <summary> | |
/// Full HD resolution. | |
/// </summary> | |
[InspectorName(@"1080p Landscape")] | |
_1920xAuto = 2, | |
/// <summary> | |
/// Portrait 2K WQHD resolution. | |
/// </summary> | |
[InspectorName(@"2K Portrait")] | |
_1440xAuto = 13, | |
/// <summary> | |
/// 2K WQHD resolution. | |
/// </summary> | |
[InspectorName(@"2K Landscape")] | |
_2560xAuto = 3, | |
/// <summary> | |
/// Portrait 4K UHD resolution. | |
/// </summary> | |
[InspectorName(@"4K Portrait")] | |
_2160xAuto = 14, | |
/// <summary> | |
/// 4K UHD resolution. | |
/// </summary> | |
[InspectorName(@"4K Landscape")] | |
_3840xAuto = 4, | |
/// <summary> | |
/// Screen resolution. | |
/// </summary> | |
Screen = 9, | |
/// <summary> | |
/// Half of the screen resolution. | |
/// </summary> | |
HalfScreen = 10, | |
/// <summary> | |
/// Custom resolution. | |
/// </summary> | |
Custom = 8, | |
} | |
/// <summary> | |
/// Recorder status. | |
/// </summary> | |
public enum Status : int { | |
/// <summary> | |
/// No recording session is in progress. | |
/// </summary> | |
Idle = 0, | |
/// <summary> | |
/// Recording session is in progress. | |
/// </summary> | |
Recording = 1, | |
/// <summary> | |
/// Recording session is in progress but is paused. | |
/// </summary> | |
Paused = 2, | |
} | |
/// <summary> | |
/// Video watermark mode. | |
/// </summary> | |
public enum WatermarkMode : int { | |
/// <summary> | |
/// No watermark. | |
/// </summary> | |
None = 0, | |
/// <summary> | |
/// Place watermark in the bottom-left of the frame. | |
/// </summary> | |
BottomLeft = 1, | |
/// <summary> | |
/// Place watermark in the bottom-right of the frame. | |
/// </summary> | |
BottomRight = 2, | |
/// <summary> | |
/// Place watermark in the upper-left of the frame. | |
/// </summary> | |
UpperLeft = 3, | |
/// <summary> | |
/// Place watermark in the upper-right of the frame. | |
/// </summary> | |
UpperRight = 4, | |
/// <summary> | |
/// Place watermark in a user-defined rectangle. | |
/// Set the rect with the `watermarkRect` property. | |
/// </summary> | |
Custom = 5, | |
} | |
/// <summary> | |
/// Recording action. | |
/// </summary> | |
[Flags] | |
public enum RecordingAction : int { | |
/// <summary> | |
/// Nothing. | |
/// </summary> | |
None = 0, | |
/// <summary> | |
/// Save the media asset to the camera roll. | |
/// </summary> | |
CameraRoll = 1 << 1, | |
/// <summary> | |
/// Prompt the user to share the media asset with the native sharing UI. | |
/// </summary> | |
Share = 1 << 2, | |
/// <summary> | |
/// Playback the video with the platform default media player. | |
/// </summary> | |
Playback = 1 << 3, | |
/// <summary> | |
/// Define a custom callback to receive the media asset. | |
/// NOTE: This is mutually exclusive with all other recording actions. | |
/// </summary> | |
Custom = 1 << 5, | |
} | |
#endregion | |
#region --Inspector-- | |
[Header(@"Format")] | |
/// <summary> | |
/// Recording format. | |
/// </summary> | |
[Tooltip(@"Recording format.")] | |
public MediaFormat format = MediaFormat.MP4; | |
/// <summary> | |
/// Prepare the hardware encoders on awake. | |
/// This prevents a noticeable stutter that occurs on the very first recording. | |
/// </summary> | |
[Tooltip(@"Prepare the hardware encoders on awake. This prevents a noticeable stutter that occurs on the very first recording.")] | |
public bool prepareOnAwake = false; | |
[Header(@"Video")] | |
/// <summary> | |
/// Video recording mode. | |
/// </summary> | |
[Tooltip(@"Video recording mode.")] | |
public VideoMode videoMode = VideoMode.Camera; | |
/// <summary> | |
/// Video recording resolution. | |
/// NOTE: This is not supported with `VideoMode.CameraDevice`. | |
/// </summary> | |
[Tooltip(@"Video recording resolution.")] | |
public Resolution resolution = Resolution._1280xAuto; | |
/// <summary> | |
/// Video recording custom resolution. | |
/// NOTE: This is only used when `resolution` is set to `Resolution.Custom`. | |
/// </summary> | |
[Tooltip(@"Video recording custom resolution.")] | |
public Vector2Int customResolution = new Vector2Int(1280, 720); | |
/// <summary> | |
/// Game cameras to record. | |
/// </summary> | |
[Tooltip(@"Game cameras to record.")] | |
public Camera[] cameras = new Camera[0]; | |
/// <summary> | |
/// Recording texture for recording video frames from a texture. | |
/// </summary> | |
[Tooltip(@"Recording texture for recording video frames from a texture.")] | |
public Texture? texture; | |
/// <summary> | |
/// Camera manager for recording video frames from a camera device. | |
/// </summary> | |
[Tooltip(@"Camera manager for recording video frames from a camera device.")] | |
public VideoKitCameraManager? cameraManager; | |
/// <summary> | |
/// Frame rate for animated GIF images. | |
/// This only applies when recording GIF images. | |
/// </summary> | |
[Tooltip(@"Frame rate for animated GIF images."), Range(5f, 30f), FormerlySerializedAs(@"frameRate")] | |
public float _frameRate = 10f; | |
/// <summary> | |
/// Number of successive camera frames to skip while recording. | |
/// NOTE: This is not supported with `VideoMode.CameraDevice`. | |
/// </summary> | |
[Tooltip(@"Number of successive camera frames to skip while recording."), Range(0, 5)] | |
public int frameSkip = 0; | |
[Header(@"Watermark")] | |
/// <summary> | |
/// Recording watermark mode for adding a watermark to videos. | |
/// </summary> | |
[Tooltip(@"Recording watermark mode for adding a watermark to videos.")] | |
public WatermarkMode watermarkMode = WatermarkMode.None; | |
/// <summary> | |
/// Recording watermark. | |
/// </summary> | |
[SerializeField, FormerlySerializedAs(@"watermark"), Tooltip(@"Recording watermark.")] | |
private Texture? _watermark; | |
/// <summary> | |
/// Watermark display rect when `watermarkMode` is set to `WatermarkMode.Custom`. | |
/// </summary> | |
[SerializeField, FormerlySerializedAs(@"watermarkRect"), Tooltip(@"Watermark display rect when `watermarkMode` is set to `WatermarkMode.Custom`")] | |
private RectInt _watermarkRect; | |
[Header(@"Audio")] | |
/// <summary> | |
/// Audio recording mode. | |
/// </summary> | |
[Tooltip(@"Audio recording mode.")] | |
public AudioMode audioMode = AudioMode.None; | |
/// <summary> | |
/// Audio manager for recording audio from an audio device. | |
/// </summary> | |
[Tooltip(@"Audio manager for recording audio from an audio device.")] | |
public VideoKitAudioManager? audioManager; | |
/// <summary> | |
/// Whether the recorder can configure the audio manager for recording. | |
/// Unless you intend to override the audio manager configuration, leave this `true`. | |
/// </summary> | |
[Tooltip(@"Whether the recorder can configure the audio manager for recording.")] | |
public bool configureAudioManager = true; | |
/// <summary> | |
/// Audio listener for recording audio from an audio listener. | |
/// </summary> | |
[Tooltip(@"Audio listener for recording audio from an audio listener.")] | |
public AudioListener? audioListener; | |
/// <summary> | |
/// Audio source for recording audio from an audio source. | |
/// </summary> | |
[Tooltip(@"Audio source for recording audio from an audio source.")] | |
public AudioSource? audioSource; | |
[Header(@"Recording")] | |
/// <summary> | |
/// Recording action. | |
/// </summary> | |
[Tooltip(@"Recording action.")] | |
public RecordingAction recordingAction = 0; | |
/// <summary> | |
/// Event raised when a recording session is completed. | |
/// </summary> | |
[Tooltip(@"Event raised when a recording session is completed.")] | |
public UnityEvent<MediaAsset>? OnRecordingCompleted; | |
#endregion | |
#region --Client API-- | |
/// <summary> | |
/// Recording path prefix when saving recordings to the app's documents. | |
/// </summary> | |
[HideInInspector] | |
public string mediaPathPrefix = @"recordings"; | |
/// <summary> | |
/// Video bit rate in bits per second. | |
/// </summary> | |
[HideInInspector] | |
public int videoBitRate = 20_000_000; | |
/// <summary> | |
/// Video keyframe interval in seconds. | |
/// </summary> | |
[HideInInspector] | |
public int keyframeInterval = 2; | |
/// <summary> | |
/// Audio bit rate in bits per second. | |
/// </summary> | |
[HideInInspector] | |
public int audioBitRate = 64_000; | |
/// <summary> | |
/// Recording watermark. | |
/// </summary> | |
public Texture? watermark { | |
get => textureSource?.watermark ?? _watermark; | |
set { | |
_watermark = value; | |
if (textureSource != null) | |
textureSource.watermark = value; | |
} | |
} | |
/// <summary> | |
/// Watermark display rect when `watermarkMode` is set to `WatermarkMode.Custom`. | |
/// </summary> | |
public RectInt watermarkRect { | |
get => textureSource?.watermarkRect ?? _watermarkRect; | |
set { | |
_watermarkRect = value; | |
if (textureSource != null) | |
textureSource.watermarkRect = value; | |
} | |
} | |
/// <summary> | |
/// Recorder status. | |
/// </summary> | |
public Status status => clock?.paused switch { | |
true => Status.Paused, | |
false => Status.Recording, | |
null => Status.Idle, | |
}; | |
/// <summary> | |
/// Start recording. | |
/// </summary> | |
public async void StartRecording () => await StartRecordingAsync(); | |
/// <summary> | |
/// Start recording. | |
/// </summary> | |
public async Task StartRecordingAsync () { | |
// Check active | |
if (!isActiveAndEnabled) | |
throw new InvalidOperationException(@"VideoKitRecorder cannot start recording because component is disabled"); | |
// Check status | |
if (status != Status.Idle) | |
throw new InvalidOperationException(@"VideoKitRecorder cannot start recording because a recording session is already in progress"); | |
// Check camera device mode | |
if (videoMode == VideoMode.CameraDevice) { | |
// Check camera manager | |
if (cameraManager == null) | |
throw new InvalidOperationException(@"VideoKitRecorder cannot start recording because the video mode is set to `VideoMode.CameraDevice` but `cameraManager` is null"); | |
// Check camera preview | |
if (!cameraManager.running) | |
throw new InvalidOperationException(@"VideoKitRecorder cannot start recording because the video mode is set to `VideoMode.CameraDevice` but the `cameraManager` is not running"); | |
} | |
// Check audio mode | |
if (audioMode.HasFlag(AudioMode.AudioListener) && Application.platform == RuntimePlatform.WebGLPlayer) { | |
Debug.LogWarning(@"VideoKitRecorder cannot record audio from AudioListener because WebGL does not support `OnAudioFilterRead`"); | |
audioMode &= ~AudioMode.AudioListener; | |
} | |
// Check audio device | |
if (audioMode.HasFlag(AudioMode.AudioDevice)) { | |
// Check audio manager | |
if (audioManager == null) | |
throw new InvalidOperationException(@"VideoKitRecorder cannot start recording because the audio mode includes `AudioMode.AudioDevice` but `audioManager` is null"); | |
// Configure audio manager | |
if (configureAudioManager) { | |
// Set format | |
if (audioMode.HasFlag(AudioMode.AudioListener)) { | |
audioManager.sampleRate = VideoKitAudioManager.SampleRate.MatchUnity; | |
audioManager.channelCount = VideoKitAudioManager.ChannelCount.MatchUnity; | |
} | |
// Start running | |
await audioManager.StartRunningAsync(); | |
} | |
} | |
// Check format | |
if (format == MediaFormat.MP4 && Application.platform == RuntimePlatform.WebGLPlayer) { | |
format = MediaFormat.WEBM; | |
Debug.LogWarning(@"VideoKitRecorder will use WEBM format on WebGL because MP4 is not supported"); | |
} | |
// Create recorder | |
recorder = await MediaRecorder.Create( | |
format, | |
width: width, | |
height: height, | |
frameRate: frameRate, | |
sampleRate: sampleRate, | |
channelCount: channelCount, | |
videoBitRate: videoBitRate, | |
keyframeInterval: keyframeInterval, | |
compressionQuality: 0.8f, | |
audioBitRate: audioBitRate, | |
prefix: mediaPathPrefix | |
); | |
// Create inputs | |
clock = new RealtimeClock(); | |
videoInput = CreateVideoInput(recorder!.width, recorder.height, recorder.Append); | |
audioInput = CreateAudioInput(); | |
} | |
/// <summary> | |
/// Pause recording. | |
/// </summary> | |
[Obsolete(@"Deprecated in VideoKit 0.0.20 and will be removed soon after.", false)] | |
public void PauseRecording () { | |
// Check | |
if (status != Status.Recording) { | |
Debug.LogError(@"Cannot pause recording because no recording session is in progress"); | |
return; | |
} | |
// Stop audio manager | |
if (configureAudioManager && audioManager != null) | |
audioManager.StopRunning(); | |
// Dispose inputs | |
videoInput?.Dispose(); | |
audioInput?.Dispose(); | |
videoInput = null; | |
audioInput = null; | |
// Pause clock | |
clock!.paused = true; | |
} | |
/// <summary> | |
/// Resume recording. | |
/// </summary> | |
[Obsolete(@"Deprecated in VideoKit 0.0.20 and will be removed soon after.", false)] | |
public void ResumeRecording () { | |
// Check status | |
if (status != Status.Paused) { | |
Debug.LogError(@"Cannot resume recording because the recording session is not paused"); | |
return; | |
} | |
// Check active | |
if (!isActiveAndEnabled) { | |
Debug.LogError(@"Cannot resume recording because component is disabled"); | |
return; | |
} | |
// Check audio manager | |
if (configureAudioManager && audioManager != null) | |
audioManager.StartRunning(); | |
// Unpause clock | |
clock!.paused = false; | |
// Create inputs | |
videoInput = CreateVideoInput(recorder!.width, recorder.height, recorder.Append); | |
audioInput = CreateAudioInput(); | |
} | |
/// <summary> | |
/// Stop recording. | |
/// </summary> | |
public async void StopRecording () => await StopRecordingAsync(); | |
/// <summary> | |
/// Stop recording. | |
/// </summary> | |
public async Task StopRecordingAsync () { | |
// Check | |
if (status == Status.Idle) { | |
Debug.LogWarning(@"Cannot stop recording because no recording session is in progress"); | |
return; | |
} | |
// Stop audio manager | |
if (configureAudioManager && audioManager != null) | |
audioManager.StopRunning(); | |
// Stop inputs | |
audioInput?.Dispose(); | |
videoInput?.Dispose(); | |
videoInput = null; | |
audioInput = null; | |
clock = null; | |
// Stop recording | |
var asset = await recorder!.FinishWriting(); | |
// Check that this is not result of disabling // CHECK // Delete asset? | |
if (!isActiveAndEnabled) | |
return; | |
// Post action | |
if (recordingAction.HasFlag(RecordingAction.Custom)) | |
OnRecordingCompleted?.Invoke(asset); | |
if (recordingAction.HasFlag(RecordingAction.CameraRoll)) | |
{ | |
#if UNITY_EDITOR | |
Debug.LogWarning(@"VideoKitRecorder cannot save to camera roll in the editor. This will only work in a built application. See ./recordings folder"); | |
#else | |
await asset.SaveToCameraRoll(); | |
#endif | |
} | |
if (recordingAction.HasFlag(RecordingAction.Share)) | |
await asset.Share(); | |
if (recordingAction.HasFlag(RecordingAction.Playback) && asset.type == MediaType.Video) { | |
#if UNITY_IOS || UNITY_ANDROID | |
Handheld.PlayFullScreenMovie($"file://{asset.path}"); | |
#endif | |
} | |
} | |
/// <summary> | |
/// Capture a screenshot with the current video settings. | |
/// </summary> | |
/// <returns>Screenshot image asset.</returns> | |
public async Task<MediaAsset> CaptureScreenshot () { | |
var recorder = await MediaRecorder.Create( | |
MediaFormat.JPEG, | |
width, | |
height, | |
compressionQuality: 0.8f, | |
prefix: mediaPathPrefix | |
); | |
var tcs = new TaskCompletionSource<MediaAsset>(); | |
IDisposable? source = null; | |
source = CreateVideoInput( | |
recorder.width, | |
recorder.height, | |
async pixelBuffer => { | |
recorder.Append(pixelBuffer); | |
source!.Dispose(); | |
var sequenceAsset = await recorder.FinishWriting(); | |
var imageAsset = sequenceAsset.assets[0]; | |
tcs.SetResult(imageAsset); | |
} | |
); | |
var result = await tcs.Task; | |
return result; | |
} | |
#endregion | |
#region --Operations-- | |
private MediaRecorder? recorder; | |
private RealtimeClock? clock; | |
private IDisposable? videoInput; | |
private IDisposable? audioInput; | |
private int width => resolution switch { | |
var _ when videoMode == 0 => 0, | |
var _ when videoMode == VideoMode.CameraDevice => cameraManager!.texture!.width, | |
Resolution._240xAuto => 240, | |
Resolution._320xAuto => 320, | |
Resolution._480xAuto => 480, | |
Resolution._640xAuto => 640, | |
Resolution._720xAuto => 720, | |
Resolution._1080xAuto => 1080, | |
Resolution._1280xAuto => 1280, | |
Resolution._1920xAuto => 1920, | |
Resolution._1440xAuto => 1440, | |
Resolution._2560xAuto => 2560, | |
Resolution._3840xAuto => 3840, | |
Resolution.Screen => Screen.width >> 1 << 1, | |
Resolution.HalfScreen => Screen.width >> 2 << 1, | |
Resolution.Custom => customResolution.x, | |
_ => 1280, | |
}; | |
private float aspect => videoMode switch { | |
VideoMode.Camera => (float)Screen.width / Screen.height, | |
VideoMode.Screen => (float)Screen.width / Screen.height, | |
VideoMode.Texture => (float)texture!.width / texture!.height, | |
_ => 0f, | |
}; | |
private int height => resolution switch { | |
var _ when videoMode == 0 => 0, | |
var _ when videoMode == VideoMode.CameraDevice => cameraManager!.texture!.height, | |
Resolution.Custom => customResolution.y, | |
Resolution.Screen => Screen.height >> 1 << 1, | |
Resolution.HalfScreen => Screen.height >> 2 << 1, | |
_ => Mathf.RoundToInt(width / aspect) >> 1 << 1, | |
}; | |
private float frameRate => videoMode switch { | |
var _ when format == MediaFormat.GIF => _frameRate, | |
VideoMode.CameraDevice => cameraManager!.device!.frameRate, | |
_ => 30, | |
}; | |
private int sampleRate => audioMode switch { | |
AudioMode.AudioDevice => audioManager?.device?.sampleRate ?? 0, | |
AudioMode.AudioListener => AudioSettings.outputSampleRate, | |
AudioMode.AudioSource => AudioSettings.outputSampleRate, | |
_ => 0, | |
}; | |
private int channelCount => audioMode switch { | |
AudioMode.AudioDevice => audioManager?.device?.channelCount ?? 0, | |
AudioMode.AudioListener => (int)AudioSettings.speakerMode, | |
AudioMode.AudioSource => (int)AudioSettings.speakerMode, | |
_ => 0, | |
}; | |
private TextureSource? textureSource => videoInput switch { | |
CameraSource cameraSource => cameraSource.textureSource, | |
ScreenSource screenSource => screenSource.textureSource, | |
TextureSource textureSource => textureSource, | |
_ => null, | |
}; | |
private void Reset () { | |
cameras = Camera.allCameras; | |
cameraManager = FindObjectOfType<VideoKitCameraManager>(); | |
audioManager = FindObjectOfType<VideoKitAudioManager>(); | |
audioListener = FindObjectOfType<AudioListener>(); | |
} | |
private async void Awake () { | |
if (prepareOnAwake) | |
await PrepareEncoder(); | |
} | |
private void OnDestroy () { | |
if (status != Status.Idle) | |
StopRecording(); | |
} | |
private IDisposable? CreateVideoInput ( | |
int width, | |
int height, | |
Action<PixelBuffer> handler | |
) => videoMode switch { | |
var _ when !MediaRecorder.CanAppend<PixelBuffer>(format) => null, | |
VideoMode.Screen => new ScreenSource(width, height, handler, clock) { frameSkip = frameSkip }, | |
VideoMode.Camera => new CameraSource(width, height, handler, clock, cameras) { frameSkip = frameSkip }, | |
VideoMode.Texture => new TextureSource(width, height, handler, clock) { texture = texture, frameSkip = frameSkip }, | |
VideoMode.CameraDevice => new CameraManagerSource(cameraManager!, handler, clock) { }, | |
_ => null | |
}; | |
private IDisposable? CreateAudioInput () => audioMode switch { | |
var _ when !MediaRecorder.CanAppend<AudioBuffer>(format) => null, | |
AudioMode.AudioDevice => new AudioManagerSource(recorder!, clock, audioManager!), | |
AudioMode.AudioListener => new AudioComponentSource(recorder!, clock, audioListener!), | |
AudioMode.AudioSource => new AudioComponentSource(recorder!, clock, audioSource!), | |
_ => null, | |
}; | |
#endregion | |
#region --Utility-- | |
private static RectInt CreateWatermarkRect (MediaRecorder recorder, WatermarkMode mode, RectInt customRect) { | |
// Check none | |
if (mode == WatermarkMode.None) | |
return default; | |
// Check custom | |
if (mode == WatermarkMode.Custom) | |
return customRect; | |
// Construct rect | |
var NormalizedPositions = new Dictionary<WatermarkMode, Vector2> { | |
[WatermarkMode.BottomLeft] = new Vector2(0.2f, 0.15f), | |
[WatermarkMode.BottomRight] = new Vector2(0.8f, 0.15f), | |
[WatermarkMode.UpperLeft] = new Vector2(0.2f, 0.85f), | |
[WatermarkMode.UpperRight] = new Vector2(0.8f, 0.85f), | |
}; | |
var normalizedPosition = NormalizedPositions[mode]; | |
var position = Vector2Int.RoundToInt(Vector2.Scale(normalizedPosition, new Vector2(recorder.width, recorder.height))); | |
var size = Mathf.RoundToInt(0.15f * Mathf.Max(recorder.width, recorder.height)); | |
var rect = new RectInt(position.x - size / 1, position.y - size / 1, size, size); | |
return rect; | |
} | |
private static async Task PrepareEncoder () { | |
try { | |
// Create recorder | |
var clock = new FixedClock(30); | |
var recorder = await MediaRecorder.Create( | |
MediaFormat.MP4, | |
width: 1280, | |
height: 720, | |
frameRate: 30 | |
); | |
// Commit empty frames | |
using var pixelData = new NativeArray<byte>( | |
recorder.width * recorder.height * 4, | |
Allocator.Persistent, | |
NativeArrayOptions.ClearMemory | |
); | |
var format = PixelBuffer.Format.RGBA8888; | |
for (var i = 0; i < 3; ++i) { | |
using var pixelBuffer = new PixelBuffer( | |
recorder.width, | |
recorder.height, | |
format, | |
pixelData, | |
timestamp: | |
clock.timestamp | |
); | |
recorder.Append(pixelBuffer); | |
} | |
// Finish and delete | |
var asset = await recorder.FinishWriting(); | |
File.Delete(asset.path); | |
} catch { } | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment