Skip to content

Commit

Permalink
Merge pull request #4 from keijiro/dev-editmode
Browse files Browse the repository at this point in the history
Edit mode and Timeline support
  • Loading branch information
keijiro authored Feb 6, 2019
2 parents b9f3f64 + d32f9c3 commit 3cbac2f
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 89 deletions.
83 changes: 54 additions & 29 deletions Assets/Klak/Hap/Editor/HapPlayerEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,43 @@ sealed class HapPlayerEditor : Editor
SerializedProperty _targetRenderer;
SerializedProperty _targetMaterialProperty;

string _sourceInfo;

public string SourceInfo { get {
if (_sourceInfo != null) return _sourceInfo;

var player = (HapPlayer)target;
_sourceInfo = string.Format(
"{0}\n" +
"Codec: {1}\n" +
"Frame dimensions: {2} x {3}\n" +
"Stream duration: {4:0.00}\n" +
"Frame rate: {5:0.00}",
player.resolvedFilePath, player.codecType,
player.frameWidth, player.frameHeight,
player.streamDuration,
player.frameCount / player.streamDuration
);

return _sourceInfo;
} }

static class Labels
{
public static readonly GUIContent Property = new GUIContent("Property");
public static readonly GUIContent Select = new GUIContent("Select");
}

string _sourceInfo;

void ShowSourceInfo(HapPlayer player)
{
if (!player.enabled || !player.gameObject.activeInHierarchy) return;

if (!player.isValid)
{
EditorGUILayout.HelpBox(
"Failed to open file. " +
"Please specify a valid HAP-encoded .mov file.",
MessageType.Warning
);
return;
}

if (_sourceInfo == null)
_sourceInfo = string.Format(
"Codec: {0}\n" +
"Frame dimensions: {1} x {2}\n" +
"Stream duration: {3:0.00}\n" +
"Frame rate: {4:0.00}",
player.codecType,
player.frameWidth, player.frameHeight,
player.streamDuration,
player.frameCount / player.streamDuration
);

EditorGUILayout.HelpBox(_sourceInfo, MessageType.None);
}

void OnEnable()
{
_filePath = serializedObject.FindProperty("_filePath");
Expand All @@ -61,19 +71,24 @@ void OnEnable()

public override void OnInspectorGUI()
{
var reload = false;

serializedObject.Update();

if (!Application.isPlaying)
// Source infomation
if (!_filePath.hasMultipleDifferentValues &&
!_pathMode.hasMultipleDifferentValues &&
!string.IsNullOrEmpty(_filePath.stringValue))
{
// Source file
EditorGUILayout.PropertyField(_filePath);
EditorGUILayout.PropertyField(_pathMode);
}
else if (targets.Length == 1)
{
EditorGUILayout.HelpBox(SourceInfo, MessageType.None);
ShowSourceInfo((HapPlayer)target);
}

// Source file
EditorGUI.BeginChangeCheck();
EditorGUILayout.DelayedTextField(_filePath);
EditorGUILayout.PropertyField(_pathMode);
reload = EditorGUI.EndChangeCheck();

// Playback control
EditorGUILayout.PropertyField(_time);
EditorGUILayout.PropertyField(_speed);
Expand All @@ -99,6 +114,16 @@ public override void OnInspectorGUI()
EditorGUI.indentLevel--;

serializedObject.ApplyModifiedProperties();

if (reload)
{
// This is a little bit scary hack but we can force a HapPlayer
// to reload a given video file by invoking OnDestroy.
foreach (HapPlayer hp in targets) hp.SendMessage("OnDestroy");

// Also the source information string should be refreshed.
_sourceInfo = null;
}
}
}
}
145 changes: 101 additions & 44 deletions Assets/Klak/Hap/Runtime/HapPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

