You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1118 lines
28 KiB
1118 lines
28 KiB
//#define AVPROVIDEO_BETA_SUPPORT_TIMESCALE // BETA FEATURE: comment this in if you want to support frame stepping based on changes in Time.timeScale or Time.captureFramerate |
|
#if UNITY_ANDROID && !UNITY_EDITOR |
|
#define REAL_ANDROID |
|
#endif |
|
|
|
using UnityEngine; |
|
using System.Collections; |
|
using System.IO; |
|
using Video; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Copyright 2015-2016 RenderHeads Ltd. All rights reserverd. |
|
//----------------------------------------------------------------------------- |
|
|
|
namespace RenderHeads.Media.AVProVideo |
|
{ |
|
[AddComponentMenu("AVPro Video/Media Player")] |
|
public class MediaPlayer : MonoBehaviour |
|
{ |
|
// These fields are just used to setup the default properties for a new video that is about to be loaded |
|
// Once a video has been loaded you should use the interfaces exposed in the properties to |
|
// change playback properties (eg volume, looping, mute) |
|
public FileLocation m_VideoLocation = FileLocation.RelativeToStreamingAssetsFolder; |
|
public string m_VideoPath; |
|
|
|
public bool m_AutoOpen = true; |
|
public bool m_AutoStart = false; |
|
public bool m_Loop = false; |
|
[Range(0.0f, 1.0f)] |
|
public float m_Volume = 1.0f; |
|
public bool m_Muted = false; |
|
[Range(-4.0f, 8.0f)]//-4,4 |
|
public float m_PlaybackRate = 1.0f;//1.0 |
|
|
|
// Component Properties |
|
public bool m_DebugGui = false; |
|
public bool m_Persistent = false; |
|
|
|
public StereoPacking m_StereoPacking = StereoPacking.None; |
|
public bool m_DisplayDebugStereoColorTint = false; |
|
public FilterMode m_FilterMode = FilterMode.Bilinear; |
|
public TextureWrapMode m_WrapMode = TextureWrapMode.Clamp; |
|
[Range(0, 16)] |
|
public int m_AnisoLevel = 0; |
|
|
|
[SerializeField] |
|
private MediaPlayerEvent m_events; |
|
|
|
private IMediaControl m_Control; |
|
private IMediaProducer m_Texture; |
|
private IMediaInfo m_Info; |
|
private IMediaPlayer m_Player; |
|
private System.IDisposable m_Dispose; |
|
|
|
private bool m_VideoOpened = false; |
|
private bool m_AutoStartTriggered = false; |
|
private bool m_WasPlayingOnPause = false; |
|
private bool m_isRenderCoroutineRunning = false; |
|
|
|
// Debug GUI |
|
private const float s_GuiScale = 1.5f; |
|
private const int s_GuiStartWidth = 10; |
|
private const int s_GuiWidth = 180; |
|
private int m_GuiPositionX = s_GuiStartWidth; |
|
|
|
// Global init |
|
private static bool s_GlobalStartup = false; |
|
|
|
// Events |
|
private bool m_EventFired_ReadyToPlay = false; |
|
private bool m_EventFired_Started = false; |
|
private bool m_EventFired_FirstFrameReady = false; |
|
private bool m_EventFired_FinishedPlaying = false; |
|
private bool m_EventFired_MetaDataReady = false; |
|
|
|
public enum FileLocation |
|
{ |
|
AbsolutePathOrURL, |
|
RelativeToProjectFolder, |
|
RelativeToStreamingAssetsFolder, |
|
RelativeToDataFolder, |
|
RelativeToPeristentDataFolder, |
|
// TODO: Resource? |
|
} |
|
|
|
[System.Serializable] |
|
public class PlatformOptions |
|
{ |
|
public bool overridePath = false; |
|
public FileLocation pathLocation = FileLocation.RelativeToStreamingAssetsFolder; |
|
public string path; |
|
} |
|
|
|
[System.Serializable] |
|
public class OptionsWindows : PlatformOptions |
|
{ |
|
public bool forceDirectShowApi = false; |
|
public string forceAudioOutputDeviceName = string.Empty; |
|
} |
|
|
|
[System.Serializable] |
|
public class OptionsMacOSX : PlatformOptions |
|
{ |
|
} |
|
[System.Serializable] |
|
public class OptionsIOS : PlatformOptions |
|
{ |
|
} |
|
[System.Serializable] |
|
public class OptionsTVOS : PlatformOptions |
|
{ |
|
} |
|
[System.Serializable] |
|
public class OptionsAndroid : PlatformOptions |
|
{ |
|
} |
|
[System.Serializable] |
|
public class OptionsWindowsPhone : PlatformOptions |
|
{ |
|
} |
|
[System.Serializable] |
|
public class OptionsWindowsUWP : PlatformOptions |
|
{ |
|
} |
|
|
|
public OptionsWindows _optionsWindows = new OptionsWindows(); |
|
public OptionsMacOSX _optionsMacOSX = new OptionsMacOSX(); |
|
public OptionsIOS _optionsIOS = new OptionsIOS(); |
|
public OptionsTVOS _optionsTVOS = new OptionsTVOS(); |
|
public OptionsAndroid _optionsAndroid = new OptionsAndroid(); |
|
public OptionsWindowsPhone _optionsWindowsPhone = new OptionsWindowsPhone(); |
|
public OptionsWindowsUWP _optionsWindowsUWP = new OptionsWindowsUWP(); |
|
|
|
/// <summary> |
|
/// Properties |
|
/// </summary> |
|
|
|
public IMediaInfo Info |
|
{ |
|
get { return m_Info; } |
|
} |
|
public IMediaControl Control |
|
{ |
|
get { return m_Control; } |
|
} |
|
|
|
public IMediaPlayer Player |
|
{ |
|
get { return m_Player; } |
|
} |
|
|
|
public virtual IMediaProducer TextureProducer |
|
{ |
|
get { return m_Texture; } |
|
} |
|
|
|
public MediaPlayerEvent Events |
|
{ |
|
get |
|
{ |
|
if (m_events == null) |
|
{ |
|
m_events = new MediaPlayerEvent(); |
|
} |
|
return m_events; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Methods |
|
/// </summary> |
|
|
|
void Awake() |
|
{ |
|
if (m_Persistent) |
|
{ |
|
// TODO: set "this.transform.root.gameObject" to also DontDestroyOnLoad? |
|
DontDestroyOnLoad(this.gameObject); |
|
} |
|
} |
|
|
|
private void Initialise() |
|
{ |
|
BaseMediaPlayer mediaPlayer = CreatePlatformMediaPlayer(); |
|
if (mediaPlayer != null) |
|
{ |
|
// Set-up interface |
|
m_Control = mediaPlayer; |
|
m_Texture = mediaPlayer; |
|
m_Info = mediaPlayer; |
|
m_Player = mediaPlayer; |
|
m_Dispose = mediaPlayer; |
|
|
|
if (!s_GlobalStartup) |
|
{ |
|
#if UNITY_5 |
|
// Debug.Log(string.Format("[AVProVideo] Initialising AVPro Video (script v{0} plugin v{1}) on {2}/{3} (MT {4})", Helper.ScriptVersion, mediaPlayer.GetVersion(), SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceVersion, SystemInfo.graphicsMultiThreaded)); |
|
#else |
|
Debug.Log(string.Format("[AVProVideo] Initialising AVPro Video (script v{0} plugin v{1}) on {2}/{3}", Helper.ScriptVersion, mediaPlayer.GetVersion(), SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceVersion)); |
|
#endif |
|
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE |
|
Debug.LogWarning("[AVProVideo] TimeScale support used. This could affect performance when changing Time.timeScale or Time.captureFramerate. This feature is useful for supporting video capture system that adjust time scale during capturing."); |
|
#endif |
|
s_GlobalStartup = true; |
|
} |
|
} |
|
} |
|
|
|
void Start() |
|
{ |
|
if (m_Control == null) |
|
{ |
|
Initialise(); |
|
} |
|
|
|
if (m_Control != null) |
|
{ |
|
if (m_AutoOpen) |
|
{ |
|
OpenVideoFromFile(); |
|
} |
|
|
|
StartRenderCoroutine(); |
|
} |
|
} |
|
|
|
public bool OpenVideoFromFile(FileLocation location, string path, bool autoPlay = true) |
|
{ |
|
m_VideoLocation = location; |
|
m_VideoPath = path; |
|
m_AutoStart = autoPlay; |
|
|
|
if (m_Control == null) |
|
{ |
|
Initialise(); |
|
} |
|
|
|
return OpenVideoFromFile(); |
|
} |
|
|
|
private bool OpenVideoFromFile() |
|
{ |
|
bool result = false; |
|
// Open the video file |
|
if ( m_Control != null ) |
|
{ |
|
CloseVideo(); |
|
|
|
m_VideoOpened = true; |
|
m_AutoStartTriggered = !m_AutoStart; |
|
m_EventFired_MetaDataReady = false; |
|
m_EventFired_ReadyToPlay = false; |
|
m_EventFired_Started = false; |
|
m_EventFired_FirstFrameReady = false; |
|
|
|
// Potentially override the file location |
|
string fullPath = GetPlatformFilePath(GetPlatform(), ref m_VideoPath, ref m_VideoLocation); |
|
|
|
if (!string.IsNullOrEmpty(m_VideoPath)) |
|
{ |
|
bool checkForFileExist = true; |
|
if (fullPath.Contains("://")) |
|
{ |
|
checkForFileExist = false; |
|
} |
|
#if (UNITY_ANDROID) |
|
checkForFileExist = false; |
|
#endif |
|
|
|
if (checkForFileExist && !System.IO.File.Exists(fullPath)) |
|
{ |
|
Debug.LogError("[AVProVideo] File not found: " + fullPath, this); |
|
|
|
VCR.Instance().CloseMediaPlayer();//关闭播放器 |
|
} |
|
else |
|
{ |
|
Debug.Log("[AVProVideo] Opening " + fullPath); |
|
|
|
if (!m_Control.OpenVideoFromFile(fullPath)) |
|
{ |
|
Debug.LogError("[AVProVideo] Failed to open " + fullPath, this); |
|
VCR.Instance().CloseMediaPlayer();//关闭播放器 |
|
} |
|
else |
|
{ |
|
SetPlaybackOptions(); |
|
result = true; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
Debug.LogError("[AVProVideo] No file path specified", this); |
|
VCR.Instance().CloseMediaPlayer();//关闭播放器 |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
private void SetPlaybackOptions() |
|
{ |
|
// Set playback options |
|
if (m_Control != null) |
|
{ |
|
m_Control.SetLooping(m_Loop); |
|
m_Control.SetVolume(m_Volume); |
|
m_Control.SetPlaybackRate(m_PlaybackRate); |
|
m_Control.MuteAudio(m_Muted); |
|
m_Control.SetTextureProperties(m_FilterMode, m_WrapMode, m_AnisoLevel); |
|
} |
|
} |
|
|
|
public void CloseVideo() |
|
{ |
|
// Open the video file |
|
if( m_Control != null ) |
|
{ |
|
if (m_events != null) |
|
{ |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.Closing, ErrorCode.None); |
|
} |
|
|
|
m_AutoStartTriggered = false; |
|
m_VideoOpened = false; |
|
m_EventFired_ReadyToPlay = false; |
|
m_EventFired_Started = false; |
|
m_EventFired_MetaDataReady = false; |
|
m_EventFired_FirstFrameReady = false; |
|
m_Control.CloseVideo(); |
|
} |
|
} |
|
|
|
public void Play() |
|
{ |
|
if (m_Control != null && m_Control.CanPlay()) |
|
{ |
|
m_Control.Play(); |
|
} |
|
else |
|
{ |
|
// Can't play, perhaps it's still loading? Queuing play using m_AutoStart to play after loading |
|
m_AutoStart = true; |
|
} |
|
} |
|
|
|
public void Pause() |
|
{ |
|
if (m_Control != null && m_Control.IsPlaying()) |
|
{ |
|
m_Control.Pause(); |
|
} |
|
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE |
|
_timeScaleIsControlling = false; |
|
#endif |
|
} |
|
|
|
public void Stop() |
|
{ |
|
if (m_Control != null) |
|
{ |
|
m_Control.Stop(); |
|
} |
|
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE |
|
_timeScaleIsControlling = false; |
|
#endif |
|
} |
|
|
|
public void Rewind(bool pause) |
|
{ |
|
if (m_Control != null) |
|
{ |
|
if (pause) |
|
{ |
|
Pause(); |
|
} |
|
m_Control.Rewind(); |
|
} |
|
} |
|
|
|
void Update() |
|
{ |
|
// Auto start the playback |
|
if (m_Control != null) |
|
{ |
|
if (m_VideoOpened && m_AutoStart && !m_AutoStartTriggered && m_Control.CanPlay()) |
|
{ |
|
m_AutoStartTriggered = true; |
|
m_Control.Play(); |
|
} |
|
|
|
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE |
|
UpdateTimeScale(); |
|
#endif |
|
// Update |
|
m_Player.Update(); |
|
|
|
// Render (done in co-routine) |
|
//m_Player.Render(); |
|
|
|
UpdateErrors(); |
|
UpdateEvents(); |
|
} |
|
} |
|
|
|
void OnEnable() |
|
{ |
|
if (m_Control != null && m_WasPlayingOnPause) |
|
{ |
|
m_AutoStart = true; |
|
m_AutoStartTriggered = false; |
|
m_WasPlayingOnPause = false; |
|
} |
|
|
|
StartRenderCoroutine(); |
|
} |
|
|
|
void OnDisable() |
|
{ |
|
if (m_Control != null) |
|
{ |
|
if (m_Control.IsPlaying()) |
|
{ |
|
m_WasPlayingOnPause = true; |
|
Pause(); |
|
} |
|
} |
|
|
|
StopRenderCoroutine(); |
|
} |
|
|
|
void OnDestroy() |
|
{ |
|
StopRenderCoroutine(); |
|
|
|
m_VideoOpened = false; |
|
|
|
if (m_Dispose != null) |
|
{ |
|
m_Dispose.Dispose(); |
|
m_Dispose = null; |
|
} |
|
m_Control = null; |
|
m_Texture = null; |
|
m_Info = null; |
|
m_Player = null; |
|
|
|
// TODO: possible bug if MediaPlayers are created and destroyed manually (instantiated), OnApplicationQuit won't be called! |
|
} |
|
|
|
//public void OnApplicationQuit() |
|
public static void OnQuiteEvent() |
|
{ |
|
if (s_GlobalStartup) |
|
{ |
|
//Debug.Log("[AVProVideo] Shutdown"); |
|
|
|
// Clean up any open media players |
|
MediaPlayer[] players = Resources.FindObjectsOfTypeAll<MediaPlayer>(); |
|
if (players != null && players.Length > 0) |
|
{ |
|
for (int i = 0; i < players.Length; i++) |
|
{ |
|
players[i].CloseVideo(); |
|
} |
|
} |
|
|
|
#if UNITY_EDITOR |
|
#if UNITY_EDITOR_WIN |
|
WindowsMediaPlayer.DeinitPlatform(); |
|
#endif |
|
#else |
|
#if (UNITY_STANDALONE_WIN) |
|
WindowsMediaPlayer.DeinitPlatform(); |
|
#endif |
|
#endif |
|
s_GlobalStartup = false; |
|
} |
|
} |
|
|
|
#region Rendering Coroutine |
|
private void StartRenderCoroutine() |
|
{ |
|
if (!m_isRenderCoroutineRunning) |
|
{ |
|
m_isRenderCoroutineRunning = true; |
|
StartCoroutine("FinalRenderCapture"); |
|
} |
|
} |
|
|
|
private void StopRenderCoroutine() |
|
{ |
|
if (m_isRenderCoroutineRunning) |
|
{ |
|
StopCoroutine("FinalRenderCapture"); |
|
m_isRenderCoroutineRunning = false; |
|
} |
|
} |
|
|
|
private IEnumerator FinalRenderCapture() |
|
{ |
|
while (Application.isPlaying) |
|
{ |
|
yield return new WaitForEndOfFrame(); |
|
|
|
if (this.enabled) |
|
{ |
|
if (m_Player != null) |
|
{ |
|
m_Player.Render(); |
|
} |
|
} |
|
} |
|
} |
|
#endregion |
|
|
|
#region Platform and Path |
|
public static Platform GetPlatform() |
|
{ |
|
Platform result = Platform.Unknown; |
|
|
|
// Setup for running in the editor (Either OSX, Windows or Linux) |
|
#if UNITY_EDITOR |
|
#if (UNITY_EDITOR_OSX && UNITY_EDITOR_64) |
|
result = Platform.MacOSX; |
|
#elif UNITY_EDITOR_WIN |
|
result = Platform.Windows; |
|
#endif |
|
#else |
|
// Setup for running builds |
|
#if (UNITY_STANDALONE_WIN) |
|
result = Platform.Windows; |
|
#elif (UNITY_STANDALONE_OSX) |
|
result = Platform.MacOSX; |
|
#elif (UNITY_IPHONE || UNITY_IOS) |
|
result = Platform.iOS; |
|
#elif (UNITY_TVOS) |
|
result = Platform.tvOS; |
|
#elif (UNITY_ANDROID) |
|
result = Platform.Android; |
|
#elif (UNITY_WP8) |
|
// TODO: add Windows Phone 8 suppport |
|
#elif (UNITY_WP81) |
|
// TODO: add Windows Phone 8.1 suppport |
|
#endif |
|
|
|
#endif |
|
|
|
return result; |
|
} |
|
|
|
public PlatformOptions GetPlatformOptions(Platform platform) |
|
{ |
|
PlatformOptions result = null; |
|
|
|
#if UNITY_EDITOR |
|
#if (UNITY_EDITOR_OSX && UNITY_EDITOR_64) |
|
result = _optionsMacOSX; |
|
#elif UNITY_EDITOR_WIN |
|
result = _optionsWindows; |
|
#endif |
|
#else |
|
// Setup for running builds |
|
|
|
#if (UNITY_STANDALONE_WIN) |
|
result = _optionsWindows; |
|
#elif (UNITY_STANDALONE_OSX) |
|
result = _optionsMacOSX; |
|
#elif (UNITY_IPHONE || UNITY_IOS) |
|
result = _optionsIOS; |
|
#elif (UNITY_TVOS) |
|
result = _optionsTVOS; |
|
#elif (UNITY_ANDROID) |
|
result = _optionsAndroid; |
|
#elif (UNITY_WP8 || UNITY_WP81 || UNITY_WINRT_8_1) |
|
result = _optionsWindowsPhone; |
|
#elif (UNITY_WSA_10_0) |
|
result = _optionsWindowsUWP; |
|
#endif |
|
|
|
#endif |
|
return result; |
|
} |
|
|
|
#if UNITY_EDITOR |
|
public static string GetPlatformOptionsVariable(Platform platform) |
|
{ |
|
string result = string.Empty; |
|
|
|
switch (platform) |
|
{ |
|
case Platform.Windows: |
|
result = "_optionsWindows"; |
|
break; |
|
case Platform.MacOSX: |
|
result = "_optionsMacOSX"; |
|
break; |
|
case Platform.iOS: |
|
result = "_optionsIOS"; |
|
break; |
|
case Platform.tvOS: |
|
result = "_optionsTVOS"; |
|
break; |
|
case Platform.Android: |
|
result = "_optionsAndroid"; |
|
break; |
|
case Platform.WindowsPhone: |
|
result = "_optionsWindowsPhone"; |
|
break; |
|
case Platform.WindowsUWP: |
|
result = "_optionsWindowsUWP"; |
|
break; |
|
} |
|
|
|
return result; |
|
} |
|
#endif |
|
|
|
public static string GetPath(FileLocation location) |
|
{ |
|
string result = string.Empty; |
|
switch (location) |
|
{ |
|
case FileLocation.AbsolutePathOrURL: |
|
break; |
|
case FileLocation.RelativeToDataFolder: |
|
result = Application.dataPath; |
|
break; |
|
case FileLocation.RelativeToPeristentDataFolder: |
|
result = Application.persistentDataPath; |
|
break; |
|
case FileLocation.RelativeToProjectFolder: |
|
#if !UNITY_WINRT_8_1 |
|
result = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, "..")); |
|
result = result.Replace('\\', '/'); |
|
#endif |
|
break; |
|
case FileLocation.RelativeToStreamingAssetsFolder: |
|
result = Application.streamingAssetsPath; |
|
break; |
|
} |
|
return result; |
|
} |
|
|
|
public static string GetFilePath(string path, FileLocation location) |
|
{ |
|
string result = string.Empty; |
|
if (!string.IsNullOrEmpty(path)) |
|
{ |
|
switch (location) |
|
{ |
|
case FileLocation.AbsolutePathOrURL: |
|
result = path; |
|
break; |
|
case FileLocation.RelativeToDataFolder: |
|
case FileLocation.RelativeToPeristentDataFolder: |
|
case FileLocation.RelativeToProjectFolder: |
|
case FileLocation.RelativeToStreamingAssetsFolder: |
|
result = System.IO.Path.Combine(GetPath(location), path); |
|
break; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
private string GetPlatformFilePath(Platform platform, ref string filePath, ref FileLocation fileLocation) |
|
{ |
|
string result = string.Empty; |
|
|
|
// Replace file path and location if overriden by platform options |
|
if (platform != Platform.Unknown) |
|
{ |
|
PlatformOptions options = GetPlatformOptions(platform); |
|
if (options != null) |
|
{ |
|
if (options.overridePath) |
|
{ |
|
filePath = options.path; |
|
fileLocation = options.pathLocation; |
|
} |
|
} |
|
} |
|
|
|
result = GetFilePath(filePath, fileLocation); |
|
|
|
return result; |
|
} |
|
#endregion |
|
|
|
public virtual BaseMediaPlayer CreatePlatformMediaPlayer() |
|
{ |
|
BaseMediaPlayer mediaPlayer = null; |
|
|
|
// Setup for running in the editor (Either OSX, Windows or Linux) |
|
#if UNITY_EDITOR |
|
#if (UNITY_EDITOR_OSX) |
|
#if UNITY_EDITOR_64 |
|
mediaPlayer = new OSXMediaPlayer(); |
|
#else |
|
Debug.LogError("[AVProVideo] 32-bit OS X Unity editor not supported. 64-bit required."); |
|
#endif |
|
#elif UNITY_EDITOR_WIN |
|
WindowsMediaPlayer.InitialisePlatform(); |
|
mediaPlayer = new WindowsMediaPlayer(_optionsWindows.forceDirectShowApi, _optionsWindows.forceAudioOutputDeviceName); |
|
#endif |
|
#else |
|
// Setup for running builds |
|
#if (UNITY_STANDALONE_WIN || UNITY_WSA_10_0 || UNITY_WINRT_8_1) |
|
WindowsMediaPlayer.InitialisePlatform(); |
|
mediaPlayer = new WindowsMediaPlayer(_optionsWindows.forceDirectShowApi); |
|
#elif (UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS) |
|
mediaPlayer = new OSXMediaPlayer(); |
|
#elif (UNITY_ANDROID) |
|
// Initialise platform (also unpacks videos from StreamingAsset folder (inside a jar), to the persistent data path) |
|
AndroidMediaPlayer.InitialisePlatform(); |
|
mediaPlayer = new AndroidMediaPlayer(); |
|
#endif |
|
#endif |
|
|
|
// Fallback |
|
if (mediaPlayer == null) |
|
{ |
|
Debug.LogWarning("[AVProVideo] Not supported on this platform. Using placeholder player!"); |
|
mediaPlayer = new NullMediaPlayer(); |
|
} |
|
|
|
return mediaPlayer; |
|
} |
|
|
|
#region Support for Time Scale |
|
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE |
|
// Adjust this value to get faster performance but may drop frames. |
|
// Wait longer to ensure there is enough time for frames to process |
|
private const float TimeScaleTimeoutMs = 20f; |
|
private bool _timeScaleIsControlling; |
|
private float _timeScaleVideoTime; |
|
|
|
private void UpdateTimeScale() |
|
{ |
|
if (Time.timeScale != 1f || Time.captureFramerate != 0) |
|
{ |
|
if (Control.IsPlaying()) |
|
{ |
|
Control.Pause(); |
|
_timeScaleIsControlling = true; |
|
_timeScaleVideoTime = Control.GetCurrentTimeMs(); |
|
} |
|
|
|
if (_timeScaleIsControlling) |
|
{ |
|
int frameCount = TextureProducer.GetTextureFrameCount(); |
|
|
|
// Progress time |
|
_timeScaleVideoTime += (Time.deltaTime * 1000f); |
|
|
|
// Handle looping |
|
if (m_Loop && _timeScaleVideoTime >= Info.GetDurationMs()) |
|
{ |
|
_timeScaleVideoTime = 0f; |
|
} |
|
|
|
// Seek |
|
Control.Seek(_timeScaleVideoTime); |
|
|
|
// Wait for frame to change |
|
ForceWaitForNewFrame(frameCount, TimeScaleTimeoutMs); |
|
} |
|
} |
|
else |
|
{ |
|
// Restore playback when timeScale becomes 1 |
|
if (_timeScaleIsControlling) |
|
{ |
|
Control.Play(); |
|
_timeScaleIsControlling = false; |
|
} |
|
} |
|
} |
|
#endif |
|
#endregion |
|
|
|
private bool ForceWaitForNewFrame(int lastFrameCount, float timeoutMs) |
|
{ |
|
bool result = false; |
|
// Wait for the frame to change, or timeout to happen (for the case that there is no new frame for this time) |
|
System.DateTime startTime = System.DateTime.Now; |
|
int iterationCount = 0; |
|
while (Control != null && (System.DateTime.Now - startTime).TotalMilliseconds < (double)timeoutMs) |
|
{ |
|
m_Player.Update(); |
|
|
|
// TODO: check if Seeking has completed! Then we don't have to wait |
|
|
|
// If frame has changed we can continue |
|
// NOTE: this will never happen because GL.IssuePlugin.Event is never called in this loop |
|
if (lastFrameCount != TextureProducer.GetTextureFrameCount()) |
|
{ |
|
result = true; |
|
break; |
|
} |
|
|
|
iterationCount++; |
|
|
|
// NOTE: we tried to add Sleep for 1ms but it was very slow, so switched to this time based method which burns more CPU but about double the speed |
|
// NOTE: had to add the Sleep back in as after too many iterations (over 1000000) of GL.IssuePluginEvent Unity seems to lock up |
|
// NOTE: seems that GL.IssuePluginEvent can't be called if we're stuck in a while loop and they just stack up |
|
//System.Threading.Thread.Sleep(0); |
|
} |
|
|
|
m_Player.Render(); |
|
|
|
return result; |
|
} |
|
|
|
private void UpdateErrors() |
|
{ |
|
ErrorCode errorCode = m_Control.GetLastError(); |
|
if (ErrorCode.None != errorCode) |
|
{ |
|
Debug.LogError("[AVProVideo] Error: " + errorCode.ToString()); |
|
VCR.Instance().CloseMediaPlayer();//关闭播放器 |
|
|
|
if (m_events != null) |
|
{ |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.Error, errorCode); |
|
} |
|
} |
|
} |
|
|
|
private void UpdateEvents() |
|
{ |
|
if (m_events != null && m_Control != null) |
|
{ |
|
// Finished event |
|
if (!m_EventFired_FinishedPlaying && !m_Control.IsLooping() && m_Control.CanPlay() && m_Control.IsFinished()) |
|
{ |
|
m_EventFired_FinishedPlaying = true; |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.FinishedPlaying, ErrorCode.None); |
|
} |
|
|
|
// Keep track of whether the Playing state has changed |
|
if (m_EventFired_Started && !m_Control.IsPlaying()) |
|
{ |
|
// Playing has stopped |
|
m_EventFired_Started = false; |
|
} |
|
|
|
if (m_EventFired_FinishedPlaying && m_Control.IsPlaying() && !m_Control.IsFinished()) |
|
{ |
|
m_EventFired_FinishedPlaying = false; |
|
} |
|
|
|
// Fire events |
|
if (!m_EventFired_MetaDataReady && m_Control.HasMetaData()) |
|
{ |
|
m_EventFired_MetaDataReady = true; |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.MetaDataReady, ErrorCode.None); |
|
} |
|
if (!m_EventFired_ReadyToPlay && !m_Control.IsPlaying() && m_Control.CanPlay()) |
|
{ |
|
m_EventFired_ReadyToPlay = true; |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.ReadyToPlay, ErrorCode.None); |
|
} |
|
if (!m_EventFired_Started && m_Control.IsPlaying()) |
|
{ |
|
m_EventFired_Started = true; |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.Started, ErrorCode.None); |
|
} |
|
if (m_Texture != null) |
|
{ |
|
if (!m_EventFired_FirstFrameReady && m_Control.CanPlay() && m_Texture.GetTextureFrameCount() > 0) |
|
{ |
|
m_EventFired_FirstFrameReady = true; |
|
m_events.Invoke(this, MediaPlayerEvent.EventType.FirstFrameReady, ErrorCode.None); |
|
} |
|
} |
|
} |
|
} |
|
|
|
#region Application Focus and Pausing |
|
#if !UNITY_EDITOR |
|
void OnApplicationFocus( bool focusStatus ) |
|
{ |
|
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN) |
|
// Debug.Log("OnApplicationFocus: focusStatus: " + focusStatus); |
|
|
|
if( focusStatus ) |
|
{ |
|
if( m_Control != null && m_WasPlayingOnPause ) |
|
{ |
|
m_WasPlayingOnPause = false; |
|
m_Control.Play(); |
|
|
|
Debug.Log("OnApplicationFocus: playing video again"); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
void OnApplicationPause( bool pauseStatus ) |
|
{ |
|
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN) |
|
// Debug.Log("OnApplicationPause: pauseStatus: " + pauseStatus); |
|
|
|
if( pauseStatus ) |
|
{ |
|
if( m_Control!= null && m_Control.IsPlaying() ) |
|
{ |
|
m_WasPlayingOnPause = true; |
|
m_Control.Pause(); |
|
|
|
Debug.Log("OnApplicationPause: pausing video"); |
|
} |
|
} |
|
else |
|
{ |
|
// Catch coming back from power off state when no lock screen |
|
OnApplicationFocus( true ); |
|
} |
|
#endif |
|
} |
|
#endif |
|
#endregion |
|
|
|
#region Save Frame To PNG |
|
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR |
|
[ContextMenu("Save Frame To PNG")] |
|
public void SaveFrameToPng() |
|
{ |
|
Texture2D frame = ExtractFrame(null); |
|
if (frame != null) |
|
{ |
|
byte[] imageBytes = frame.EncodeToPNG(); |
|
if (imageBytes != null) |
|
{ |
|
#if !UNITY_WEBPLAYER |
|
string timecode = Mathf.FloorToInt(Control.GetCurrentTimeMs()).ToString("D8"); |
|
System.IO.File.WriteAllBytes("frame-" + timecode + ".png", imageBytes); |
|
#else |
|
Debug.LogError("Web Player platform doesn't support file writing. Change platform in Build Settings."); |
|
#endif |
|
} |
|
|
|
Destroy(frame); |
|
} |
|
} |
|
#endif |
|
#endregion |
|
|
|
#region Extract Frame |
|
// "target" can be null or you can pass in an existing texture. |
|
public Texture2D ExtractFrame(Texture2D target, float timeSeconds = -1f, bool accurateSeek = true, int timeoutMs = 1000) |
|
{ |
|
Texture2D result = target; |
|
|
|
// Extract frames returns the interal frame of the video player |
|
Texture frame = ExtractFrame(timeSeconds, accurateSeek, timeoutMs); |
|
if (frame != null) |
|
{ |
|
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), target); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private Texture ExtractFrame(float timeSeconds = -1f, bool accurateSeek = true, int timeoutMs = 1000) |
|
{ |
|
Texture result = null; |
|
|
|
if (m_Control != null) |
|
{ |
|
if (timeSeconds >= 0f) |
|
{ |
|
Pause(); |
|
|
|
float seekTimeMs = timeSeconds * 1000f; |
|
|
|
// If the frame is already avaiable just grab it |
|
if (TextureProducer.GetTexture() != null && m_Control.GetCurrentTimeMs() == seekTimeMs) |
|
{ |
|
result = TextureProducer.GetTexture(); |
|
} |
|
else |
|
{ |
|
// Store frame count before seek |
|
int frameCount = TextureProducer.GetTextureFrameCount(); |
|
|
|
// Seek to the frame |
|
if (accurateSeek) |
|
{ |
|
m_Control.Seek(seekTimeMs); |
|
} |
|
else |
|
{ |
|
m_Control.SeekFast(seekTimeMs); |
|
} |
|
|
|
// Wait for frame to change |
|
ForceWaitForNewFrame(frameCount, timeoutMs); |
|
result = TextureProducer.GetTexture(); |
|
} |
|
} |
|
else |
|
{ |
|
result = TextureProducer.GetTexture(); |
|
} |
|
} |
|
return result; |
|
} |
|
#endregion |
|
|
|
#region Play/Pause Support for Unity Editor |
|
// This code handles the pause/play buttons in the editor |
|
#if UNITY_EDITOR |
|
static MediaPlayer() |
|
{ |
|
UnityEditor.EditorApplication.playmodeStateChanged -= OnUnityPlayModeChanged; |
|
UnityEditor.EditorApplication.playmodeStateChanged += OnUnityPlayModeChanged; |
|
} |
|
|
|
private static void OnUnityPlayModeChanged() |
|
{ |
|
if (UnityEditor.EditorApplication.isPlaying) |
|
{ |
|
if (UnityEditor.EditorApplication.isPaused) |
|
{ |
|
MediaPlayer[] players = Resources.FindObjectsOfTypeAll<MediaPlayer>(); |
|
foreach (MediaPlayer player in players) |
|
{ |
|
player.EditorPause(); |
|
} |
|
} |
|
else |
|
{ |
|
MediaPlayer[] players = Resources.FindObjectsOfTypeAll<MediaPlayer>(); |
|
foreach (MediaPlayer player in players) |
|
{ |
|
player.EditorUnpause(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void EditorPause() |
|
{ |
|
if (this.isActiveAndEnabled) |
|
{ |
|
if (m_Control != null && m_Control.IsPlaying()) |
|
{ |
|
m_WasPlayingOnPause = true; |
|
Pause(); |
|
} |
|
StopRenderCoroutine(); |
|
} |
|
} |
|
|
|
private void EditorUnpause() |
|
{ |
|
if (this.isActiveAndEnabled) |
|
{ |
|
if (m_Control != null && m_WasPlayingOnPause) |
|
{ |
|
m_AutoStart = true; |
|
m_WasPlayingOnPause = false; |
|
m_AutoStartTriggered = false; |
|
} |
|
StartRenderCoroutine(); |
|
} |
|
} |
|
#endif |
|
#endregion |
|
|
|
#region IMGUI Debug Information Display |
|
public void SetGuiPositionFromVideoIndex(int index) |
|
{ |
|
m_GuiPositionX = Mathf.FloorToInt((s_GuiStartWidth * s_GuiScale) + (s_GuiWidth * index * s_GuiScale)); |
|
} |
|
|
|
public void SetDebugGuiEnabled(bool bEnabled) |
|
{ |
|
m_DebugGui = bEnabled; |
|
} |
|
|
|
void OnGUI() |
|
{ |
|
if (m_Info != null && m_DebugGui) |
|
{ |
|
GUI.depth = -1; |
|
GUI.matrix = Matrix4x4.TRS(new Vector3(m_GuiPositionX, 10f, 0f), Quaternion.identity, new Vector3(s_GuiScale, s_GuiScale, 1.0f)); |
|
|
|
GUILayout.BeginVertical("box", GUILayout.MaxWidth(s_GuiWidth)); |
|
GUILayout.Label(System.IO.Path.GetFileName(m_VideoPath)); |
|
GUILayout.Label("Dimensions: " + m_Info.GetVideoWidth() + " x " + m_Info.GetVideoHeight()); |
|
GUILayout.Label("Time: " + (m_Control.GetCurrentTimeMs() * 0.001f).ToString("F1") + "s / " + (m_Info.GetDurationMs() * 0.001f).ToString("F1") + "s"); |
|
GUILayout.Label("Rate: " + m_Info.GetVideoPlaybackRate().ToString("F2") + "Hz"); |
|
|
|
if (TextureProducer != null && TextureProducer.GetTexture() != null) |
|
{ |
|
// Show texture without and with alpha blending |
|
GUILayout.BeginHorizontal(); |
|
Rect r1 = GUILayoutUtility.GetRect(32f, 32f); |
|
GUILayout.Space(8f); |
|
Rect r2 = GUILayoutUtility.GetRect(32f, 32f); |
|
if (TextureProducer.RequiresVerticalFlip()) |
|
{ |
|
GUIUtility.ScaleAroundPivot(new Vector2(1f, -1f), new Vector2(0, r1.y + (r1.height / 2))); |
|
} |
|
GUI.DrawTexture(r1, TextureProducer.GetTexture(), ScaleMode.ScaleToFit, false); |
|
GUI.DrawTexture(r2, TextureProducer.GetTexture(), ScaleMode.ScaleToFit, true); |
|
GUILayout.FlexibleSpace(); |
|
GUILayout.EndHorizontal(); |
|
} |
|
GUILayout.EndVertical(); |
|
} |
|
} |
|
#endregion |
|
} |
|
} |