UP-Viagg-io/Viagg-io/Assets/Packages/AVProVideo/Runtime/Scripts/Components/MediaPlayerSync.cs

469 lines
11 KiB
C#
Executable File

#if AVPROVIDEO_SUPPORT_BUFFERED_DISPLAY
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RenderHeads.Media.AVProVideo;
//-----------------------------------------------------------------------------
// Copyright 2015-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo.Experimental
{
/// <summary>
/// Syncronise multiple MediaPlayer components (currently Windows ONLY using Media Foundation ONLY)
/// This feature requires Ultra Edition
/// </summary>
[AddComponentMenu("AVPro Video/Media Player Sync (BETA)", -90)]
[HelpURL("https://www.renderheads.com/products/avpro-video/")]
public class MediaPlayerSync : MonoBehaviour
{
[SerializeField] MediaPlayer _masterPlayer = null;
[SerializeField] MediaPlayer[] _slavePlayers = null;
[SerializeField] bool _playOnStart = true;
[SerializeField] bool _waitAfterPreroll = false;
[SerializeField] bool _logSyncErrors = false;
public MediaPlayer MasterPlayer { get { return _masterPlayer; } set { _masterPlayer = value; } }
public MediaPlayer[] SlavePlayers { get { return _slavePlayers; } set { _slavePlayers = value; } }
public bool PlayOnStart { get { return _playOnStart; } set { _playOnStart = value; } }
public bool WaitAfterPreroll { get { return _waitAfterPreroll; } set { _waitAfterPreroll = value; } }
public bool LogSyncErrors { get { return _logSyncErrors; } set { _logSyncErrors = value; } }
private enum State
{
Idle,
Loading,
Prerolling,
Prerolled,
Playing,
Finished,
}
private State _state = State.Idle;
void Awake()
{
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
SetupPlayers();
#else
Debug.LogError("[AVProVideo] This component only works on the Windows platform");
this.enabled = false;
#endif
}
void Start()
{
if (_playOnStart)
{
StartPlayback();
_state = State.Loading;
_playOnStart = false;
}
}
public void OpenMedia(string[] mediaPaths)
{
Debug.Assert(mediaPaths.Length == (_slavePlayers.Length + 1));
_masterPlayer.MediaSource = MediaSource.Path;
_masterPlayer.MediaPath = new MediaPath(mediaPaths[0], MediaPathType.AbsolutePathOrURL);
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].MediaSource = MediaSource.Path;
_slavePlayers[i].MediaPath = new MediaPath(mediaPaths[i+1], MediaPathType.AbsolutePathOrURL);
}
StartPlayback();
}
/// <summary>
/// This is called when _autoPlay is false and once the MediaPlayers have had their source media set
/// </summary>
[ContextMenu("StartPlayback")]
public void StartPlayback()
{
SetupPlayers();
if (!IsPrerolled())
{
OpenMediaAll();
_state = State.Loading;
}
else
{
PlayAll();
_state = State.Playing;
}
}
public void Seek(double time, bool approximate = true)
{
if (approximate)
{
SeekFastAll(time);
}
else
{
SeekAll(time);
}
_state = State.Prerolling;
}
public bool IsPrerolled()
{
return (_state == State.Prerolled);
}
void SetupPlayers()
{
SetupPlayer(_masterPlayer);
for (int i = 0; i < _slavePlayers.Length; i++)
{
SetupPlayer(_slavePlayers[i]);
}
}
void SetupPlayer(MediaPlayer player)
{
bool isMaster = (player == _masterPlayer);
player.AutoOpen = false;
player.AutoStart = false;
player.AudioMuted = !isMaster;
player.PlatformOptionsWindows.videoApi = Windows.VideoApi.MediaFoundation;
player.PlatformOptionsWindows.useLowLatency = true;
player.PlatformOptionsWindows.pauseOnPrerollComplete = true;
player.PlatformOptionsWindows.bufferedFrameSelection = isMaster ? BufferedFrameSelectionMode.ElapsedTimeVsynced : BufferedFrameSelectionMode.FromExternalTime;
}
// NOTE: We check on LateUpdate() as MediaPlayer uses Update() to update state and we want to make sure all players have been updated
void LateUpdate()
{
if (_state == State.Idle)
{
}
if (_state == State.Loading)
{
UpdateLoading();
}
if (_state == State.Prerolling)
{
UpdatePrerolling();
}
if (_state == State.Prerolled)
{
/*if (Input.GetKeyDown(KeyCode.Alpha0))
{
StartPlayback();
}*/
}
if (_state == State.Playing)
{
UpdatePlaying();
}
if (_state == State.Finished)
{
}
#if UNITY_EDITOR
if (Input.GetKeyDown(KeyCode.Alpha5))
{
Debug.Log("sleep");
System.Threading.Thread.Sleep(16);
}
/*if (Input.GetKeyDown(KeyCode.Alpha1))
{
double time = Random.Range(0f, (float)_masterPlayer.Info.GetDuration());
Seek(time);
}
long gcMemory = System.GC.GetTotalMemory(false);
//Debug.Log("GC: " + (gcMemory / 1024) + " " + (gcMemory - lastGcMemory));
if ((gcMemory - lastGcMemory) < 0)
{
Debug.LogWarning("COLLECTION!!! " + (lastGcMemory - gcMemory));
}
lastGcMemory = gcMemory;*/
#endif
}
//long lastGcMemory = 0;
void UpdateLoading()
{
// Finished loading?
if (IsAllVideosLoaded())
{
// Assign the master and slaves
_masterPlayer.BufferedDisplay.SetBufferedDisplayMode(BufferedFrameSelectionMode.ElapsedTimeVsynced);
IBufferedDisplay[] slaves = new IBufferedDisplay[_slavePlayers.Length];
for (int i = 0; i < _slavePlayers.Length; i++)
{
slaves[i] = _slavePlayers[i].BufferedDisplay;
}
_masterPlayer.BufferedDisplay.SetSlaves(slaves);
//System.Threading.Thread.Sleep(1250);
// Begin preroll
PlayAll();
_state = State.Prerolling;
}
}
void UpdatePrerolling()
{
if (IsAllVideosPaused())
{
//System.Threading.Thread.Sleep(250);
if (_waitAfterPreroll)
{
_state = State.Prerolled;
}
else
{
PlayAll();
_state = State.Playing;
}
}
}
void UpdatePlaying()
{
if (_masterPlayer.Control.IsPlaying())
{
if (_logSyncErrors)
{
CheckSync();
CheckSmoothness();
}
BufferedFramesState state = _masterPlayer.BufferedDisplay.GetBufferedFramesState();
if (state.bufferedFrameCount < 3)
{
//Debug.LogWarning("FORCE SLEEP");
System.Threading.Thread.Sleep(16);
}
}
else
{
// Pause slaves
for (int i = 0; i < _slavePlayers.Length; i++)
{
MediaPlayer slave = _slavePlayers[i];
slave.Pause();
}
}
// Finished?
if (IsPlaybackFinished(_masterPlayer))
{
_state = State.Finished;
}
}
private long _lastTimeStamp;
private int _sameFrameCount;
void CheckSmoothness()
{
long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
//int frameCount = _masterPlayer.TextureProducer.GetTextureFrameCount();
long frameDuration = (long)(10000000f / _masterPlayer.Info.GetVideoFrameRate());
long vsyncDuration = (long)((QualitySettings.vSyncCount * 10000000f) / (float)Screen.currentResolution.refreshRate);
float vsyncFrames = (float)vsyncDuration / frameDuration;
float fractionalFrames = vsyncFrames - Mathf.FloorToInt(vsyncFrames);
if (fractionalFrames == 0f)
{
if (QualitySettings.vSyncCount != 0)
{
if (!Mathf.Approximately(_sameFrameCount, vsyncFrames))
{
Debug.LogWarning("Frame " + timeStamp + " was shown for " + _sameFrameCount + " frames instead of expected " + vsyncFrames);
}
}
}
long d = (timeStamp - _lastTimeStamp);
if (d != 0)
{
long threshold = 10000;
if (d > frameDuration + threshold ||
d < frameDuration - threshold)
{
Debug.LogWarning("Possible frame skip, " + timeStamp + " " + d);
}
_sameFrameCount = 1;
}
else
{
_sameFrameCount++;
}
_lastTimeStamp = timeStamp;
//Debug.Log(frameDuration);
}
void CheckSync()
{
long timeStamp = _masterPlayer.TextureProducer.GetTextureTimeStamp();
bool inSync = true;
foreach (MediaPlayer slavePlayer in _slavePlayers)
{
if (slavePlayer.TextureProducer.GetTextureTimeStamp() != timeStamp)
{
inSync = false;
break;
}
}
if (!inSync)
{
LogSyncState();
Debug.LogWarning("OUT OF SYNC!!!!!!!");
//Debug.Break();
}
else
{
//LogSyncState();
}
}
void LogSyncState()
{
string text = "Time - Full,Free\t\tRange\n";
text += LogSyncState(_masterPlayer) + "\n";
foreach (MediaPlayer slavePlayer in _slavePlayers)
{
text += LogSyncState(slavePlayer) + "\n";
}
Debug.Log(text);
}
string LogSyncState(MediaPlayer player)
{
BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
long timeStamp = player.TextureProducer.GetTextureTimeStamp();
string result = string.Format("{4} - {2},{3}\t\t{0}-{1} ({5})", state.minTimeStamp, state.maxTimeStamp, state.bufferedFrameCount, state.freeFrameCount, timeStamp, Time.deltaTime);
return result;
}
void OpenMediaAll()
{
_masterPlayer.OpenMedia(autoPlay:false);
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].OpenMedia(autoPlay:false);
}
}
void PauseAll()
{
_masterPlayer.Pause();
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].Pause();
}
}
void PlayAll()
{
_masterPlayer.Play();
for (int i = 0; i < _slavePlayers.Length; i++)
{
_slavePlayers[i].Play();
}
}
void SeekAll(double time)
{
_masterPlayer.Control.Seek(time);
foreach (MediaPlayer player in _slavePlayers)
{
player.Control.Seek(time);
}
}
void SeekFastAll(double time)
{
_masterPlayer.Control.SeekFast(time);
foreach (MediaPlayer player in _slavePlayers)
{
player.Control.SeekFast(time);
}
}
bool IsAllVideosLoaded()
{
bool result = false;
if (IsVideoLoaded(_masterPlayer))
{
result = true;
for (int i = 0; i < _slavePlayers.Length; i++)
{
if (!IsVideoLoaded(_slavePlayers[i]))
{
result = false;
break;
}
}
}
return result;
}
bool IsAllVideosPaused()
{
bool result = false;
if (IsVideoPaused(_masterPlayer))
{
result = true;
for (int i = 0; i < _slavePlayers.Length; i++)
{
if (!IsVideoPaused(_slavePlayers[i]))
{
result = false;
break;
}
}
}
return result;
}
static bool IsPlaybackFinished(MediaPlayer player)
{
bool result = false;
if (player != null && player.Control != null)
{
if (player.Control.IsFinished())
{
BufferedFramesState state = player.BufferedDisplay.GetBufferedFramesState();
if (state.bufferedFrameCount == 0)
{
result = true;
}
}
}
return result;
}
static bool IsVideoLoaded(MediaPlayer player)
{
return (player != null && player.Control != null && player.Control.HasMetaData() && player.Control.CanPlay());
}
static bool IsVideoPaused(MediaPlayer player)
{
return (player != null && player.Control != null && player.Control.IsPaused());
}
}
}
#endif