340 lines
9.6 KiB
C#
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
|
|
}
|
|
}
|