MediaPlayerSync.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #if AVPROVIDEO_SUPPORT_BUFFERED_DISPLAY
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using RenderHeads.Media.AVProVideo;
  6. //-----------------------------------------------------------------------------
  7. // Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
  8. //-----------------------------------------------------------------------------
  9. namespace RenderHeads.Media.AVProVideo.Experimental
  10. {
  11. /// <summary>
  12. /// Syncronise multiple MediaPlayer components (currently Windows ONLY using Media Foundation ONLY)
  13. /// This feature requires Ultra Edition
  14. /// </summary>
  15. [AddComponentMenu("AVPro Video/Media Player Sync (BETA)", -90)]
  16. [HelpURL("https://www.renderheads.com/products/avpro-video/")]
  17. public class MediaPlayerSync : MonoBehaviour
  18. {
  19. [SerializeField] MediaPlayer _masterPlayer = null;
  20. [SerializeField] MediaPlayer[] _slavePlayers = null;
  21. [SerializeField] bool _playOnStart = true;
  22. [SerializeField] bool _waitAfterPreroll = false;
  23. [SerializeField] bool _logSyncErrors = false;
  24. public MediaPlayer MasterPlayer { get { return _masterPlayer; } set { _masterPlayer = value; } }
  25. public MediaPlayer[] SlavePlayers { get { return _slavePlayers; } set { _slavePlayers = value; } }
  26. public bool PlayOnStart { get { return _playOnStart; } set { _playOnStart = value; } }
  27. public bool WaitAfterPreroll { get { return _waitAfterPreroll; } set { _waitAfterPreroll = value; } }
  28. public bool LogSyncErrors { get { return _logSyncErrors; } set { _logSyncErrors = value; } }
  29. private enum State
  30. {
  31. Idle,
  32. Loading,
  33. Prerolling,
  34. Prerolled,
  35. Playing,
  36. Finished,
  37. }
  38. private State _state = State.Idle;
  39. void Awake()
  40. {
  41. #if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
  42. SetupPlayers();
  43. #else
  44. Debug.LogError("[AVProVideo] This component only works on the Windows platform");
  45. this.enabled = false;
  46. #endif
  47. }
  48. void Start()
  49. {
  50. if (_playOnStart)
  51. {
  52. StartPlayback();
  53. _state = State.Loading;
  54. _playOnStart = false;
  55. }
  56. }
  57. public void OpenMedia(string[] mediaPaths)
  58. {
  59. Debug.Assert(mediaPaths.Length == (_slavePlayers.Length + 1));
  60. _masterPlayer.MediaSource = MediaSource.Path;
  61. _masterPlayer.MediaPath = new MediaPath(mediaPaths[0], MediaPathType.AbsolutePathOrURL);
  62. for (int i = 0; i < _slavePlayers.Length; i++)
  63. {
  64. _slavePlayers[i].MediaSource = MediaSource.Path;
  65. _slavePlayers[i].MediaPath = new MediaPath(mediaPaths[i+1], MediaPathType.AbsolutePathOrURL);
  66. }
  67. StartPlayback();
  68. }
  69. /// <summary>
  70. /// This is called when _autoPlay is false and once the MediaPlayers have had their source media set
  71. /// </summary>
  72. [ContextMenu("StartPlayback")]
  73. public void StartPlayback()
  74. {
  75. SetupPlayers();
  76. if (!IsPrerolled())
  77. {
  78. OpenMediaAll();
  79. _state = State.Loading;
  80. }
  81. else
  82. {
  83. PlayAll();
  84. _state = State.Playing;
  85. }
  86. }
  87. public void Seek(double time, bool approximate = true)
  88. {
  89. if (approximate)
  90. {
  91. SeekFastAll(time);
  92. }
  93. else
  94. {
  95. SeekAll(time);
  96. }
  97. _state = State.Prerolling;
  98. }
  99. public bool IsPrerolled()
  100. {
  101. return (_state == State.Prerolled);
  102. }
  103. void SetupPlayers()
  104. {
  105. SetupPlayer(_masterPlayer);
  106. for (int i = 0; i < _slavePlayers.Length; i++)
  107. {
  108. SetupPlayer(_slavePlayers[i]);
  109. }
  110. }
  111. void SetupPlayer(MediaPlayer player)
  112. {
  113. bool isMaster = (player == _masterPlayer);
  114. player.AutoOpen = false;
  115. player.AutoStart = false;
  116. player.AudioMuted = !isMaster;
  117. player.PlatformOptionsWindows.videoApi = Windows.VideoApi.MediaFoundation;
  118. player.PlatformOptionsWindows.useLowLatency = true;
  119. player.PlatformOptionsWindows.pauseOnPrerollComplete = true;
  120. player.PlatformOptionsWindows.bufferedFrameSelection = isMaster ? BufferedFrameSelectionMode.ElapsedTimeVsynced : BufferedFrameSelectionMode.FromExternalTime;
  121. }
  122. // NOTE: We check on LateUpdate() as MediaPlayer uses Update() to update state and we want to make sure all players have been updated
  123. void LateUpdate()
  124. {
  125. if (_state == State.Idle)
  126. {
  127. }
  128. if (_state == State.Loading)
  129. {
  130. UpdateLoading();
  131. }
  132. if (_state == State.Prerolling)
  133. {
  134. UpdatePrerolling();
  135. }
  136. if (_state == State.Prerolled)
  137. {
  138. /*if (Input.GetKeyDown(KeyCode.Alpha0))
  139. {
  140. StartPlayback();
  141. }*/
  142. }
  143. if (_state == State.Playing)
  144. {
  145. UpdatePlaying();
  146. }
  147. if (_state == State.Finished)
  148. {
  149. }
  150. #if UNITY_EDITOR
  151. if (Input.GetKeyDown(KeyCode.Alpha5))
  152. {
  153. Debug.Log("sleep");
  154. System.Threading.Thread.Sleep(16);
  155. }
  156. /*if (Input.GetKeyDown(KeyCode.Alpha1))
  157. {
  158. double time = Random.Range(0f, (float)_masterPlayer.Info.GetDuration());
  159. Seek(time);
  160. }
  161. long gcMemory = System.GC.GetTotalMemory(false);
  162. //Debug.Log("GC: " + (gcMemory / 1024) + " " + (gcMemory - lastGcMemory));
  163. if ((gcMemory - lastGcMemory) < 0)
  164. {
  165. Debug.LogWarning("COLLECTION!!! " + (lastGcMemory - gcMemory));
  166. }
  167. lastGcMemory = gcMemory;*/
  168. #endif
  169. }
  170. //long lastGcMemory = 0;
  171. void UpdateLoading()
  172. {
  173. // Finished loading?
  174. if (IsAllVideosLoaded())
  175. {
  176. // Assign the master and slaves
  177. _masterPlayer.BufferedDisplay.SetBufferedDisplayMode(BufferedFrameSelectionMode.ElapsedTimeVsynced);
  178. IBufferedDisplay[] slaves = new IBufferedDisplay[_slavePlayers.Length];
  179. for (int i = 0; i < _slavePlayers.Length; i++)
  180. {
  181. slaves[i] = _slavePlayers[i].BufferedDisplay;
  182. }
  183. _masterPlayer.BufferedDisplay.SetSlaves(slaves);
  184. //System.Threading.Thread.Sleep(1250);
  185. // Begin preroll
  186. PlayAll();
  187. _state = State.Prerolling;
  188. }
  189. }
  190. void UpdatePrerolling()
  191. {
  192. if (IsAllVideosPaused())
  193. {
  194. //System.Threading.Thread.Sleep(250);
  195. if (_waitAfterPreroll)
  196. {
  197. _state = State.Prerolled;
  198. }
  199. else
  200. {
  201. PlayAll();
  202. _state = State.Playing;
  203. }
  204. }
  205. }
  206. void UpdatePlaying()
  207. {
  208. if (_masterPlayer.Control.IsPlaying())
  209. {
  210. if (_logSyncErrors)
  211. {
  212. CheckSync();
  213. CheckSmoothness();
  214. }
  215. BufferedFramesState state = _masterPlayer.BufferedDisplay.GetBufferedFramesState();
  216. if (state.bufferedFrameCount < 3)
  217. {
  218. //Debug.LogWarning("FORCE SLEEP");
  219. System.Threading.Thread.Sleep(16);
  220. }
  221. }
  222. else
  223. {
  224. // Pause slaves
  225. for (int i = 0; i < _slavePlayers.Length; i++)
  226. {
  227. MediaPlayer slave = _slavePlayers[i];
  228. slave.Pause();
  229. }
  230. }
  231. // Finished?
  232. if (IsPlaybackFinished(_masterPlayer))
  233. {
  234. _state = State.Finished;
  235. }
  236. }
  237. private long _lastTimeStamp;
  238. private int _sameFrameCount;
  239. void CheckSmoothness()
  240. {
  241. long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
  242. //int frameCount = _masterPlayer.TextureProducer.GetTextureFrameCount();
  243. long frameDuration = (long)(10000000f / _masterPlayer.Info.GetVideoFrameRate());
  244. long vsyncDuration = (long)((QualitySettings.vSyncCount * 10000000f) / (float)Screen.currentResolution.refreshRate);
  245. float vsyncFrames = (float)vsyncDuration / frameDuration;
  246. float fractionalFrames = vsyncFrames - Mathf.FloorToInt(vsyncFrames);
  247. if (fractionalFrames == 0f)
  248. {
  249. if (QualitySettings.vSyncCount != 0)
  250. {
  251. if (!Mathf.Approximately(_sameFrameCount, vsyncFrames))
  252. {
  253. Debug.LogWarning("Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + vsyncFrames);
  254. }
  255. }
  256. }
  257. long d = (timeStamp - _lastTimeStamp);
  258. if (d != 0)
  259. {
  260. long threshold = 10000;
  261. if (d > frameDuration + threshold ||
  262. d < frameDuration - threshold)
  263. {
  264. Debug.LogWarning("Possible frame skip, " + timeStamp + " " + d);
  265. }
  266. _sameFrameCount = 1;
  267. }
  268. else
  269. {
  270. _sameFrameCount++;
  271. }
  272. _lastTimeStamp = timeStamp;
  273. //Debug.Log(frameDuration);
  274. }
  275. void CheckSync()
  276. {
  277. long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
  278. bool inSync = true;
  279. foreach (MediaPlayer slavePlayer in _slavePlayers)
  280. {
  281. if (slavePlayer.TextureProducer.GetTextureTimeStamp() != timeStamp)
  282. {
  283. inSync = false;
  284. break;
  285. }
  286. }
  287. if (!inSync)
  288. {
  289. LogSyncState();
  290. Debug.LogWarning("OUT OF SYNC!!!!!!!");
  291. //Debug.Break();
  292. }
  293. else
  294. {
  295. //LogSyncState();
  296. }
  297. }
  298. void LogSyncState()
  299. {
  300. string text = "Time - Full,Free\t\tRange\n";
  301. text += LogSyncState(_masterPlayer) + "\n";
  302. foreach (MediaPlayer slavePlayer in _slavePlayers)
  303. {
  304. text += LogSyncState(slavePlayer) + "\n";
  305. }
  306. Debug.Log(text);
  307. }
  308. string LogSyncState(MediaPlayer player)
  309. {
  310. BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
  311. long timeStamp = player.TextureProducer.GetTextureTimeStamp();
  312. string result = string.Format("{4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
  313. return result;
  314. }
  315. void OpenMediaAll()
  316. {
  317. _masterPlayer.OpenMedia(autoPlay:false);
  318. for (int i = 0; i < _slavePlayers.Length; i++)
  319. {
  320. _slavePlayers[i].OpenMedia(autoPlay:false);
  321. }
  322. }
  323. void PauseAll()
  324. {
  325. _masterPlayer.Pause();
  326. for (int i = 0; i < _slavePlayers.Length; i++)
  327. {
  328. _slavePlayers[i].Pause();
  329. }
  330. }
  331. void PlayAll()
  332. {
  333. _masterPlayer.Play();
  334. for (int i = 0; i < _slavePlayers.Length; i++)
  335. {
  336. _slavePlayers[i].Play();
  337. }
  338. }
  339. void SeekAll(double time)
  340. {
  341. _masterPlayer.Control.Seek(time);
  342. foreach (MediaPlayer player in _slavePlayers)
  343. {
  344. player.Control.Seek(time);
  345. }
  346. }
  347. void SeekFastAll(double time)
  348. {
  349. _masterPlayer.Control.SeekFast(time);
  350. foreach (MediaPlayer player in _slavePlayers)
  351. {
  352. player.Control.SeekFast(time);
  353. }
  354. }
  355. bool IsAllVideosLoaded()
  356. {
  357. bool result = false;
  358. if (IsVideoLoaded(_masterPlayer))
  359. {
  360. result = true;
  361. for (int i = 0; i < _slavePlayers.Length; i++)
  362. {
  363. if (!IsVideoLoaded(_slavePlayers[i]))
  364. {
  365. result = false;
  366. break;
  367. }
  368. }
  369. }
  370. return result;
  371. }
  372. bool IsAllVideosPaused()
  373. {
  374. bool result = false;
  375. if (IsVideoPaused(_masterPlayer))
  376. {
  377. result = true;
  378. for (int i = 0; i < _slavePlayers.Length; i++)
  379. {
  380. if (!IsVideoPaused(_slavePlayers[i]))
  381. {
  382. result = false;
  383. break;
  384. }
  385. }
  386. }
  387. return result;
  388. }
  389. static bool IsPlaybackFinished(MediaPlayer player)
  390. {
  391. bool result = false;
  392. if (player != null && player.Control != null)
  393. {
  394. if (player.Control.IsFinished())
  395. {
  396. BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
  397. if (state.bufferedFrameCount == 0)
  398. {
  399. result = true;
  400. }
  401. }
  402. }
  403. return result;
  404. }
  405. static bool IsVideoLoaded(MediaPlayer player)
  406. {
  407. return (player != null && player.Control != null && player.Control.HasMetaData() && player.Control.CanPlay());
  408. }
  409. static bool IsVideoPaused(MediaPlayer player)
  410. {
  411. return (player != null && player.Control != null && player.Control.IsPaused());
  412. }
  413. }
  414. }
  415. #endif