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(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 } }