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
2 years ago
|
//#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
|
||
|
}
|
||
|
}
|