namespace Klak.Hap
{
[AddComponentMenu("Klak/HAP/HAP Player")]
public sealed class HapPlayer : MonoBehaviour
[ExecuteInEditMode, AddComponentMenu("Klak/HAP/HAP Player")]
public sealed class HapPlayer : MonoBehaviour, ITimeControl, IPropertyPreview
{
#region Editable attributes

Expand Down Expand Up @@ -58,6 +60,7 @@ public string targetMaterialProperty {

#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; } }
Expand Down Expand Up @@ -115,7 +118,13 @@ void OpenInternal()

if (!_demuxer.IsValid)
{
Debug.LogError("Failed to open stream (" + resolvedFilePath + ").");
if (Application.isPlaying)
{
// In play mode, show an error message, then disable itself
// to prevent spamming the console.
Debug.LogError("Failed to open stream (" + resolvedFilePath + ").");
enabled = false;
}
_demuxer.Dispose();
_demuxer = null;
return;
Expand All @@ -136,19 +145,9 @@ void OpenInternal()
Utility.DetermineTextureFormat(_demuxer.VideoType), false
);
_texture.wrapMode = TextureWrapMode.Clamp;
_updater = new TextureUpdater(_texture, _decoder);

// Start the updater coroutine if async update is not supported.
if (!TextureUpdater.AsyncSupport) StartCoroutine(DelayedUpdater());
}
_texture.hideFlags = HideFlags.DontSave;

System.Collections.IEnumerator DelayedUpdater()
{
for (var eof = new WaitForEndOfFrame(); enabled;)
{
_updater.UpdateNow();
yield return eof;
}
_updater = new TextureUpdater(_texture, _decoder);
}

#endregion
Expand All @@ -164,7 +163,10 @@ void UpdateTargetTexture()

// Material lazy initialization
if (_blitMaterial == null)
{
_blitMaterial = new Material(Utility.DetermineShader(_demuxer.VideoType));
_blitMaterial.hideFlags = HideFlags.DontSave;
}

// Blit
Graphics.Blit(_texture, _targetTexture, _blitMaterial, 0);
Expand All @@ -186,21 +188,44 @@ void UpdateTargetRenderer()

#endregion

#region MonoBehaviour implementation
#region ITimeControl implementation

bool _externalTime;

void Start()
public void OnControlTimeStart()
{
if (_demuxer == null && !string.IsNullOrEmpty(_filePath))
OpenInternal();
_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;
}

void OnEnable()
#endregion

#region IPropertyPreview implementation

public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
{
// Updater coroutine restart
if (_updater != null && !TextureUpdater.AsyncSupport)
StartCoroutine(DelayedUpdater());
}

#endregion

#region MonoBehaviour implementation

void OnDestroy()
{
if (_updater != null)
Expand All @@ -227,37 +252,69 @@ void OnDestroy()
_demuxer = null;
}

if (_texture != null)
{
Destroy(_texture);
_texture = null;
}

if (_blitMaterial != null)
{
Destroy(_blitMaterial);
_blitMaterial = null;
}
Utility.Destroy(_texture);
Utility.Destroy(_blitMaterial);
}

void LateUpdate()
{
// Lazy initialization of demuxer
if (_demuxer == null && !string.IsNullOrEmpty(_filePath))
OpenInternal();

// Do nothing if the demuxer hasn't been instantiated.
if (_demuxer == null) return;

// Restart the stream reader when the time/speed were changed.
if (_time != _storedTime || _speed != _storedSpeed)
var duration = (float)_demuxer.Duration;

// Check if _time is still in the same frame of _storedTime.
// Resync is needed when it went out of the frame.
var dt = duration / _demuxer.FrameCount;
var resync = _time < _storedTime || _time > _storedTime + dt;

// Check if the speed was externally modified.
if (_speed != _storedSpeed)
{
_stream.Restart(_time, _speed / 60);
(_storedTime, _storedSpeed) = (_time, _speed);
resync = true; // Resync to adapt to the new speed.
_storedSpeed = _speed;
}

// Decode and update
_decoder.UpdateTime(_time);
if (TextureUpdater.AsyncSupport) _updater.RequestAsyncUpdate();
// Time clamping
var t = _loop ? _time : Mathf.Clamp(_time, 0, duration);

// Determine if background decoding is available.
// Resync shouldn't happen. Not preferable in edit mode.
var bgdec = !resync && Application.isPlaying;

// Restart the stream reader on resync.
if (resync) _stream.Restart(t, _speed / 60);

if (TextureUpdater.AsyncSupport)
{
// Asynchronous texture update supported:
// Decode a frame and request a texture update.
if (bgdec) _decoder.UpdateAsync(t); else _decoder.UpdateSync(t);
_updater.RequestAsyncUpdate();
}
else if (bgdec)
{
// Synchronous texture update with background decoding:
// Update first, then start background decoding. This
// introduces a single frame delay but makes it possible to
// offload decoding load to a background thread.
_updater.UpdateNow();
_decoder.UpdateAsync(t);
}
else
{
// Synchronous decoding and texture update.
_decoder.UpdateSync(t);
_updater.UpdateNow();
}

// Time advance
_time += Time.deltaTime * _speed;
if (!_loop) _time = Mathf.Clamp(_time, 0, (float)_demuxer.Duration);
// Update the stored time.
if (Application.isPlaying && !_externalTime)
_time += Time.deltaTime * _speed;
_storedTime = _time;

// External object updates
Expand Down
10 changes: 9 additions & 1 deletion Assets/Klak/Hap/Runtime/Internal/Decoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ public int BufferSize { get {
return KlakHap_GetDecoderBufferSize(_plugin);
} }

public void UpdateTime(float time)
public void UpdateSync(float time)
{
_time = time;
var buffer = _stream.Advance(_time);
if (buffer != null)
KlakHap_DecodeFrame(_plugin, buffer.PluginPointer);
}

public void UpdateAsync(float time)
{
_time = time;
_resume.Set();
Expand Down
Loading

0 comments on commit 3cbac2f

Please sign in to comment.