340 lines
9.6 KiB
C#

using UnityEngine;
using UnityEngine.Playables;
#if KLAKHAP_HAS_TIMELINE
using UnityEngine.Timeline;
#endif
namespace Klak.Hap
{
[ExecuteInEditMode, AddComponentMenu("Klak/HAP/HAP Player")]
#if KLAKHAP_HAS_TIMELINE
public sealed class HapPlayer : MonoBehaviour , ITimeControl, IPropertyPreview
#else
public sealed class HapPlayer : MonoBehaviour
#endif
{
#region Editable attributes
public enum PathMode { StreamingAssets, LocalFileSystem }
[SerializeField] PathMode _pathMode = PathMode.StreamingAssets;
[SerializeField] string _filePath = "";
[SerializeField] float _time = 0;
[SerializeField, Range(-10, 10)] float _speed = 1;
[SerializeField] bool _loop = true;
[SerializeField] RenderTexture _targetTexture = null;
[SerializeField] string _targetMaterialProperty = "_MainTex";
[SerializeField] bool _flipHorizontal = false;
[SerializeField] bool _flipVertical = true;
#endregion
#region Public properties
public float time {
get { return _time; }
set { _time = value; }
}
public float speed {
get { return _speed; }
set { _speed = value; }
}
public bool loop {
get { return _loop; }
set { _loop = value; }
}
public RenderTexture targetTexture {
get { return _targetTexture; }
set { _targetTexture = value; }
}
public string targetMaterialProperty {
get { return _targetMaterialProperty; }
set { _targetMaterialProperty = value; }
}
public bool flipHorizontal {
get { return _flipHorizontal; }
set { _flipHorizontal = value; }
}
public bool flipVertical {
get { return _flipVertical; }
set { _flipVertical = value; }
}
#endregion
#region Read-only properties
public bool isValid { get { return _demuxer != null; } }
public int frameWidth { get { return _demuxer?.Width ?? 0; } }
public int frameHeight { get { return _demuxer?.Height ?? 0; } }
public int frameCount { get { return _demuxer?.FrameCount ?? 0; } }
public double streamDuration { get { return _demuxer?.Duration ?? 0; } }
public CodecType codecType { get {
return Utility.DetermineCodecType(_demuxer?.VideoType ?? 0);
} }
public string resolvedFilePath { get {
if (_pathMode == PathMode.StreamingAssets)
return System.IO.Path.Combine(Application.streamingAssetsPath, _filePath);
else
return _filePath;
} }
public Texture2D texture { get { return _texture; } }
#endregion
#region Public methods
public void Open(string filePath, PathMode pathMode = PathMode.StreamingAssets)
{
if (_demuxer != null)
{
Debug.LogError("Stream has already been opened.");
return;
}
_filePath = filePath;
_pathMode = pathMode;
OpenInternal();
}
public void UpdateNow()
=> LateUpdate();
#endregion
#region Private members
Demuxer _demuxer;
StreamReader _stream;
Decoder _decoder;
Texture2D _texture;
TextureUpdater _updater;
float _storedTime;
float _storedSpeed;
// Flip-related variables
Material _flipMaterial;
void OpenInternal()
{
// Demuxer instantiation
_demuxer = new Demuxer(resolvedFilePath);
if (!_demuxer.IsValid)
{
if (Application.isPlaying)
{
Debug.LogError("Failed to open stream (" + resolvedFilePath + ").");
enabled = false;
}
_demuxer.Dispose();
_demuxer = null;
return;
}
_stream = new StreamReader(_demuxer, _time, _speed / 60);
(_storedTime, _storedSpeed) = (_time, _speed);
_decoder = new Decoder(
_stream, _demuxer.Width, _demuxer.Height, _demuxer.VideoType
);
_texture = new Texture2D(
_demuxer.Width, _demuxer.Height,
Utility.DetermineTextureFormat(_demuxer.VideoType), false
);
_texture.wrapMode = TextureWrapMode.Clamp;
_texture.hideFlags = HideFlags.DontSave;
_updater = new TextureUpdater(_texture, _decoder);
}
void UpdateTargetTexture()
{
if (_targetTexture == null || _texture == null || _demuxer == null) return;
if (this == null || !enabled) return;
if (_flipMaterial == null)
{
_flipMaterial = new Material(Shader.Find("Hidden/FlipBlit"));
_flipMaterial.hideFlags = HideFlags.DontSave;
}
_flipMaterial.SetFloat("_FlipX", _flipHorizontal ? 1f : 0f);
_flipMaterial.SetFloat("_FlipY", _flipVertical ? 1f : 0f);
try
{
Graphics.Blit(_texture, _targetTexture, _flipMaterial);
}
catch (System.NullReferenceException)
{
return;
}
}
#endregion
#region ITimeControl implementation
bool _externalTime;
public void OnControlTimeStart()
{
_externalTime = true;
// In the external time mode, we can't know the actual playback
// speed but sure that it's positive (Control Track doesn't support
// reverse playback), so we assume that the speed is 1.0.
// Cons: Resync could happen every frame for high speed play back.
_speed = 1;
}
public void OnControlTimeStop()
{
_externalTime = false;
}
public void SetTime(double time)
{
_time = (float)time;
_speed = 1;
}
#endregion
#region IPropertyPreview implementation
#if KLAKHAP_HAS_TIMELINE
public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
{
driver.AddFromName<HapPlayer>(gameObject, "_time");
}
#endif
#endregion
#region MonoBehaviour implementation
void OnDestroy()
{
if (_updater != null)
{
_updater.Dispose();
_updater = null;
}
if (_decoder != null)
{
_decoder.Dispose();
_decoder = null;
}
if (_stream != null)
{
_stream.Dispose();
_stream = null;
}
if (_demuxer != null)
{
_demuxer.Dispose();
_demuxer = null;
}
Utility.Destroy(_texture);
Utility.Destroy(_flipMaterial);
}
#if UNITY_EDITOR
void OnValidate()
{
// 더 이상 필요 없음: 렌더러 관련 클리어 코드 제거
}
#endif
int _lastUpdateFrameCount = -1;
void LateUpdate()
{
if (!enabled || this == null) return;
if (Time.frameCount == _lastUpdateFrameCount) return;
_lastUpdateFrameCount = Time.frameCount;
if (_demuxer == null && !string.IsNullOrEmpty(_filePath))
OpenInternal();
if (_demuxer == null) return;
var duration = (float)_demuxer.Duration;
var dt = duration / _demuxer.FrameCount;
var resync = _time < _storedTime || _time > _storedTime + dt;
if (_speed != _storedSpeed)
{
resync = true;
_storedSpeed = _speed;
}
var t = _loop ? _time : Mathf.Clamp(_time, 0, duration - 1e-4f);
var bgdec = !resync && Application.isPlaying;
if (resync && _stream != null) _stream.Restart(t, _speed / 60);
if (_decoder != null && _updater != null)
{
if (TextureUpdater.AsyncSupport)
{
if (bgdec) _decoder.UpdateAsync(t); else _decoder.UpdateSync(t);
_updater.RequestAsyncUpdate();
}
#if !HAP_NO_DELAY
else if (bgdec)
{
_updater.UpdateNow();
_decoder.UpdateAsync(t);
}
#endif
else
{
_decoder.UpdateSync(t);
_updater.UpdateNow();
}
}
if (Application.isPlaying && !_externalTime)
_time += Time.deltaTime * _speed;
_storedTime = _time;
// 렌더 텍스처만 업데이트
if (this != null && enabled)
{
try
{
if (_targetTexture != null)
UpdateTargetTexture();
}
catch (System.NullReferenceException ex)
{
Debug.LogWarning($"HapPlayer: Null reference in external object updates - {ex.Message}");
}
catch (System.Exception ex)
{
Debug.LogWarning($"HapPlayer: Unexpected error in external object updates - {ex.Message}");
}
}
}
#endregion
}
}