//#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
//#define AVPROVIDEO_FORCE_NULL_MEDIAPLAYER // DEV FEATURE: comment this out to make all mediaplayers use the null mediaplayer
//#define AVPROVIDEO_DISABLE_LOGGING // DEV FEATURE: disables Debug.Log from AVPro Video
#if UNITY_ANDROID && !UNITY_EDITOR
#define REAL_ANDROID
#endif
#if UNITY_5_4_OR_NEWER || (UNITY_5 && !UNITY_5_0)
#define UNITY_HELPATTRIB
#endif
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if NETFX_CORE
using Windows.Storage.Streams;
#endif
//-----------------------------------------------------------------------------
// Copyright 2015-2020 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProVideo
{
///
/// This is the primary AVPro Video component and handles all media loading,
/// seeking, information retrieving etc. This component does not do any display
/// of the video. Instead this is handled by other components such as
/// ApplyToMesh, ApplyToMaterial, DisplayIMGUI, DisplayUGUI.
///
[AddComponentMenu("AVPro Video/Media Player", -100)]
#if UNITY_HELPATTRIB
[HelpURL("http://renderheads.com/products/avpro-video/")]
#endif
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 = true;
public bool m_Loop = false;
[Range(0.0f, 1.0f)]
public float m_Volume = 1.0f;
[SerializeField]
[Range(-1.0f, 1.0f)]
private float m_Balance = 0.0f;
public bool m_Muted = false;
[SerializeField]
[Range(-4.0f, 4.0f)]
public float m_PlaybackRate = 1.0f;
public bool m_Resample = false;
public Resampler.ResampleMode m_ResampleMode = Resampler.ResampleMode.POINT;
[Range(3, 10)]
public int m_ResampleBufferSize = 5;
private Resampler m_Resampler = null;
public Resampler FrameResampler
{
get { return m_Resampler; }
}
[System.Serializable]
public class Setup
{
public bool persistent;
}
// Component Properties
[SerializeField]
private bool m_Persistent = false;
public bool Persistent
{
get { return m_Persistent; }
set { m_Persistent = value; }
}
[SerializeField]
private VideoMapping m_videoMapping = VideoMapping.Unknown;
public VideoMapping VideoLayoutMapping
{
get { return m_videoMapping; }
set { m_videoMapping = value; }
}
public StereoPacking m_StereoPacking = StereoPacking.None;
public AlphaPacking m_AlphaPacking = AlphaPacking.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 bool m_LoadSubtitles;
[SerializeField]
private FileLocation m_SubtitleLocation = FileLocation.RelativeToStreamingAssetsFolder;
private FileLocation m_queueSubtitleLocation;
[SerializeField]
private string m_SubtitlePath;
private string m_queueSubtitlePath;
private Coroutine m_loadSubtitlesRoutine;
[SerializeField]
private Transform m_AudioHeadTransform;
[SerializeField]
private bool m_AudioFocusEnabled;
[SerializeField]
private Transform m_AudioFocusTransform;
[SerializeField, Range(40, 120)]
private float m_AudioFocusWidthDegrees = 90;
[SerializeField, Range(-24, 0)]
private float m_AudioFocusOffLevelDB = 0;
[SerializeField]
private MediaPlayerEvent m_events = null;
[SerializeField]
private int m_eventMask = -1;
[SerializeField]
private FileFormat m_forceFileFormat = FileFormat.Unknown;
[SerializeField]
private bool _pauseMediaOnAppPause = true;
[SerializeField]
private bool _playMediaOnAppUnpause = true;
private IMediaControl m_Control;
private IMediaProducer m_Texture;
private IMediaInfo m_Info;
private IMediaPlayer m_Player;
private IMediaSubtitles m_Subtitles;
private System.IDisposable m_Dispose;
// State
private bool m_VideoOpened = false;
private bool m_AutoStartTriggered = false;
private bool m_WasPlayingOnPause = false;
private Coroutine _renderingCoroutine = null;
// Global init
private static bool s_GlobalStartup = false;
// Event state
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;
private bool m_EventState_PlaybackStalled = false;
private bool m_EventState_PlaybackBuffering = false;
private bool m_EventState_PlaybackSeeking = false;
private int m_EventState_PreviousWidth = 0;
private int m_EventState_PreviousHeight = 0;
private int m_previousSubtitleIndex = -1;
private static Camera m_DummyCamera = null;
private bool m_FinishedFrameOpenCheck = false;
[SerializeField]
private uint m_sourceSampleRate = 0;
[SerializeField]
private uint m_sourceChannels = 0;
[SerializeField]
private bool m_manuallySetAudioSourceProperties = false;
public enum FileLocation
{
AbsolutePathOrURL,
RelativeToProjectFolder,
RelativeToStreamingAssetsFolder,
RelativeToDataFolder,
RelativeToPersistentDataFolder,
// TODO: Resource, AssetBundle?
}
[System.Serializable]
public class PlatformOptions
{
public bool overridePath = false;
public FileLocation pathLocation = FileLocation.RelativeToStreamingAssetsFolder;
public string path;
public virtual bool IsModified()
{
return overridePath; // The other variables don't matter if overridePath is false
}
// Decryption support
public virtual string GetKeyServerURL() { return null; }
public virtual string GetKeyServerAuthToken() { return null; }
public virtual string GetDecryptionKey() { return null; }
// HTTP header support
[System.Serializable]
public struct HTTPHeader
{
public string header;
public string value;
public HTTPHeader(string header, string value) { this.header = header; this.value = value; }
}
// Make a string json compatible by escaping '\', '"' and control characters.
protected static string StringAsJsonString(string str)
{
System.Text.StringBuilder builder = null;
for (int i = 0; i < str.Length; ++i)
{
switch (str[i])
{
case '"':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\\"");
break;
case '\\':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\\\");
break;
case '/':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\/");
break;
case '\b':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\b");
break;
case '\f':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\f");
break;
case '\n':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\n");
break;
case '\r':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\r");
break;
case '\t':
if (builder == null)
builder = new System.Text.StringBuilder(str.Substring(0, i));
builder.Append("\\t");
break;
default:
if (builder != null)
builder.Append(str[i]);
break;
}
}
if (builder != null)
return builder.ToString();
else
return str;
}
private enum ParseJSONHeadersState
{
Begin,
FindKey,
ReadKey,
FindColon,
FindValue,
ReadValue,
FindComma,
Finished,
Failed,
}
// Convert the old style Json HTTP headers into new list style.
protected static List ParseJsonHTTPHeadersIntoHTTPHeaderList(string httpHeaderJson)
{
// Only support "key" : "value" style construct to keep things simple
ParseJSONHeadersState state = ParseJSONHeadersState.Begin;
int j = 0;
string key = null;
string value = null;
List headers = new List();
System.Char c = '\0';
System.Char pc = c;
for (int i = 0; i < httpHeaderJson.Length; ++i)
{
if (state == ParseJSONHeadersState.Finished || state == ParseJSONHeadersState.Failed)
break;
pc = c;
c = httpHeaderJson[i];
switch (state)
{
case ParseJSONHeadersState.Begin:
// Skip any whitespace
if (System.Char.IsWhiteSpace(c))
continue;
// Looking for opening brace
if (c == '{')
state = ParseJSONHeadersState.FindKey;
else
state = ParseJSONHeadersState.Failed;
break;
case ParseJSONHeadersState.FindKey:
// Skip any whitespace
if (System.Char.IsWhiteSpace(c))
continue;
if (c == '"')
{
state = ParseJSONHeadersState.ReadKey;
j = i + 1;
}
else if (c == '}')
{
state = ParseJSONHeadersState.Finished;
}
else
state = ParseJSONHeadersState.Failed;
break;
case ParseJSONHeadersState.ReadKey:
if (c == '"' && pc != '\\')
{
key = httpHeaderJson.Substring(j, i - j);
state = ParseJSONHeadersState.FindColon;
}
else
continue;
break;
case ParseJSONHeadersState.FindColon:
// Skip any whitespace
if (System.Char.IsWhiteSpace(c))
continue;
if (c == ':')
state = ParseJSONHeadersState.FindValue;
else
state = ParseJSONHeadersState.Failed;
break;
case ParseJSONHeadersState.FindValue:
// Skip any whitespace
if (System.Char.IsWhiteSpace(c))
continue;
if (c == '"')
{
state = ParseJSONHeadersState.ReadValue;
j = i + 1;
}
else
state = ParseJSONHeadersState.Failed;
break;
case ParseJSONHeadersState.ReadValue:
if (c == '"' && pc != '\\')
{
value = httpHeaderJson.Substring(j, i - j);
headers.Add(new HTTPHeader(key, value));
state = ParseJSONHeadersState.FindComma;
}
else
continue;
break;
case ParseJSONHeadersState.FindComma:
// Skip any whitespace
if (System.Char.IsWhiteSpace(c))
continue;
if (c == ',')
state = ParseJSONHeadersState.FindKey;
else if (c == '}')
state = ParseJSONHeadersState.Finished;
break;
case ParseJSONHeadersState.Finished:
break;
case ParseJSONHeadersState.Failed:
break;
}
}
if (state == ParseJSONHeadersState.Finished)
{
return headers;
}
else
{
Debug.LogWarning("Failed to convert HTTP headers from Json, you will need to do this manually.");
return null;
}
}
}
[System.Serializable]
public class OptionsWindows : PlatformOptions
{
public Windows.VideoApi videoApi = Windows.VideoApi.MediaFoundation;
public bool useHardwareDecoding = true;
public bool useUnityAudio = false;
public bool forceAudioResample = true;
public bool useTextureMips = false;
public bool hintAlphaChannel = false;
public bool useLowLatency = false;
public string forceAudioOutputDeviceName = string.Empty;
public List preferredFilters = new List();
public bool enableAudio360 = false;
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
public override bool IsModified()
{
return (base.IsModified() || !useHardwareDecoding || useTextureMips || hintAlphaChannel || useLowLatency || useUnityAudio || videoApi != Windows.VideoApi.MediaFoundation || !forceAudioResample || enableAudio360 || audio360ChannelMode != Audio360ChannelMode.TBE_8_2 || !string.IsNullOrEmpty(forceAudioOutputDeviceName) || preferredFilters.Count != 0);
}
}
[System.Serializable]
public class OptionsApple : PlatformOptions, ISerializationCallbackReceiver
{
public enum AudioMode
{
SystemDirect,
Unity
};
public AudioMode audioMode = AudioMode.SystemDirect;
public List httpHeaders = new List();
[SerializeField, Multiline]
private string httpHeaderJson = null;
public string GetHTTPHeadersAsJSON()
{
if (httpHeaders.Count > 0)
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
int i = 0;
builder.Append("{");
builder.AppendFormat("\"{0}\":\"{1}\"", StringAsJsonString(httpHeaders[i].header), StringAsJsonString(httpHeaders[i].value));
for (i = 1; i < httpHeaders.Count; ++i)
builder.AppendFormat(",\"{0}\":\"{1}\"", StringAsJsonString(httpHeaders[i].header), StringAsJsonString(httpHeaders[i].value));
builder.Append("}");
return builder.ToString();
}
else
return httpHeaderJson;
}
// Support for handling encrypted HLS streams
public string keyServerURLOverride = null;
public string keyServerAuthToken = null;
[Multiline]
public string base64EncodedKeyBlob = null;
public override bool IsModified()
{
return (base.IsModified())
|| (audioMode != AudioMode.SystemDirect)
|| (httpHeaders != null && httpHeaders.Count > 0)
|| (string.IsNullOrEmpty(httpHeaderJson) == false)
|| (string.IsNullOrEmpty(keyServerURLOverride) == false)
|| (string.IsNullOrEmpty(keyServerAuthToken) == false)
|| (string.IsNullOrEmpty(base64EncodedKeyBlob) == false);
}
public override string GetKeyServerURL() { return keyServerURLOverride; }
public override string GetKeyServerAuthToken() { return keyServerAuthToken; }
public override string GetDecryptionKey() { return base64EncodedKeyBlob; }
// MARK: ISerializationCallbackReceiver
public void OnBeforeSerialize()
{
// If we have the new style headers and for some reason the
// json still exists get rid of it.
if (httpHeaders != null && httpHeaders.Count > 0 && httpHeaderJson.Length > 0)
{
httpHeaderJson = null;
}
}
public void OnAfterDeserialize()
{
if (httpHeaderJson == null || httpHeaderJson.Length == 0)
{
return;
}
httpHeaders = ParseJsonHTTPHeadersIntoHTTPHeaderList(httpHeaderJson);
if (httpHeaders != null)
{
httpHeaderJson = null;
}
}
}
[System.Serializable]
public class OptionsMacOSX : OptionsApple
{
}
[System.Serializable]
public class OptionsIOS : OptionsApple
{
public bool useYpCbCr420Textures = true;
public bool resumePlaybackOnAudioSessionRouteChange = false;
public override bool IsModified()
{
return (base.IsModified())
|| (useYpCbCr420Textures == false)
|| (resumePlaybackOnAudioSessionRouteChange == true);
}
}
[System.Serializable]
public class OptionsTVOS : OptionsIOS
{
}
[System.Serializable]
public class OptionsAndroid : PlatformOptions, ISerializationCallbackReceiver
{
public Android.VideoApi videoApi = Android.VideoApi.ExoPlayer;
public bool useFastOesPath = false;
public bool showPosterFrame = false;
public bool enableAudio360 = false;
public Audio360ChannelMode audio360ChannelMode = Audio360ChannelMode.TBE_8_2;
public bool preferSoftwareDecoder = false;
public List httpHeaders = new List();
[SerializeField, Multiline]
private string httpHeaderJson = null;
public string GetHTTPHeadersAsJSON()
{
if (httpHeaders.Count > 0)
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
int i = 0;
builder.Append("{");
builder.AppendFormat("\"{0}\":\"{1}\"", StringAsJsonString(httpHeaders[i].header), StringAsJsonString(httpHeaders[i].value));
for (i = 1; i < httpHeaders.Count; ++i)
builder.AppendFormat(",\"{0}\":\"{1}\"", StringAsJsonString(httpHeaders[i].header), StringAsJsonString(httpHeaders[i].value));
builder.Append("}");
return builder.ToString();
}
else
return httpHeaderJson;
}
[SerializeField, Tooltip("Byte offset into the file where the media file is located. This is useful when hiding or packing media files within another file.")]
public int fileOffset = 0;
public override bool IsModified()
{
return base.IsModified()
|| (fileOffset != 0)
|| useFastOesPath
|| showPosterFrame
|| (videoApi != Android.VideoApi.ExoPlayer)
|| (httpHeaders != null && httpHeaders.Count > 0)
|| enableAudio360
|| (audio360ChannelMode != Audio360ChannelMode.TBE_8_2)
|| preferSoftwareDecoder;
}
// MARK: ISerializationCallbackReceiver
public void OnBeforeSerialize()
{
// If we have the new style headers and for some reason the
// json still exists get rid of it.
if (httpHeaders != null && httpHeaders.Count > 0 && httpHeaderJson.Length > 0)
{
httpHeaderJson = null;
}
}
public void OnAfterDeserialize()
{
if (httpHeaderJson == null || httpHeaderJson.Length == 0)
{
return;
}
httpHeaders = ParseJsonHTTPHeadersIntoHTTPHeaderList(httpHeaderJson);
if (httpHeaders != null)
{
httpHeaderJson = null;
}
}
}
[System.Serializable]
public class OptionsWindowsPhone : PlatformOptions
{
public bool useHardwareDecoding = true;
public bool useUnityAudio = false;
public bool forceAudioResample = true;
public bool useTextureMips = false;
public bool useLowLatency = false;
public override bool IsModified()
{
return (base.IsModified() || !useHardwareDecoding || useTextureMips || useLowLatency || useUnityAudio || !forceAudioResample);
}
}
[System.Serializable]
public class OptionsWindowsUWP : PlatformOptions
{
public bool useHardwareDecoding = true;
public bool useUnityAudio = false;
public bool forceAudioResample = true;
public bool useTextureMips = false;
public bool useLowLatency = false;
public override bool IsModified()
{
return (base.IsModified() || !useHardwareDecoding || useTextureMips || useLowLatency || useUnityAudio || !forceAudioResample);
}
}
[System.Serializable]
public class OptionsWebGL : PlatformOptions
{
public WebGL.ExternalLibrary externalLibrary = WebGL.ExternalLibrary.None;
public bool useTextureMips = false;
public override bool IsModified()
{
return (base.IsModified() || externalLibrary != WebGL.ExternalLibrary.None || useTextureMips);
}
}
[System.Serializable]
public class OptionsPS4 : PlatformOptions
{
}
public delegate void ProcessExtractedFrame(Texture2D extractedFrame);
// TODO: move these to a Setup object
[SerializeField] OptionsWindows _optionsWindows = new OptionsWindows();
[SerializeField] OptionsMacOSX _optionsMacOSX = new OptionsMacOSX();
[SerializeField] OptionsIOS _optionsIOS = new OptionsIOS();
[SerializeField] OptionsTVOS _optionsTVOS = new OptionsTVOS();
[SerializeField] OptionsAndroid _optionsAndroid = new OptionsAndroid();
[SerializeField] OptionsWindowsPhone _optionsWindowsPhone = new OptionsWindowsPhone();
[SerializeField] OptionsWindowsUWP _optionsWindowsUWP = new OptionsWindowsUWP();
[SerializeField] OptionsWebGL _optionsWebGL = new OptionsWebGL();
[SerializeField] OptionsPS4 _optionsPS4 = new OptionsPS4();
///
/// Properties
///
public virtual IMediaInfo Info
{
get { return m_Info; }
}
public virtual IMediaControl Control
{
get { return m_Control; }
}
public virtual IMediaPlayer Player
{
get { return m_Player; }
}
public virtual IMediaProducer TextureProducer
{
get { return m_Texture; }
}
public virtual IMediaSubtitles Subtitles
{
get { return m_Subtitles; }
}
public MediaPlayerEvent Events
{
get
{
if (m_events == null)
{
m_events = new MediaPlayerEvent();
}
return m_events;
}
}
public bool VideoOpened
{
get { return m_VideoOpened; }
}
public bool PauseMediaOnAppPause
{
get { return _pauseMediaOnAppPause; }
set { _pauseMediaOnAppPause = value; }
}
public bool PlayMediaOnAppUnpause
{
get { return _playMediaOnAppUnpause; }
set { _playMediaOnAppUnpause = value; }
}
public FileFormat ForceFileFormat { get { return m_forceFileFormat; } set { m_forceFileFormat = value; } }
public Transform AudioHeadTransform { set { m_AudioHeadTransform = value; } get { return m_AudioHeadTransform; } }
public bool AudioFocusEnabled { get { return m_AudioFocusEnabled; } set { m_AudioFocusEnabled = value; } }
public float AudioFocusOffLevelDB { get { return m_AudioFocusOffLevelDB; } set { m_AudioFocusOffLevelDB = value; } }
public float AudioFocusWidthDegrees { get { return m_AudioFocusWidthDegrees; } set { m_AudioFocusWidthDegrees = value; } }
public Transform AudioFocusTransform { get { return m_AudioFocusTransform; } set { m_AudioFocusTransform = value; } }
public OptionsWindows PlatformOptionsWindows { get { return _optionsWindows; } }
public OptionsMacOSX PlatformOptionsMacOSX { get { return _optionsMacOSX; } }
public OptionsIOS PlatformOptionsIOS { get { return _optionsIOS; } }
public OptionsTVOS PlatformOptionsTVOS { get { return _optionsTVOS; } }
public OptionsAndroid PlatformOptionsAndroid { get { return _optionsAndroid; } }
public OptionsWindowsPhone PlatformOptionsWindowsPhone { get { return _optionsWindowsPhone; } }
public OptionsWindowsUWP PlatformOptionsWindowsUWP { get { return _optionsWindowsUWP; } }
public OptionsWebGL PlatformOptionsWebGL { get { return _optionsWebGL; } }
public OptionsPS4 PlatformOptionsPS4 { get { return _optionsPS4; } }
///
/// Methods
///
void Awake()
{
if (m_Persistent)
{
// TODO: set "this.transform.root.gameObject" to also DontDestroyOnLoad?
DontDestroyOnLoad(this.gameObject);
}
}
protected void Initialise()
{
BaseMediaPlayer mediaPlayer = CreatePlatformMediaPlayer();
if (mediaPlayer != null)
{
// Set-up interface
m_Control = mediaPlayer;
m_Texture = mediaPlayer;
m_Info = mediaPlayer;
m_Player = mediaPlayer;
m_Subtitles = mediaPlayer;
m_Dispose = mediaPlayer;
if (!s_GlobalStartup)
{
#if UNITY_5 || UNITY_5_4_OR_NEWER
Helper.LogInfo(string.Format("Initialising AVPro Video (script v{0} plugin v{1}) on {2}/{3} (MT {4}) on {5} {6}", Helper.ScriptVersion, mediaPlayer.GetVersion(), SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceVersion, SystemInfo.graphicsMultiThreaded, Application.platform, SystemInfo.operatingSystem));
#else
Helper.LogInfo(string.Format("Initialising AVPro Video (script v{0} plugin v{1}) on {2}/{3} on {4} {5}", Helper.ScriptVersion, mediaPlayer.GetVersion(), SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceVersion, Application.platform, SystemInfo.operatingSystem));
#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
#if (UNITY_HAS_GOOGLEVR || UNITY_DAYDREAM) && (UNITY_ANDROID)
// NOTE: We've removed this minor optimisation until Daydream support is more official..
// It seems to work with the official release, but in 5.6beta UNITY_HAS_GOOGLEVR is always defined
// even for GearVR, which causes a problem as it doesn't use the same stereo eye determination method
// TODO: add iOS support for this once Unity supports it
//Helper.LogInfo("Enabling Google Daydream support");
//Shader.EnableKeyword("GOOGLEVR");
#endif
s_GlobalStartup = true;
}
}
}
void Start()
{
#if UNITY_WEBGL
m_Resample = false;
#endif
if (m_Control == null)
{
Initialise();
}
if (m_Control != null)
{
if (m_AutoOpen)
{
OpenVideoFromFile();
if (m_LoadSubtitles && m_Subtitles != null && !string.IsNullOrEmpty(m_SubtitlePath))
{
EnableSubtitles(m_SubtitleLocation, m_SubtitlePath);
}
}
StartRenderCoroutine();
}
}
public bool OpenVideoFromFile(FileLocation location, string path, bool autoPlay = true)
{
m_VideoLocation = location;
m_VideoPath = path;
m_AutoStart = autoPlay;
if (m_Control == null)
{
m_AutoOpen = false; // If OpenVideoFromFile() is called before Start() then set m_AutoOpen to false so that it doesn't load the video a second time during Start()
Initialise();
}
return OpenVideoFromFile();
}
public bool OpenVideoFromBuffer(byte[] buffer, bool autoPlay = true)
{
m_VideoLocation = FileLocation.AbsolutePathOrURL;
m_VideoPath = "buffer";
m_AutoStart = autoPlay;
if (m_Control == null)
{
Initialise();
}
return OpenVideoFromBufferInternal(buffer);
}
public bool StartOpenChunkedVideoFromBuffer(ulong length, bool autoPlay = true)
{
m_VideoLocation = FileLocation.AbsolutePathOrURL;
m_VideoPath = "buffer";
m_AutoStart = autoPlay;
if (m_Control == null)
{
Initialise();
}
return StartOpenVideoFromBufferInternal(length);
}
public bool AddChunkToVideoBuffer(byte[] chunk, ulong offset, ulong chunkSize)
{
return AddChunkToBufferInternal(chunk, offset, chunkSize);
}
public bool EndOpenChunkedVideoFromBuffer()
{
return EndOpenVideoFromBufferInternal();
}
#if NETFX_CORE
public bool OpenVideoFromStream(IRandomAccessStream ras, string path, bool autoPlay = true)
{
m_VideoLocation = FileLocation.AbsolutePathOrURL;
m_VideoPath = path;
m_AutoStart = autoPlay;
if (m_Control == null)
{
Initialise();
}
return OpenVideoFromStream(ras);
}
#endif
public bool SubtitlesEnabled
{
get { return m_LoadSubtitles; }
}
public string SubtitlePath
{
get { return m_SubtitlePath; }
}
public FileLocation SubtitleLocation
{
get { return m_SubtitleLocation; }
}
public bool EnableSubtitles(FileLocation fileLocation, string filePath)
{
bool result = false;
if (m_Subtitles != null)
{
if (!string.IsNullOrEmpty(filePath))
{
string fullPath = GetPlatformFilePath(GetPlatform(), ref filePath, ref fileLocation);
bool checkForFileExist = true;
if (fullPath.Contains("://"))
{
checkForFileExist = false;
}
#if (REAL_ANDROID || (UNITY_5_2 && UNITY_WSA))
checkForFileExist = false;
#endif
if (checkForFileExist && !System.IO.File.Exists(fullPath))
{
Debug.LogError("[AVProVideo] Subtitle file not found: " + fullPath, this);
}
else
{
Helper.LogInfo("Opening subtitles " + fullPath, this);
m_previousSubtitleIndex = -1;
try
{
if (fullPath.Contains("://"))
{
// Use coroutine and WWW class for loading
if (m_loadSubtitlesRoutine != null)
{
StopCoroutine(m_loadSubtitlesRoutine);
m_loadSubtitlesRoutine = null;
}
m_loadSubtitlesRoutine = StartCoroutine(LoadSubtitlesCoroutine(fullPath, fileLocation, filePath));
}
else
{
// Load directly from file
#if !UNITY_WEBPLAYER
string subtitleData = System.IO.File.ReadAllText(fullPath);
if (m_Subtitles.LoadSubtitlesSRT(subtitleData))
{
m_SubtitleLocation = fileLocation;
m_SubtitlePath = filePath;
m_LoadSubtitles = false;
result = true;
}
else
#endif
{
Debug.LogError("[AVProVideo] Failed to load subtitles" + fullPath, this);
}
}
}
catch (System.Exception e)
{
Debug.LogError("[AVProVideo] Failed to load subtitles " + fullPath, this);
Debug.LogException(e, this);
}
}
}
else
{
Debug.LogError("[AVProVideo] No subtitle file path specified", this);
}
}
else
{
m_queueSubtitleLocation = fileLocation;
m_queueSubtitlePath = filePath;
}
return result;
}
private IEnumerator LoadSubtitlesCoroutine(string url, FileLocation fileLocation, string filePath)
{
#if UNITY_5_4_OR_NEWER
UnityEngine.Networking.UnityWebRequest www = UnityEngine.Networking.UnityWebRequest.Get(url);
#elif UNITY_5_5_OR_NEWER
UnityEngine.Experimental.Networking.UnityWebRequest www = UnityEngine.Experimental.Networking.UnityWebRequest.Get(url);
#else
WWW www = new WWW(url);
yield return www;
#endif
#if UNITY_2017_2_OR_NEWER
yield return www.SendWebRequest();
#elif UNITY_5_4_OR_NEWER
yield return www.Send();
#endif
string subtitleData = string.Empty;
#if UNITY_2017_1_OR_NEWER
if (!www.isNetworkError)
#elif UNITY_5_4_OR_NEWER
if (!www.isError)
#endif
#if UNITY_5_4_OR_NEWER
{
subtitleData = ((UnityEngine.Networking.DownloadHandler)www.downloadHandler).text;
}
#else
if (string.IsNullOrEmpty(www.error))
{
subtitleData = www.text;
}
#endif
else
{
Debug.LogError("[AVProVideo] Error loading subtitles '" + www.error + "' from " + url);
}
if (m_Subtitles.LoadSubtitlesSRT(subtitleData))
{
m_SubtitleLocation = fileLocation;
m_SubtitlePath = filePath;
m_LoadSubtitles = false;
}
else
{
Debug.LogError("[AVProVideo] Failed to load subtitles" + url, this);
}
m_loadSubtitlesRoutine = null;
www.Dispose();
}
public void DisableSubtitles()
{
if (m_loadSubtitlesRoutine != null)
{
StopCoroutine(m_loadSubtitlesRoutine);
m_loadSubtitlesRoutine = null;
}
if (m_Subtitles != null)
{
m_previousSubtitleIndex = -1;
m_LoadSubtitles = false;
m_Subtitles.LoadSubtitlesSRT(string.Empty);
}
else
{
m_queueSubtitlePath = string.Empty;
}
}
private bool OpenVideoFromBufferInternal(byte[] buffer)
{
bool result = false;
// Open the video file
if (m_Control != null)
{
CloseVideo();
m_VideoOpened = true;
m_AutoStartTriggered = !m_AutoStart;
Helper.LogInfo("Opening buffer of length " + buffer.Length, this);
if (!m_Control.OpenVideoFromBuffer(buffer))
{
Debug.LogError("[AVProVideo] Failed to open buffer", this);
if (GetCurrentPlatformOptions() != PlatformOptionsWindows || PlatformOptionsWindows.videoApi != Windows.VideoApi.DirectShow)
{
Debug.LogError("[AVProVideo] Loading from buffer is currently only supported in Windows when using the DirectShow API");
}
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
return result;
}
private bool StartOpenVideoFromBufferInternal(ulong length)
{
bool result = false;
// Open the video file
if (m_Control != null)
{
CloseVideo();
m_VideoOpened = true;
m_AutoStartTriggered = !m_AutoStart;
Helper.LogInfo("Starting Opening buffer of length " + length, this);
if (!m_Control.StartOpenVideoFromBuffer(length))
{
Debug.LogError("[AVProVideo] Failed to start open video from buffer", this);
if (GetCurrentPlatformOptions() != PlatformOptionsWindows || PlatformOptionsWindows.videoApi != Windows.VideoApi.DirectShow)
{
Debug.LogError("[AVProVideo] Loading from buffer is currently only supported in Windows when using the DirectShow API");
}
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
return result;
}
private bool AddChunkToBufferInternal(byte[] chunk, ulong offset, ulong chunkSize)
{
if (Control != null)
{
return Control.AddChunkToVideoBuffer(chunk, offset, chunkSize);
}
return false;
}
private bool EndOpenVideoFromBufferInternal()
{
if (Control != null)
{
return Control.EndOpenVideoFromBuffer();
}
return false;
}
private bool OpenVideoFromFile()
{
bool result = false;
// Open the video file
if (m_Control != null)
{
CloseVideo();
m_VideoOpened = true;
m_AutoStartTriggered = !m_AutoStart;
m_FinishedFrameOpenCheck = true;
// Potentially override the file location
long fileOffset = GetPlatformFileOffset();
string fullPath = GetPlatformFilePath(GetPlatform(), ref m_VideoPath, ref m_VideoLocation);
if (!string.IsNullOrEmpty(m_VideoPath))
{
string httpHeaderJson = null;
bool checkForFileExist = true;
if (fullPath.Contains("://"))
{
checkForFileExist = false;
httpHeaderJson = GetPlatformHttpHeaderJson();
}
#if (REAL_ANDROID || (UNITY_5_2 && UNITY_WSA))
checkForFileExist = false;
#endif
if (checkForFileExist && !System.IO.File.Exists(fullPath))
{
Debug.LogError("[AVProVideo] File not found: " + fullPath, this);
}
else
{
Helper.LogInfo(string.Format("Opening {0} (offset {1}) with API {2}", fullPath, fileOffset, GetPlatformVideoApiString()), this);
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
if (_optionsWindows.enableAudio360)
{
m_Control.SetAudioChannelMode(_optionsWindows.audio360ChannelMode);
}
else
{
m_Control.SetAudioChannelMode(Audio360ChannelMode.INVALID);
}
#endif
if (!m_Control.OpenVideoFromFile(fullPath, fileOffset, httpHeaderJson, m_manuallySetAudioSourceProperties ? m_sourceSampleRate : 0,
m_manuallySetAudioSourceProperties ? m_sourceChannels : 0, (int)m_forceFileFormat))
{
Debug.LogError("[AVProVideo] Failed to open " + fullPath, this);
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
}
else
{
Debug.LogError("[AVProVideo] No file path specified", this);
}
}
return result;
}
#if NETFX_CORE
private bool OpenVideoFromStream(IRandomAccessStream ras)
{
bool result = false;
// Open the video file
if (m_Control != null)
{
CloseVideo();
m_VideoOpened = true;
m_AutoStartTriggered = !m_AutoStart;
// Potentially override the file location
long fileOffset = GetPlatformFileOffset();
if (!m_Control.OpenVideoFromFile(ras, m_VideoPath, fileOffset, null, m_manuallySetAudioSourceProperties ? m_sourceSampleRate : 0,
m_manuallySetAudioSourceProperties ? m_sourceChannels : 0))
{
Debug.LogError("[AVProVideo] Failed to open " + m_VideoPath, this);
}
else
{
SetPlaybackOptions();
result = true;
StartRenderCoroutine();
}
}
return result;
}
#endif
private void SetPlaybackOptions()
{
// Set playback options
if (m_Control != null)
{
m_Control.SetLooping(m_Loop);
m_Control.SetPlaybackRate(m_PlaybackRate);
m_Control.SetVolume(m_Volume);
m_Control.SetBalance(m_Balance);
m_Control.MuteAudio(m_Muted);
m_Control.SetTextureProperties(m_FilterMode, m_WrapMode, m_AnisoLevel);
// Encryption support
PlatformOptions options = GetCurrentPlatformOptions();
if (options != null)
{
m_Control.SetKeyServerURL(options.GetKeyServerURL());
m_Control.SetKeyServerAuthToken(options.GetKeyServerAuthToken());
m_Control.SetDecryptionKeyBase64(options.GetDecryptionKey());
}
}
}
public void CloseVideo()
{
// Close the video file
if (m_Control != null)
{
if (m_events != null && m_VideoOpened && m_events.HasListeners() && IsHandleEvent(MediaPlayerEvent.EventType.Closing))
{
m_events.Invoke(this, MediaPlayerEvent.EventType.Closing, ErrorCode.None);
}
m_AutoStartTriggered = false;
m_VideoOpened = false;
m_EventFired_MetaDataReady = false;
m_EventFired_ReadyToPlay = false;
m_EventFired_Started = false;
m_EventFired_FirstFrameReady = false;
m_EventFired_FinishedPlaying = false;
m_EventState_PlaybackBuffering = false;
m_EventState_PlaybackSeeking = false;
m_EventState_PlaybackStalled = false;
m_EventState_PreviousWidth = 0;
m_EventState_PreviousHeight = 0;
if (m_loadSubtitlesRoutine != null)
{
StopCoroutine(m_loadSubtitlesRoutine);
m_loadSubtitlesRoutine = null;
}
m_previousSubtitleIndex = -1;
m_Control.CloseVideo();
}
if (m_Resampler != null)
{
m_Resampler.Reset();
}
StopRenderCoroutine();
}
public void Play()
{
if (m_Control != null && m_Control.CanPlay())
{
m_Control.Play();
// Mark this event as done because it's irrelevant once playback starts
m_EventFired_ReadyToPlay = true;
}
else
{
// Can't play, perhaps it's still loading? Queuing play using m_AutoStart to play after loading
m_AutoStart = true;
m_AutoStartTriggered = false;
}
}
public void Pause()
{
if (m_Control != null && m_Control.IsPlaying())
{
m_Control.Pause();
}
m_WasPlayingOnPause = false;
#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();
}
}
protected virtual void Update()
{
// Auto start the playback
if (m_Control != null)
{
if (m_VideoOpened && m_AutoStart && !m_AutoStartTriggered && m_Control.CanPlay())
{
m_AutoStartTriggered = true;
Play();
}
if (_renderingCoroutine == null && m_Control.CanPlay())
{
StartRenderCoroutine();
}
if (m_Subtitles != null && !string.IsNullOrEmpty(m_queueSubtitlePath))
{
EnableSubtitles(m_queueSubtitleLocation, m_queueSubtitlePath);
m_queueSubtitlePath = string.Empty;
}
#if AVPROVIDEO_BETA_SUPPORT_TIMESCALE
UpdateTimeScale();
#endif
UpdateAudioHeadTransform();
UpdateAudioFocus();
// Update
m_Player.Update();
// Render (done in co-routine)
//m_Player.Render();
UpdateErrors();
UpdateEvents();
}
#if UNITY_EDITOR && UNITY_5_4_OR_NEWER
CheckEditorAudioMute();
#endif
}
private void LateUpdate()
{
UpdateResampler();
}
private void UpdateResampler()
{
#if !UNITY_WEBGL
if (m_Resample)
{
if (m_Resampler == null)
{
m_Resampler = new Resampler(this, gameObject.name, m_ResampleBufferSize, m_ResampleMode);
}
}
#else
m_Resample = false;
#endif
if (m_Resampler != null)
{
m_Resampler.Update();
m_Resampler.UpdateTimestamp();
}
}
void OnEnable()
{
if (m_Control != null && m_WasPlayingOnPause)
{
m_AutoStart = true;
m_AutoStartTriggered = false;
m_WasPlayingOnPause = false;
}
if (m_Player != null)
{
m_Player.OnEnable();
}
StartRenderCoroutine();
}
void OnDisable()
{
if (m_Control != null)
{
if (m_Control.IsPlaying())
{
m_WasPlayingOnPause = true;
Pause();
}
}
StopRenderCoroutine();
}
protected virtual void OnDestroy()
{
CloseVideo();
if (m_Dispose != null)
{
m_Dispose.Dispose();
m_Dispose = null;
}
m_Control = null;
m_Texture = null;
m_Info = null;
m_Player = null;
if (m_Resampler != null)
{
m_Resampler.Release();
m_Resampler = null;
}
// TODO: possible bug if MediaPlayers are created and destroyed manually (instantiated), OnApplicationQuit won't be called!
}
void OnApplicationQuit()
{
if (s_GlobalStartup)
{
Helper.LogInfo("Shutdown");
// Clean up any open media players
MediaPlayer[] players = Resources.FindObjectsOfTypeAll();
if (players != null && players.Length > 0)
{
for (int i = 0; i < players.Length; i++)
{
players[i].CloseVideo();
players[i].OnDestroy();
}
}
#if UNITY_EDITOR
#if UNITY_EDITOR_WIN
WindowsMediaPlayer.DeinitPlatform();
#endif
#else
#if (UNITY_STANDALONE_WIN)
WindowsMediaPlayer.DeinitPlatform();
#elif (UNITY_ANDROID)
AndroidMediaPlayer.DeinitPlatform();
#endif
#endif
s_GlobalStartup = false;
}
}
#region Rendering Coroutine
private void StartRenderCoroutine()
{
if (_renderingCoroutine == null)
{
// Use the method instead of the method name string to prevent garbage
_renderingCoroutine = StartCoroutine(FinalRenderCapture());
}
}
private void StopRenderCoroutine()
{
if (_renderingCoroutine != null)
{
StopCoroutine(_renderingCoroutine);
_renderingCoroutine = null;
}
}
private IEnumerator FinalRenderCapture()
{
// Preallocate the YieldInstruction to prevent garbage
YieldInstruction wait = new WaitForEndOfFrame();
while (Application.isPlaying)
{
// NOTE: in editor, if the game view isn't visible then WaitForEndOfFrame will never complete
yield return wait;
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 || UNITY_WP81 || UNITY_WINRT_8_1)
result = Platform.WindowsPhone;
#elif (UNITY_WSA_10_0)
result = Platform.WindowsUWP;
#elif (UNITY_WEBGL)
result = Platform.WebGL;
#elif (UNITY_PS4)
result = Platform.PS4;
#endif
#endif
return result;
}
public PlatformOptions GetCurrentPlatformOptions()
{
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;
#elif (UNITY_WEBGL)
result = _optionsWebGL;
#elif (UNITY_PS4)
result = _optionsPS4;
#endif
#endif
return result;
}
#if UNITY_EDITOR
public PlatformOptions GetPlatformOptions(Platform platform)
{
PlatformOptions result = null;
switch (platform)
{
case Platform.Windows:
result = _optionsWindows;
break;
case Platform.MacOSX:
result = _optionsMacOSX;
break;
case Platform.Android:
result = _optionsAndroid;
break;
case Platform.iOS:
result = _optionsIOS;
break;
case Platform.tvOS:
result = _optionsTVOS;
break;
case Platform.WindowsPhone:
result = _optionsWindowsPhone;
break;
case Platform.WindowsUWP:
result = _optionsWindowsUWP;
break;
case Platform.WebGL:
result = _optionsWebGL;
break;
case Platform.PS4:
result = _optionsPS4;
break;
}
return result;
}
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;
case Platform.WebGL:
result = "_optionsWebGL";
break;
case Platform.PS4:
result = "_optionsPS4";
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.RelativeToPersistentDataFolder:
result = Application.persistentDataPath;
break;
case FileLocation.RelativeToProjectFolder:
#if !UNITY_WINRT_8_1
string path = "..";
#if UNITY_STANDALONE_OSX && !UNITY_EDITOR_OSX
path += "/..";
#endif
result = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, path));
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.RelativeToPersistentDataFolder:
case FileLocation.RelativeToProjectFolder:
case FileLocation.RelativeToStreamingAssetsFolder:
result = System.IO.Path.Combine(GetPath(location), path);
break;
}
}
return result;
}
private string GetPlatformVideoApiString()
{
string result = string.Empty;
#if UNITY_EDITOR
#if UNITY_EDITOR_OSX
#elif UNITY_EDITOR_WIN
result = _optionsWindows.videoApi.ToString();
#elif UNITY_EDITOR_LINUX
#endif
#else
#if UNITY_STANDALONE_WIN
result = _optionsWindows.videoApi.ToString();
#elif UNITY_ANDROID
result = _optionsAndroid.videoApi.ToString();
#endif
#endif
return result;
}
private long GetPlatformFileOffset()
{
long result = 0;
#if UNITY_EDITOR
#if UNITY_EDITOR_OSX
#elif UNITY_EDITOR_WIN
#elif UNITY_EDITOR_LINUX
#endif
#else
#if UNITY_ANDROID
result = _optionsAndroid.fileOffset;
#endif
#endif
return result;
}
private string GetPlatformHttpHeaderJson()
{
string result = null;
#if UNITY_EDITOR_OSX
result = _optionsMacOSX.GetHTTPHeadersAsJSON();
#elif UNITY_EDITOR_WIN
#elif UNITY_EDITOR_LINUX
#elif UNITY_STANDALONE_OSX
result = _optionsMacOSX.GetHTTPHeadersAsJSON();
#elif UNITY_STANDALONE_WIN
#elif UNITY_WSA_10_0
#elif UNITY_WINRT_8_1
#elif UNITY_IOS || UNITY_IPHONE
result = _optionsIOS.GetHTTPHeadersAsJSON();
#elif UNITY_TVOS
result = _optionsTVOS.GetHTTPHeadersAsJSON();
#elif UNITY_ANDROID
result = _optionsAndroid.GetHTTPHeadersAsJSON();
#elif UNITY_WEBGL
#endif
if (!string.IsNullOrEmpty(result))
{
result = result.Trim();
}
return result;
}
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError = true)]
private static extern int GetShortPathName([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string pathName,
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] System.Text.StringBuilder shortName,
int cbShortName);
#endif
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 = GetCurrentPlatformOptions();
if (options != null)
{
if (options.overridePath)
{
filePath = options.path;
fileLocation = options.pathLocation;
}
}
}
result = GetFilePath(filePath, fileLocation);
#if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN))
// Handle very long file paths by converting to DOS 8.3 format
if (result.Length > 200 && !result.Contains("://"))
{
const string pathToken = @"\\?\";
result = pathToken + result.Replace("/", "\\");
int length = GetShortPathName(result, null, 0);
if (length > 0)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(length);
if (0 != GetShortPathName(result, sb, length))
{
result = sb.ToString().Replace(pathToken, "");
Debug.LogWarning("[AVProVideo] Long path detected. Changing to DOS 8.3 format");
}
}
}
#endif
return result;
}
#endregion
public virtual BaseMediaPlayer CreatePlatformMediaPlayer()
{
BaseMediaPlayer mediaPlayer = null;
#if !AVPROVIDEO_FORCE_NULL_MEDIAPLAYER
// Setup for running in the editor (Either OSX, Windows or Linux)
#if UNITY_EDITOR
#if (UNITY_EDITOR_OSX)
#if UNITY_EDITOR_64
OSXMediaPlayer macOSMediaPlayer = new OSXMediaPlayer();
mediaPlayer = macOSMediaPlayer;
if (_optionsMacOSX.audioMode == OptionsApple.AudioMode.Unity)
macOSMediaPlayer.EnableAudioCapture();
#else
Debug.LogWarning("[AVProVideo] 32-bit OS X Unity editor not supported. 64-bit required.");
#endif
#elif UNITY_EDITOR_WIN
if (WindowsMediaPlayer.InitialisePlatform())
{
mediaPlayer = new WindowsMediaPlayer(_optionsWindows.videoApi, _optionsWindows.useHardwareDecoding, _optionsWindows.useTextureMips, _optionsWindows.hintAlphaChannel, _optionsWindows.useLowLatency, _optionsWindows.forceAudioOutputDeviceName, _optionsWindows.useUnityAudio, _optionsWindows.forceAudioResample, _optionsWindows.preferredFilters);
}
#endif
#else
// Setup for running builds
#if (UNITY_STANDALONE_WIN || UNITY_WSA_10_0 || UNITY_WINRT_8_1)
if (WindowsMediaPlayer.InitialisePlatform())
{
#if UNITY_STANDALONE_WIN
mediaPlayer = new WindowsMediaPlayer(_optionsWindows.videoApi, _optionsWindows.useHardwareDecoding, _optionsWindows.useTextureMips, _optionsWindows.hintAlphaChannel, _optionsWindows.useLowLatency, _optionsWindows.forceAudioOutputDeviceName, _optionsWindows.useUnityAudio, _optionsWindows.forceAudioResample, _optionsWindows.preferredFilters);
#elif UNITY_WSA_10_0
mediaPlayer = new WindowsMediaPlayer(Windows.VideoApi.MediaFoundation, _optionsWindowsUWP.useHardwareDecoding, _optionsWindowsUWP.useTextureMips, false, _optionsWindowsUWP.useLowLatency, string.Empty, _optionsWindowsUWP.useUnityAudio, _optionsWindowsUWP.forceAudioResample, _optionsWindows.preferredFilters);
#elif UNITY_WINRT_8_1
mediaPlayer = new WindowsMediaPlayer(Windows.VideoApi.MediaFoundation, _optionsWindowsPhone.useHardwareDecoding, _optionsWindowsPhone.useTextureMips, false, _optionsWindowsPhone.useLowLatency, string.Empty, _optionsWindowsPhone.useUnityAudio, _optionsWindowsPhone.forceAudioResample, _optionsWindows.preferredFilters);
#endif
}
#elif (UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS)
bool appleEnableAudioCapture = false;
#if UNITY_TVOS
OSXMediaPlayer osxMediaPlayer = new OSXMediaPlayer(_optionsTVOS.useYpCbCr420Textures);
appleEnableAudioCapture = _optionsTVOS.audioMode == OptionsApple.AudioMode.Unity;
#elif (UNITY_IOS || UNITY_IPHONE)
OSXMediaPlayer osxMediaPlayer = new OSXMediaPlayer(_optionsIOS.useYpCbCr420Textures);
osxMediaPlayer.SetResumePlaybackOnAudioSessionRouteChange(_optionsIOS.resumePlaybackOnAudioSessionRouteChange);
appleEnableAudioCapture = _optionsIOS.audioMode == OptionsApple.AudioMode.Unity;
#else
OSXMediaPlayer osxMediaPlayer = new OSXMediaPlayer();
appleEnableAudioCapture = _optionsMacOSX.audioMode == OptionsApple.AudioMode.Unity;
#endif
mediaPlayer = osxMediaPlayer;
if (appleEnableAudioCapture)
osxMediaPlayer.EnableAudioCapture();
#elif (UNITY_ANDROID)
// Initialise platform (also unpacks videos from StreamingAsset folder (inside a jar), to the persistent data path)
if (AndroidMediaPlayer.InitialisePlatform())
{
mediaPlayer = new AndroidMediaPlayer(_optionsAndroid.useFastOesPath, _optionsAndroid.showPosterFrame, _optionsAndroid.videoApi,
_optionsAndroid.enableAudio360, _optionsAndroid.audio360ChannelMode, _optionsAndroid.preferSoftwareDecoder);
}
#elif (UNITY_WEBGL)
WebGLMediaPlayer.InitialisePlatform();
mediaPlayer = new WebGLMediaPlayer(_optionsWebGL.externalLibrary, _optionsWebGL.useTextureMips);
#elif (UNITY_PS4)
mediaPlayer = new PS4MediaPlayer();
#endif
#endif
#endif
// Fallback
if (mediaPlayer == null)
{
Debug.LogError(string.Format("[AVProVideo] Not supported on this platform {0} {1} {2} {3}. Using null media player!", Application.platform, SystemInfo.deviceModel, SystemInfo.processorType, SystemInfo.operatingSystem));
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 (m_Control.IsPlaying())
{
m_Control.Pause();
_timeScaleIsControlling = true;
_timeScaleVideoTime = m_Control.GetCurrentTimeMs();
}
if (_timeScaleIsControlling)
{
// Progress time
_timeScaleVideoTime += (Time.deltaTime * 1000f);
// Handle looping
if (m_Control.IsLooping() && _timeScaleVideoTime >= Info.GetDurationMs())
{
// TODO: really we should seek to (_timeScaleVideoTime % Info.GetDurationMs())
_timeScaleVideoTime = 0f;
}
int preSeekFrameCount = m_Texture.GetTextureFrameCount();
// Seek to the new time
{
float preSeekTime = Control.GetCurrentTimeMs();
// Seek
m_Control.Seek(_timeScaleVideoTime);
// Early out, if after the seek the time hasn't changed, the seek was probably too small to go to the next frame.
// TODO: This behaviour may be different on other platforms (not Windows) and needs more testing.
if (Mathf.Approximately(preSeekTime, m_Control.GetCurrentTimeMs()))
{
return;
}
}
// Wait for the new frame to arrive
if (!m_Control.WaitForNextFrame(GetDummyCamera(), preSeekFrameCount))
{
// If WaitForNextFrame fails (e.g. in android single threaded), we run the below code to asynchronously wait for the frame
System.DateTime startTime = System.DateTime.Now;
int lastFrameCount = TextureProducer.GetTextureFrameCount();
while (m_Control != null && (System.DateTime.Now - startTime).TotalMilliseconds < (double)TimeScaleTimeoutMs)
{
m_Player.Update();
m_Player.Render();
GetDummyCamera().Render();
if (lastFrameCount != TextureProducer.GetTextureFrameCount())
{
break;
}
}
}
}
}
else
{
// Restore playback when timeScale becomes 1
if (_timeScaleIsControlling)
{
m_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 UpdateAudioFocus()
{
// TODO: we could use gizmos to draw the focus area
m_Control.SetAudioFocusEnabled(m_AudioFocusEnabled);
m_Control.SetAudioFocusProperties(m_AudioFocusOffLevelDB, m_AudioFocusWidthDegrees);
m_Control.SetAudioFocusRotation(m_AudioFocusTransform == null ? Quaternion.identity : m_AudioFocusTransform.rotation);
}
private void UpdateAudioHeadTransform()
{
if (m_AudioHeadTransform != null)
{
m_Control.SetAudioHeadRotation(m_AudioHeadTransform.rotation);
}
else
{
m_Control.ResetAudioHeadRotation();
}
}
private void UpdateErrors()
{
ErrorCode errorCode = m_Control.GetLastError();
if (ErrorCode.None != errorCode)
{
Debug.LogError("[AVProVideo] Error: " + Helper.GetErrorMessage(errorCode));
// Display additional information for load failures
if (ErrorCode.LoadFailed == errorCode)
{
#if !UNITY_EDITOR && UNITY_ANDROID
if (m_VideoPath.ToLower().Contains("http://"))
{
Debug.LogError("Android 8 and above require HTTPS by default, change to HTTPS or enable ClearText in the AndroidManifest.xml");
}
#endif
}
if (m_events != null && m_events.HasListeners() && IsHandleEvent(MediaPlayerEvent.EventType.Error))
{
m_events.Invoke(this, MediaPlayerEvent.EventType.Error, errorCode);
}
}
}
private void UpdateEvents()
{
if (m_events != null && m_Control != null && m_events.HasListeners())
{
//NOTE: Fixes a bug where the event was being fired immediately, so when a file is opened, the finishedPlaying fired flag gets set but
//is then set to true immediately afterwards due to the returned value
m_FinishedFrameOpenCheck = false;
if (IsHandleEvent(MediaPlayerEvent.EventType.FinishedPlaying))
{
if (FireEventIfPossible(MediaPlayerEvent.EventType.FinishedPlaying, m_EventFired_FinishedPlaying))
{
m_EventFired_FinishedPlaying = !m_FinishedFrameOpenCheck;
}
}
// Reset some event states that can reset during playback
{
// Keep track of whether the Playing state has changed
if (m_EventFired_Started && IsHandleEvent(MediaPlayerEvent.EventType.Started) &&
m_Control != null && !m_Control.IsPlaying() && !m_Control.IsSeeking())
{
// Playing has stopped
m_EventFired_Started = false;
}
// NOTE: We check m_Control isn't null in case the scene is unloaded in response to the FinishedPlaying event
if (m_EventFired_FinishedPlaying && IsHandleEvent(MediaPlayerEvent.EventType.FinishedPlaying) &&
m_Control != null && m_Control.IsPlaying() && !m_Control.IsFinished())
{
bool reset = true;
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_WSA))
reset = false;
if (m_Info.HasVideo())
{
// Don't reset if within a frame of the end of the video, important for time > duration workaround
float msPerFrame = 1000f / m_Info.GetVideoFrameRate();
//Debug.Log(m_Info.GetDurationMs() - m_Control.GetCurrentTimeMs() + " " + msPerFrame);
if (m_Info.GetDurationMs() - m_Control.GetCurrentTimeMs() > msPerFrame)
{
reset = true;
}
}
else
{
// For audio only media just check if we're not beyond the duration
if (m_Control.GetCurrentTimeMs() < m_Info.GetDurationMs())
{
reset = true;
}
}
#endif
if (reset)
{
//Debug.Log("Reset");
m_EventFired_FinishedPlaying = false;
}
}
}
// Events that can only fire once
m_EventFired_MetaDataReady = FireEventIfPossible(MediaPlayerEvent.EventType.MetaDataReady, m_EventFired_MetaDataReady);
m_EventFired_ReadyToPlay = FireEventIfPossible(MediaPlayerEvent.EventType.ReadyToPlay, m_EventFired_ReadyToPlay);
m_EventFired_Started = FireEventIfPossible(MediaPlayerEvent.EventType.Started, m_EventFired_Started);
m_EventFired_FirstFrameReady = FireEventIfPossible(MediaPlayerEvent.EventType.FirstFrameReady, m_EventFired_FirstFrameReady);
// Events that can fire multiple times
{
// Subtitle changing
if (FireEventIfPossible(MediaPlayerEvent.EventType.SubtitleChange, false))
{
m_previousSubtitleIndex = m_Subtitles.GetSubtitleIndex();
}
// Resolution changing
if (FireEventIfPossible(MediaPlayerEvent.EventType.ResolutionChanged, false))
{
m_EventState_PreviousWidth = m_Info.GetVideoWidth();
m_EventState_PreviousHeight = m_Info.GetVideoHeight();
}
// Stalling
if (IsHandleEvent(MediaPlayerEvent.EventType.Stalled))
{
bool newState = m_Info.IsPlaybackStalled();
if (newState != m_EventState_PlaybackStalled)
{
m_EventState_PlaybackStalled = newState;
var newEvent = m_EventState_PlaybackStalled ? MediaPlayerEvent.EventType.Stalled : MediaPlayerEvent.EventType.Unstalled;
FireEventIfPossible(newEvent, false);
}
}
// Seeking
if (IsHandleEvent(MediaPlayerEvent.EventType.StartedSeeking))
{
bool newState = m_Control.IsSeeking();
if (newState != m_EventState_PlaybackSeeking)
{
m_EventState_PlaybackSeeking = newState;
var newEvent = m_EventState_PlaybackSeeking ? MediaPlayerEvent.EventType.StartedSeeking : MediaPlayerEvent.EventType.FinishedSeeking;
FireEventIfPossible(newEvent, false);
}
}
// Buffering
if (IsHandleEvent(MediaPlayerEvent.EventType.StartedBuffering))
{
bool newState = m_Control.IsBuffering();
if (newState != m_EventState_PlaybackBuffering)
{
m_EventState_PlaybackBuffering = newState;
var newEvent = m_EventState_PlaybackBuffering ? MediaPlayerEvent.EventType.StartedBuffering : MediaPlayerEvent.EventType.FinishedBuffering;
FireEventIfPossible(newEvent, false);
}
}
}
}
}
protected bool IsHandleEvent(MediaPlayerEvent.EventType eventType)
{
return ((uint)m_eventMask & (1 << (int)eventType)) != 0;
}
private bool FireEventIfPossible(MediaPlayerEvent.EventType eventType, bool hasFired)
{
if (CanFireEvent(eventType, hasFired))
{
hasFired = true;
m_events.Invoke(this, eventType, ErrorCode.None);
}
return hasFired;
}
private bool CanFireEvent(MediaPlayerEvent.EventType et, bool hasFired)
{
bool result = false;
if (m_events != null && m_Control != null && !hasFired && IsHandleEvent(et))
{
switch (et)
{
case MediaPlayerEvent.EventType.FinishedPlaying:
//Debug.Log(m_Control.GetCurrentTimeMs() + " " + m_Info.GetDurationMs());
result = (!m_Control.IsLooping() && m_Control.CanPlay() && m_Control.IsFinished())
#if UNITY_EDITOR_WIN || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_WSA))
|| (m_Control.GetCurrentTimeMs() > m_Info.GetDurationMs() && !m_Control.IsLooping())
#endif
;
break;
case MediaPlayerEvent.EventType.MetaDataReady:
result = (m_Control.HasMetaData());
break;
case MediaPlayerEvent.EventType.FirstFrameReady:
result = (m_Texture != null && m_Control.CanPlay() && m_Control.HasMetaData() && m_Texture.GetTextureFrameCount() > 0);
break;
case MediaPlayerEvent.EventType.ReadyToPlay:
result = (!m_Control.IsPlaying() && m_Control.CanPlay() && !m_AutoStart);
break;
case MediaPlayerEvent.EventType.Started:
result = (m_Control.IsPlaying());
break;
case MediaPlayerEvent.EventType.SubtitleChange:
result = (m_previousSubtitleIndex != m_Subtitles.GetSubtitleIndex());
break;
case MediaPlayerEvent.EventType.Stalled:
result = m_Info.IsPlaybackStalled();
break;
case MediaPlayerEvent.EventType.Unstalled:
result = !m_Info.IsPlaybackStalled();
break;
case MediaPlayerEvent.EventType.StartedSeeking:
result = m_Control.IsSeeking();
break;
case MediaPlayerEvent.EventType.FinishedSeeking:
result = !m_Control.IsSeeking();
break;
case MediaPlayerEvent.EventType.StartedBuffering:
result = m_Control.IsBuffering();
break;
case MediaPlayerEvent.EventType.FinishedBuffering:
result = !m_Control.IsBuffering();
break;
case MediaPlayerEvent.EventType.ResolutionChanged:
result = (m_Info != null && (m_EventState_PreviousWidth != m_Info.GetVideoWidth() || m_EventState_PreviousHeight != m_Info.GetVideoHeight()));
break;
default:
Debug.LogWarning("[AVProVideo] Unhandled event type");
break;
}
}
return result;
}
#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();
Helper.LogInfo("OnApplicationFocus: playing video again");
}
}
#endif
}
void OnApplicationPause(bool pauseStatus)
{
#if !(UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
// Debug.Log("OnApplicationPause: pauseStatus: " + pauseStatus);
if (pauseStatus)
{
if (_pauseMediaOnAppPause)
{
if (m_Control!= null && m_Control.IsPlaying())
{
m_WasPlayingOnPause = true;
#if !UNITY_IPHONE
m_Control.Pause();
#endif
Helper.LogInfo("OnApplicationPause: pausing video");
}
}
}
else
{
if (_playMediaOnAppUnpause)
{
// Catch coming back from power off state when no lock screen
OnApplicationFocus(true);
}
}
#endif
}
#endif
#endregion
#region Save Frame To PNG
#if UNITY_EDITOR || (!UNITY_EDITOR && (UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX))
[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
///
/// Create or return (if cached) a camera that is inactive and renders nothing
/// This camera is used to call .Render() on which causes the render thread to run
/// This is useful for forcing GL.IssuePluginEvent() to run and is used for
/// wait for frames to render for ExtractFrame() and UpdateTimeScale()
///
private static Camera GetDummyCamera()
{
if (m_DummyCamera == null)
{
const string goName = "AVPro Video Dummy Camera";
GameObject go = GameObject.Find(goName);
if (go == null)
{
go = new GameObject(goName);
go.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSave;
go.SetActive(false);
Object.DontDestroyOnLoad(go);
m_DummyCamera = go.AddComponent();
m_DummyCamera.hideFlags = HideFlags.HideInInspector | HideFlags.DontSave;
m_DummyCamera.cullingMask = 0;
m_DummyCamera.clearFlags = CameraClearFlags.Nothing;
m_DummyCamera.enabled = false;
}
else
{
m_DummyCamera = go.GetComponent();
}
}
//Debug.Assert(m_DummyCamera != null);
return m_DummyCamera;
}
private IEnumerator ExtractFrameCoroutine(Texture2D target, ProcessExtractedFrame callback, float timeSeconds = -1f, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
#if REAL_ANDROID || UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || UNITY_IOS || UNITY_TVOS
Texture2D result = target;
Texture frame = null;
if (m_Control != null)
{
if (timeSeconds >= 0f)
{
Pause();
float seekTimeMs = timeSeconds * 1000f;
// If the right frame is already available (or close enough) just grab it
if (TextureProducer.GetTexture() != null && (Mathf.Abs(m_Control.GetCurrentTimeMs() - seekTimeMs) < timeThresholdMs))
{
frame = TextureProducer.GetTexture();
}
else
{
int preSeekFrameCount = m_Texture.GetTextureFrameCount();
// Seek to the frame
if (accurateSeek)
{
m_Control.Seek(seekTimeMs);
}
else
{
m_Control.SeekFast(seekTimeMs);
}
// Wait for the new frame to arrive
if (!m_Control.WaitForNextFrame(GetDummyCamera(), preSeekFrameCount))
{
// If WaitForNextFrame fails (e.g. in android single threaded), we run the below code to asynchronously wait for the frame
int currFc = TextureProducer.GetTextureFrameCount();
int iterations = 0;
int maxIterations = 50;
//+1 as often there will be an extra frame produced after pause (so we need to wait for the second frame instead)
while ((currFc + 1) >= TextureProducer.GetTextureFrameCount() && iterations++ < maxIterations)
{
yield return null;
}
}
frame = TextureProducer.GetTexture();
}
}
else
{
frame = TextureProducer.GetTexture();
}
}
if (frame != null)
{
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), Helper.GetOrientation(Info.GetTextureTransform()), target);
}
#else
Texture2D result = ExtractFrame(target, timeSeconds, accurateSeek, timeoutMs, timeThresholdMs);
#endif
callback(result);
yield return null;
}
public void ExtractFrameAsync(Texture2D target, ProcessExtractedFrame callback, float timeSeconds = -1f, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
StartCoroutine(ExtractFrameCoroutine(target, callback, timeSeconds, accurateSeek, timeoutMs, timeThresholdMs));
}
// "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, int timeThresholdMs = 100)
{
Texture2D result = target;
// Extract frames returns the internal frame of the video player
Texture frame = ExtractFrame(timeSeconds, accurateSeek, timeoutMs, timeThresholdMs);
if (frame != null)
{
result = Helper.GetReadableTexture(frame, TextureProducer.RequiresVerticalFlip(), Helper.GetOrientation(Info.GetTextureTransform()), target);
}
return result;
}
private Texture ExtractFrame(float timeSeconds = -1f, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100)
{
Texture result = null;
if (m_Control != null)
{
if (timeSeconds >= 0f)
{
Pause();
float seekTimeMs = timeSeconds * 1000f;
// If the right frame is already available (or close enough) just grab it
if (TextureProducer.GetTexture() != null && (Mathf.Abs(m_Control.GetCurrentTimeMs() - seekTimeMs) < timeThresholdMs))
{
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
#if UNITY_EDITOR && UNITY_5_4_OR_NEWER
#region Audio Mute Support for Unity Editor
private bool _unityAudioMasterMute = false;
private void CheckEditorAudioMute()
{
// Detect a change
if (UnityEditor.EditorUtility.audioMasterMute != _unityAudioMasterMute)
{
_unityAudioMasterMute = UnityEditor.EditorUtility.audioMasterMute;
if (m_Control != null)
{
m_Control.MuteAudio(m_Muted || _unityAudioMasterMute);
}
}
}
#endregion
#endif
#region Play/Pause Support for Unity Editor
// This code handles the pause/play buttons in the editor
#if UNITY_EDITOR
static MediaPlayer()
{
#if UNITY_2017_2_OR_NEWER
UnityEditor.EditorApplication.pauseStateChanged -= OnUnityPauseModeChanged;
UnityEditor.EditorApplication.pauseStateChanged += OnUnityPauseModeChanged;
#else
UnityEditor.EditorApplication.playmodeStateChanged -= OnUnityPlayModeChanged;
UnityEditor.EditorApplication.playmodeStateChanged += OnUnityPlayModeChanged;
#endif
}
#if UNITY_2017_2_OR_NEWER
private static void OnUnityPauseModeChanged(UnityEditor.PauseState state)
{
OnUnityPlayModeChanged();
}
#endif
private static void OnUnityPlayModeChanged()
{
if (UnityEditor.EditorApplication.isPlaying)
{
if (UnityEditor.EditorApplication.isPaused)
{
MediaPlayer[] players = Resources.FindObjectsOfTypeAll();
foreach (MediaPlayer player in players)
{
player.EditorPause();
}
}
else
{
MediaPlayer[] players = Resources.FindObjectsOfTypeAll();
foreach (MediaPlayer player in players)
{
player.EditorUnpause();
}
}
}
}
private void EditorPause()
{
if (this.isActiveAndEnabled)
{
if (m_Control != null && m_Control.IsPlaying())
{
m_WasPlayingOnPause = true;
m_Control.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
}
}