/*----------------------------------------------------------------
// Copyright (C) 2025 Beijing All rights reserved.
//
// Author: huachangmiao
// Create Date: 2025/04/11
// Module Describe: UI RawImage 视频渲染组件
//----------------------------------------------------------------*/
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
///
/// UI RawImage 视频渲染组件
/// 继承自 LeviathanVideoDecoderBase,用于在 UI RawImage 上显示视频
///
///
[RequireComponent(typeof(RawImage))]
public unsafe class LeviathanVideoDecoder : LeviathanVideoDecoderBase
{
private static readonly int ChromaKeyColorId = Shader.PropertyToID("_KeyColor");
private static readonly int ChromaKeyCutoffId = Shader.PropertyToID("_ColorCutoff");
private static readonly int ChromaKeyColorFeatheringId = Shader.PropertyToID("_ColorFeathering");
private static readonly int ChromaKeyMaskFeatheringId = Shader.PropertyToID("_MaskFeathering");
private static readonly int ChromaKeySharpeningId = Shader.PropertyToID("_Sharpening");
private Material ChromaKeyMaterial
{
get
{
if (_alphaType != PlayAlphaType.ChromaKey) return null;
var rawImage = GetComponent();
if (rawImage == null || rawImage.material == null) return null;
return rawImage.material.HasProperty(ChromaKeyColorId) ? rawImage.material : null;
}
}
public void SetChromaKeyColor(Color color)
{
var mat = ChromaKeyMaterial;
if (mat != null) mat.SetColor(ChromaKeyColorId, color);
}
public Color GetChromaKeyColor()
{
var mat = ChromaKeyMaterial;
return mat != null ? mat.GetColor(ChromaKeyColorId) : Color.green;
}
public void SetChromaKeyCutoff(float value)
{
var mat = ChromaKeyMaterial;
if (mat != null) mat.SetFloat(ChromaKeyCutoffId, Mathf.Clamp01(value));
}
public float GetChromaKeyCutoff()
{
var mat = ChromaKeyMaterial;
return mat != null ? mat.GetFloat(ChromaKeyCutoffId) : 0.2f;
}
public void SetChromaKeyColorFeathering(float value)
{
var mat = ChromaKeyMaterial;
if (mat != null) mat.SetFloat(ChromaKeyColorFeatheringId, Mathf.Clamp01(value));
}
public float GetChromaKeyColorFeathering()
{
var mat = ChromaKeyMaterial;
return mat != null ? mat.GetFloat(ChromaKeyColorFeatheringId) : 0.33f;
}
public void SetChromaKeyMaskFeathering(float value)
{
var mat = ChromaKeyMaterial;
if (mat != null) mat.SetFloat(ChromaKeyMaskFeatheringId, Mathf.Clamp01(value));
}
public float GetChromaKeyMaskFeathering()
{
var mat = ChromaKeyMaterial;
return mat != null ? mat.GetFloat(ChromaKeyMaskFeatheringId) : 1f;
}
public void SetChromaKeySharpening(float value)
{
var mat = ChromaKeyMaterial;
if (mat != null) mat.SetFloat(ChromaKeySharpeningId, Mathf.Clamp01(value));
}
public float GetChromaKeySharpening()
{
var mat = ChromaKeyMaterial;
return mat != null ? mat.GetFloat(ChromaKeySharpeningId) : 0.5f;
}
public void SetChromaKeyParams(Color keyColor, float cutoff, float colorFeathering, float maskFeathering, float sharpening)
{
var mat = ChromaKeyMaterial;
if (mat == null) return;
mat.SetColor(ChromaKeyColorId, keyColor);
mat.SetFloat(ChromaKeyCutoffId, Mathf.Clamp01(cutoff));
mat.SetFloat(ChromaKeyColorFeatheringId, Mathf.Clamp01(colorFeathering));
mat.SetFloat(ChromaKeyMaskFeatheringId, Mathf.Clamp01(maskFeathering));
mat.SetFloat(ChromaKeySharpeningId, Mathf.Clamp01(sharpening));
}
private new static void Log(string message)
{
LeviathanLogConfig.Log("VideoDecoder", message);
}
private new static void LogWarning(string message)
{
LeviathanLogConfig.LogWarning("VideoDecoder", message);
}
private new static void LogError(string message)
{
LeviathanLogConfig.LogError("VideoDecoder", message);
}
///
/// 启用渲染目标
///
protected override void OnEnableRenderTarget()
{
RawImage rawImage = GetComponent();
if (rawImage)
{
rawImage.enabled = true;
}
}
///
/// 禁用渲染目标
///
protected override void OnDisableRenderTarget()
{
RawImage rawImage = GetComponent();
if (rawImage)
{
rawImage.enabled = false;
}
}
///
/// 更新渲染目标尺寸
///
protected override void OnUpdateRenderTargetSize()
{
RawImage rawImage = GetComponent();
if (rawImage)
{
UpdateImageSize(rawImage);
}
}
///
/// 初始化材质
///
protected override void OnInitMaterial()
{
RawImage rawImage = GetComponent();
if (rawImage)
{
rawImage.texture = _textures[0];
UpdateImageMaterial(rawImage);
}
}
///
/// 更新材质
///
protected override void OnUpdateMaterial()
{
RawImage rawImage = GetComponent();
if (rawImage)
{
UpdateImageMaterial(rawImage);
}
}
protected void UpdateImageSize(RawImage rawImage)
{
if (!disableAutoSize)
{
var videoFrame = VideoFrame;
if (videoFrame != null && videoFrame->width > 0)
{
rawImage.SetNativeSize();
rawImage.rectTransform.sizeDelta = _alphaType switch
{
PlayAlphaType.RightSide => new Vector2(videoFrame->width * 0.5f, videoFrame->height),
PlayAlphaType.BottomSide => new Vector2(videoFrame->width, videoFrame->height * 0.5f),
_ => new Vector2(videoFrame->width, videoFrame->height)
};
}
}
rawImage.uvRect = new Rect(0, 0, _alphaType == PlayAlphaType.RightSide ? 1 : (float)_videoValidWidth / _videoWidth, 1);
}
protected void UpdateImageMaterial(RawImage rawImage)
{
// 软件解码器使用YUV纹理,需要YUV420P着色器
Material newMaterial;
switch (_alphaType)
{
case PlayAlphaType.RightSide:
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_Alpha_Right"));
newMaterial.SetFloat(ValidWidthRatioIdx, (float)_videoValidWidth / _videoWidth);
break;
case PlayAlphaType.BottomSide:
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_Alpha_Bottom"));
break;
case PlayAlphaType.ChromaKey:
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P_ChromaKey"));
break;
case PlayAlphaType.None:
default:
newMaterial = new Material(Shader.Find("LeviathanVideo/YUV420P"));
break;
}
newMaterial.SetTexture(UTexIdx, _textures[1]);
newMaterial.SetTexture(VTexIdx, _textures[2]);
rawImage.material = newMaterial;
}
#if UNITY_EDITOR
private void OnValidate()
{
// 使用 delayCall 避免在 OnValidate 中调用 SendMessage 相关操作
var currentBytesFile = bytesFile;
var lastBytesFile = _lastBytesFile;
UnityEditor.EditorApplication.delayCall += () =>
{
if (this == null) return;
SetAlphaType(_alphaType);
// 检测 bytesFile 是否发生变化
if (currentBytesFile != lastBytesFile)
{
_lastBytesFile = currentBytesFile;
if (IsPlaying && currentBytesFile != null)
{
Log($"编辑器中检测到 bytesFile 变化,自动播放新视频");
Stop();
Play();
}
}
};
}
#endif
}
#if UNITY_EDITOR
[CustomEditor(typeof(LeviathanVideoDecoder))]
public class LeviathanVideoDecoderEditor : Editor
{
// 格式化时间:秒转换为 HH:MM:SS.mmm 格式
private static string FormatTimeSeconds(double totalSeconds)
{
int hours = (int)(totalSeconds / 3600);
int minutes = (int)((totalSeconds % 3600) / 60);
int seconds = (int)(totalSeconds % 60);
int milliseconds = (int)((totalSeconds % 1) * 1000);
return $"{hours:D2}:{minutes:D2}:{seconds:D2}.{milliseconds:D3}";
}
// 格式化时间:微秒转换为 HH:MM:SS.mmm 格式
private static string FormatTimeMicroseconds(long microseconds)
{
return FormatTimeSeconds(microseconds / 1000000.0);
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var mono = (LeviathanVideoDecoder)target;
// Display video dimensions as read-only fields
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("帧数", $"{mono.PlayFrameIndex} / {mono.FrameCount}");
// FrameInterval 单位是秒,Duration 单位是微秒
EditorGUILayout.TextField("播放时间", FormatTimeSeconds(mono.PlayFrameIndex * mono.FrameInterval));
EditorGUILayout.TextField("总时间", FormatTimeMicroseconds(mono.Duration));
EditorGUILayout.TextField("FPS", mono.FrameInterval > 0 ? $"{1.0 / mono.FrameInterval:F2}" : "0");
EditorGUILayout.TextField("纹理尺寸", $"{mono.VideoWidth} × {mono.VideoHeight}");
EditorGUILayout.TextField("视频有效尺寸", $"{mono.VideoValidWidth} × {mono.VideoHeight}");
EditorGUI.EndDisabledGroup();
{
bool oldValue = mono.IsPlaying;
// 绘制checkbox
bool newValue = EditorGUILayout.Toggle("isPlaying", oldValue);
// 如果用户切换了checkbox
if (oldValue != newValue)
{
if (newValue)
{
mono.Play();
}
else
{
mono.Stop();
}
EditorUtility.SetDirty(target);
}
}
{
bool oldValue = mono.IsPause;
// 绘制checkbox
bool newValue = EditorGUILayout.Toggle("isPause", oldValue);
// 如果用户切换了checkbox
if (oldValue != newValue)
{
if (newValue)
{
mono.Pause();
}
else
{
mono.Resume();
}
EditorUtility.SetDirty(target);
}
}
if (mono.AlphaType == LeviathanVideoDecoderBase.PlayAlphaType.ChromaKey)
{
DrawChromaKeyParameters(mono);
}
this.Repaint();
}
private void DrawChromaKeyParameters(LeviathanVideoDecoder mono)
{
var rawImage = mono.GetComponent();
if (rawImage == null || rawImage.material == null) return;
if (!rawImage.material.HasProperty(Shader.PropertyToID("_KeyColor"))) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("ChromaKey 参数", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
Color keyColor = EditorGUILayout.ColorField("KeyColor", mono.GetChromaKeyColor());
float cutoff = EditorGUILayout.Slider("Cutoff", mono.GetChromaKeyCutoff(), 0f, 1f);
float colorFeathering = EditorGUILayout.Slider("ColorFeathering", mono.GetChromaKeyColorFeathering(), 0f, 1f);
float maskFeathering = EditorGUILayout.Slider("MaskFeathering", mono.GetChromaKeyMaskFeathering(), 0f, 1f);
float sharpening = EditorGUILayout.Slider("Sharpening", mono.GetChromaKeySharpening(), 0f, 1f);
if (EditorGUI.EndChangeCheck())
{
mono.SetChromaKeyParams(keyColor, cutoff, colorFeathering, maskFeathering, sharpening);
}
}
}
#endif