#if UNITY_2017_1_OR_NEWER
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine.Playables;
//-----------------------------------------------------------------------------
// Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProMovieCapture
{
///
/// Controls timeline updates time during offline captures
/// This class used to try to control the timestep of the Timeline, but features like Markers/Signals no longer work in Manual update mode
/// So now we just change any DSPClock directors to GameTime
///
[AddComponentMenu("AVPro Movie Capture/Utils/Timeline Controller", 300)]
public class TimelineController : MonoBehaviour
{
public enum ScanFrequencyMode
{
SceneLoad,
Frame,
}
[SerializeField] ScanFrequencyMode _scanFrequency = ScanFrequencyMode.SceneLoad;
public ScanFrequencyMode ScanFrequency
{
get { return _scanFrequency; }
set { _scanFrequency = value; ResetSceneLoading(); }
}
internal class TimelineInstance
{
private PlayableDirector _director = null;
private DirectorUpdateMode _originalTimeUpdateMode = DirectorUpdateMode.DSPClock;
private bool _isControlling = false;
private bool _isCapturing = false;
internal TimelineInstance(PlayableDirector director)
{
_director = director;
}
internal bool Is(PlayableDirector director)
{
return (_director == director);
}
internal void StartCapture()
{
// First capture to touch the playable directors
if (!_isCapturing)
{
// Null check in case director no longer exists
if (_director != null)
{
// Want to manually update?
// TODO: should we include ALL directors, as they may switch from manual to something else later on?
// DSPClock doesn't change rate when rendering offline, so we need to change to GameTime
_isControlling = (_director.timeUpdateMode == DirectorUpdateMode.DSPClock);
if (_isControlling)
{
// Cache original update mode
_originalTimeUpdateMode = _director.timeUpdateMode;
bool wasPlaying = (_director.state == PlayState.Playing);
// Set to manual update mode
// NOTE: Prior to Unity 2018.2 changing from DSP Clock to Manual did nothing, as DSP Clock mode was set to ignore manual updates
_director.timeUpdateMode = DirectorUpdateMode.GameTime;
// NOTE: In newer versions of Unity (post 2018.2) changing the timeUpdateMode to Manual pauses playback, so we must resume it
if (wasPlaying && _director.state == PlayState.Paused)
{
_director.Resume();
}
}
}
_isCapturing = true;
}
}
#if false
internal void Update(float deltaTime)
{
if (_isControlling && _isCapturing)
{
if (_director != null && _director.isActiveAndEnabled)
{
if (_director.state == PlayState.Playing)
{
double time = _director.time + deltaTime;
if (time < _director.duration)
{
_director.time = time;
_director.Evaluate();
}
else
{
switch (_director.extrapolationMode)
{
case DirectorWrapMode.Loop:
_director.time = time % _director.duration;
_director.Evaluate();
break;
case DirectorWrapMode.Hold:
_director.time = _director.duration;
_director.Evaluate();
break;
case DirectorWrapMode.None:
_director.time = 0f;
_director.Pause();
break;
}
}
}
}
}
}
#endif
internal void StopCapture()
{
if (_isCapturing)
{
// TODO: what happens to the director when the scene is unloaded?
if (_director != null)
{
// We were controlling?
if (_isControlling)
{
bool wasPlaying = (_director.state == PlayState.Playing);
// Revert update mode to original
_director.timeUpdateMode = _originalTimeUpdateMode;
if (wasPlaying)
{
// Timeline seems to get paused after changing play mode (in some versions of Unity), only a pause and resume keeps it going
_director.Pause();
_director.Resume();
}
_isControlling = false;
}
}
_isCapturing = false;
}
}
}
private List _timelines = new List(8);
void Awake()
{
ResetSceneLoading();
}
void OnValidate()
{
ResetSceneLoading();
}
internal void UpdateFrame()
{
if (_scanFrequency == ScanFrequencyMode.Frame)
{
ScanForPlayableDirectors();
}
#if false
foreach (TimelineInstance timeline in _timelines)
{
timeline.Update(Time.deltaTime);
}
#endif
}
internal void StartCapture()
{
ScanForPlayableDirectors();
foreach (TimelineInstance timeline in _timelines)
{
timeline.StartCapture();
}
}
internal void StopCapture()
{
foreach (TimelineInstance timeline in _timelines)
{
timeline.StopCapture();
}
}
public void ScanForPlayableDirectors()
{
// Remove any timeline instances with deleted (null) directors
for (int i = 0; i < _timelines.Count; i++)
{
TimelineInstance timeline = _timelines[i];
if (timeline.Is(null))
{
_timelines.RemoveAt(i); i--;
}
}
// Find all inactive and active directors
PlayableDirector[] directors = Resources.FindObjectsOfTypeAll();
// Create a unique instance for each director
foreach (PlayableDirector playableDirector in directors)
{
// Check we don't already have this director
bool hasDirector = false;
foreach (TimelineInstance timeline in _timelines)
{
if (timeline.Is(playableDirector))
{
hasDirector = true;
break;
}
}
// Add to the list
if (!hasDirector)
{
_timelines.Add(new TimelineInstance(playableDirector));
}
}
}
void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
StopCapture();
}
void ResetSceneLoading()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
if (_scanFrequency == ScanFrequencyMode.SceneLoad)
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (_scanFrequency == ScanFrequencyMode.SceneLoad)
{
ScanForPlayableDirectors();
}
}
}
}
#endif