TimelineController.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. #if UNITY_2017_1_OR_NEWER
  2. using UnityEngine;
  3. using System.Collections.Generic;
  4. using UnityEngine.SceneManagement;
  5. using UnityEngine.Playables;
  6. //-----------------------------------------------------------------------------
  7. // Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
  8. //-----------------------------------------------------------------------------
  9. namespace RenderHeads.Media.AVProMovieCapture
  10. {
  11. /// <summary>
  12. /// Controls timeline updates time during offline captures
  13. /// This class used to try to control the timestep of the Timeline, but features like Markers/Signals no longer work in Manual update mode
  14. /// So now we just change any DSPClock directors to GameTime
  15. /// </summary>
  16. [AddComponentMenu("AVPro Movie Capture/Utils/Timeline Controller", 300)]
  17. public class TimelineController : MonoBehaviour
  18. {
  19. public enum ScanFrequencyMode
  20. {
  21. SceneLoad,
  22. Frame,
  23. }
  24. [SerializeField] ScanFrequencyMode _scanFrequency = ScanFrequencyMode.SceneLoad;
  25. public ScanFrequencyMode ScanFrequency
  26. {
  27. get { return _scanFrequency; }
  28. set { _scanFrequency = value; ResetSceneLoading(); }
  29. }
  30. internal class TimelineInstance
  31. {
  32. private PlayableDirector _director = null;
  33. private DirectorUpdateMode _originalTimeUpdateMode = DirectorUpdateMode.DSPClock;
  34. private bool _isControlling = false;
  35. private bool _isCapturing = false;
  36. internal TimelineInstance(PlayableDirector director)
  37. {
  38. _director = director;
  39. }
  40. internal bool Is(PlayableDirector director)
  41. {
  42. return (_director == director);
  43. }
  44. internal void StartCapture()
  45. {
  46. // First capture to touch the playable directors
  47. if (!_isCapturing)
  48. {
  49. // Null check in case director no longer exists
  50. if (_director != null)
  51. {
  52. // Want to manually update?
  53. // TODO: should we include ALL directors, as they may switch from manual to something else later on?
  54. // DSPClock doesn't change rate when rendering offline, so we need to change to GameTime
  55. _isControlling = (_director.timeUpdateMode == DirectorUpdateMode.DSPClock);
  56. if (_isControlling)
  57. {
  58. // Cache original update mode
  59. _originalTimeUpdateMode = _director.timeUpdateMode;
  60. bool wasPlaying = (_director.state == PlayState.Playing);
  61. // Set to manual update mode
  62. // NOTE: Prior to Unity 2018.2 changing from DSP Clock to Manual did nothing, as DSP Clock mode was set to ignore manual updates
  63. _director.timeUpdateMode = DirectorUpdateMode.GameTime;
  64. // NOTE: In newer versions of Unity (post 2018.2) changing the timeUpdateMode to Manual pauses playback, so we must resume it
  65. if (wasPlaying && _director.state == PlayState.Paused)
  66. {
  67. _director.Resume();
  68. }
  69. }
  70. }
  71. _isCapturing = true;
  72. }
  73. }
  74. #if false
  75. internal void Update(float deltaTime)
  76. {
  77. if (_isControlling && _isCapturing)
  78. {
  79. if (_director != null && _director.isActiveAndEnabled)
  80. {
  81. if (_director.state == PlayState.Playing)
  82. {
  83. double time = _director.time + deltaTime;
  84. if (time < _director.duration)
  85. {
  86. _director.time = time;
  87. _director.Evaluate();
  88. }
  89. else
  90. {
  91. switch (_director.extrapolationMode)
  92. {
  93. case DirectorWrapMode.Loop:
  94. _director.time = time % _director.duration;
  95. _director.Evaluate();
  96. break;
  97. case DirectorWrapMode.Hold:
  98. _director.time = _director.duration;
  99. _director.Evaluate();
  100. break;
  101. case DirectorWrapMode.None:
  102. _director.time = 0f;
  103. _director.Pause();
  104. break;
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. #endif
  112. internal void StopCapture()
  113. {
  114. if (_isCapturing)
  115. {
  116. // TODO: what happens to the director when the scene is unloaded?
  117. if (_director != null)
  118. {
  119. // We were controlling?
  120. if (_isControlling)
  121. {
  122. bool wasPlaying = (_director.state == PlayState.Playing);
  123. // Revert update mode to original
  124. _director.timeUpdateMode = _originalTimeUpdateMode;
  125. if (wasPlaying)
  126. {
  127. // Timeline seems to get paused after changing play mode (in some versions of Unity), only a pause and resume keeps it going
  128. _director.Pause();
  129. _director.Resume();
  130. }
  131. _isControlling = false;
  132. }
  133. }
  134. _isCapturing = false;
  135. }
  136. }
  137. }
  138. private List<TimelineInstance> _timelines = new List<TimelineInstance>(8);
  139. void Awake()
  140. {
  141. ResetSceneLoading();
  142. }
  143. void OnValidate()
  144. {
  145. ResetSceneLoading();
  146. }
  147. internal void UpdateFrame()
  148. {
  149. if (_scanFrequency == ScanFrequencyMode.Frame)
  150. {
  151. ScanForPlayableDirectors();
  152. }
  153. #if false
  154. foreach (TimelineInstance timeline in _timelines)
  155. {
  156. timeline.Update(Time.deltaTime);
  157. }
  158. #endif
  159. }
  160. internal void StartCapture()
  161. {
  162. ScanForPlayableDirectors();
  163. foreach (TimelineInstance timeline in _timelines)
  164. {
  165. timeline.StartCapture();
  166. }
  167. }
  168. internal void StopCapture()
  169. {
  170. foreach (TimelineInstance timeline in _timelines)
  171. {
  172. timeline.StopCapture();
  173. }
  174. }
  175. public void ScanForPlayableDirectors()
  176. {
  177. // Remove any timeline instances with deleted (null) directors
  178. for (int i = 0; i < _timelines.Count; i++)
  179. {
  180. TimelineInstance timeline = _timelines[i];
  181. if (timeline.Is(null))
  182. {
  183. _timelines.RemoveAt(i); i--;
  184. }
  185. }
  186. // Find all inactive and active directors
  187. PlayableDirector[] directors = Resources.FindObjectsOfTypeAll<PlayableDirector>();
  188. // Create a unique instance for each director
  189. foreach (PlayableDirector playableDirector in directors)
  190. {
  191. // Check we don't already have this director
  192. bool hasDirector = false;
  193. foreach (TimelineInstance timeline in _timelines)
  194. {
  195. if (timeline.Is(playableDirector))
  196. {
  197. hasDirector = true;
  198. break;
  199. }
  200. }
  201. // Add to the list
  202. if (!hasDirector)
  203. {
  204. _timelines.Add(new TimelineInstance(playableDirector));
  205. }
  206. }
  207. }
  208. void OnDestroy()
  209. {
  210. SceneManager.sceneLoaded -= OnSceneLoaded;
  211. StopCapture();
  212. }
  213. void ResetSceneLoading()
  214. {
  215. SceneManager.sceneLoaded -= OnSceneLoaded;
  216. if (_scanFrequency == ScanFrequencyMode.SceneLoad)
  217. {
  218. SceneManager.sceneLoaded += OnSceneLoaded;
  219. }
  220. }
  221. void OnSceneLoaded(Scene scene, LoadSceneMode mode)
  222. {
  223. if (_scanFrequency == ScanFrequencyMode.SceneLoad)
  224. {
  225. ScanForPlayableDirectors();
  226. }
  227. }
  228. }
  229. }
  230. #endif