//#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 #define AVPROVIDEO_SUPPORT_LIVEEDITMODE using UnityEngine; using UnityEngine.Serialization; using System.Collections; using System.Collections.Generic; //----------------------------------------------------------------------------- // Copyright 2015-2022 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. /// #if AVPROVIDEO_SUPPORT_LIVEEDITMODE [ExecuteInEditMode] #endif [AddComponentMenu("AVPro Video/Media Player", -100)] [HelpURL("https://www.renderheads.com/products/avpro-video/")] public partial 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) // Media source [SerializeField] MediaSource _mediaSource = MediaSource.Reference; public MediaSource MediaSource { get { return _mediaSource; } internal set { _mediaSource = value; } } [SerializeField] MediaReference _mediaReference = null; public MediaReference MediaReference { get { return _mediaReference; } internal set { _mediaReference = value; } } [SerializeField] MediaPath _mediaPath = new MediaPath(); public MediaPath MediaPath { get { return _mediaPath; } internal set { _mediaPath = value; } } [SerializeField] MediaHints _fallbackMediaHints = MediaHints.Default; public MediaHints FallbackMediaHints { get { return _fallbackMediaHints; } set { _fallbackMediaHints = value; } } [FormerlySerializedAs("m_AutoOpen")] [SerializeField] bool _autoOpen = true; public bool AutoOpen { get { return _autoOpen; } set { _autoOpen = value; } } [FormerlySerializedAs("m_AutoStart")] [SerializeField] bool _autoPlayOnStart = true; public bool AutoStart { get { return _autoPlayOnStart; } set { _autoPlayOnStart = value; } } // Basic controls [FormerlySerializedAs("m_Loop")] [SerializeField] bool _loop = false; public bool Loop { get { return (_controlInterface != null) ? _controlInterface.IsLooping() : _loop; } set { _loop = value; if (_controlInterface != null) _controlInterface.SetLooping(_loop); } } [FormerlySerializedAs("m_Volume")] [Range(0.0f, 1.0f)] [SerializeField] float _audioVolume = 1.0f; public virtual float AudioVolume { get { return (_controlInterface != null) ? _controlInterface.GetVolume() : _audioVolume; } set { _audioVolume = Mathf.Clamp01(value); if (_controlInterface != null) _controlInterface.SetVolume(_audioVolume); } } [FormerlySerializedAs("m_Balance")] [Range(-1.0f, 1.0f)] [SerializeField] float _audioBalance = 0.0f; public float AudioBalance { get { return (_controlInterface != null) ? _controlInterface.GetBalance() : _audioBalance; } set { _audioBalance = Mathf.Clamp(value, -1f, 1f); if (_controlInterface != null) _controlInterface.SetBalance(_audioBalance); } } [FormerlySerializedAs("m_Muted")] [SerializeField] bool _audioMuted = false; public virtual bool AudioMuted { get { return (_controlInterface != null) ? _controlInterface.IsMuted() : _audioMuted; } set { _audioMuted = value; if (_controlInterface != null) { #if !UNITY_EDITOR _controlInterface.MuteAudio(_audioMuted); #else _controlInterface.MuteAudio(_audioMuted || UnityEditor.EditorUtility.audioMasterMute); #endif } } } private AudioSource _audioSource = null; public AudioSource AudioSource { get { return _audioSource; } internal set { _audioSource = value; } } [FormerlySerializedAs("m_PlaybackRate")] [Range(-4.0f, 4.0f)] [SerializeField] float _playbackRate = 1.0f; public float PlaybackRate { get { return (_controlInterface != null) ? _controlInterface.GetPlaybackRate() : _playbackRate; } set { _playbackRate = value; if (_controlInterface != null) _controlInterface.SetPlaybackRate(_playbackRate); } } // Resampler [FormerlySerializedAs("m_Resample")] [SerializeField] bool _useResampler = false; public bool UseResampler { get { return _useResampler; } set { _useResampler = value; } } [FormerlySerializedAs("m_ResampleMode")] [SerializeField] Resampler.ResampleMode _resampleMode = Resampler.ResampleMode.POINT; public Resampler.ResampleMode ResampleMode { get { return _resampleMode; } set { _resampleMode = value; } } [FormerlySerializedAs("m_ResampleBufferSize")] [Range(3, 10)] [SerializeField] int _resampleBufferSize = 5; public int ResampleBufferSize { get { return _resampleBufferSize; } set { _resampleBufferSize = value; } } private Resampler _resampler = null; public Resampler FrameResampler { get { return _resampler; } } // Visual options [FormerlySerializedAs("m_videoMapping")] [SerializeField] VideoMapping _videoMapping = VideoMapping.Unknown; public VideoMapping VideoLayoutMapping { get { return _videoMapping; } set { _videoMapping = value; } } [FormerlySerializedAs("m_FilterMode")] [SerializeField] FilterMode _textureFilterMode = FilterMode.Bilinear; public FilterMode TextureFilterMode { get { if (_controlInterface != null) { FilterMode filterMode = FilterMode.Point; TextureWrapMode textureWrapMode = TextureWrapMode.Repeat; int anisoLevel = 0; _controlInterface.GetTextureProperties(out filterMode, out textureWrapMode, out anisoLevel); return filterMode; } else return _textureFilterMode; } set { _textureFilterMode = value; if (_controlInterface != null) _controlInterface.SetTextureProperties(_textureFilterMode, _textureWrapMode, _textureAnisoLevel); } } [FormerlySerializedAs("m_WrapMode")] [SerializeField] TextureWrapMode _textureWrapMode = TextureWrapMode.Clamp; public TextureWrapMode TextureWrapMode { get { if (_controlInterface != null) { FilterMode filterMode = FilterMode.Point; TextureWrapMode textureWrapMode = TextureWrapMode.Repeat; int anisoLevel = 0; _controlInterface.GetTextureProperties(out filterMode, out textureWrapMode, out anisoLevel); return textureWrapMode; } else return _textureWrapMode; } set { _textureWrapMode = value; if (_controlInterface != null) _controlInterface.SetTextureProperties(_textureFilterMode, _textureWrapMode, _textureAnisoLevel); } } [FormerlySerializedAs("m_AnisoLevel")] [Range(0, 16)] [SerializeField] int _textureAnisoLevel = 0; public int TextureAnisoLevel { get { if (_controlInterface != null) { FilterMode filterMode = FilterMode.Point; TextureWrapMode textureWrapMode = TextureWrapMode.Repeat; int anisoLevel = 0; _controlInterface.GetTextureProperties(out filterMode, out textureWrapMode, out anisoLevel); return anisoLevel; } else return _textureAnisoLevel; } set { _textureAnisoLevel = value; if (_controlInterface != null) _controlInterface.SetTextureProperties(_textureFilterMode, _textureWrapMode, _textureAnisoLevel); } } #if AVPRO_FEATURE_VIDEORESOLVE [SerializeField] bool _useVideoResolve = false; public bool UseVideoResolve { get { return _useVideoResolve; } set { _useVideoResolve = value; } } [SerializeField] VideoResolveOptions _videoResolveOptions = VideoResolveOptions.Create(); public VideoResolveOptions VideoResolveOptions { get { return _videoResolveOptions; } set { _videoResolveOptions = value; } } [SerializeField] VideoResolve _videoResolve = new VideoResolve(); #endif // Sideloaded subtitles [FormerlySerializedAs("m_LoadSubtitles")] [SerializeField] bool _sideloadSubtitles; public bool SideloadSubtitles { get { return _sideloadSubtitles; } set { _sideloadSubtitles = value; } } [SerializeField] MediaPath _subtitlePath; public MediaPath SubtitlePath { get { return _subtitlePath; } set { _subtitlePath = value; } } // Audio 360 [FormerlySerializedAs("m_AudioHeadTransform")] [SerializeField] Transform _audioHeadTransform; public Transform AudioHeadTransform { set { _audioHeadTransform = value; } get { return _audioHeadTransform; } } [FormerlySerializedAs("m_AudioFocusEnabled")] [SerializeField] bool _audioFocusEnabled; public bool AudioFocusEnabled { get { return _audioFocusEnabled; } set { _audioFocusEnabled = value; } } [FormerlySerializedAs("m_AudioFocusTransform")] [SerializeField] Transform _audioFocusTransform; public Transform AudioFocusTransform { get { return _audioFocusTransform; } set { _audioFocusTransform = value; } } [FormerlySerializedAs("m_AudioFocusWidthDegrees")] [SerializeField, Range(40f, 120f)] float _audioFocusWidthDegrees = 90f; public float AudioFocusWidthDegrees { get { return _audioFocusWidthDegrees; } set { _audioFocusWidthDegrees = value; } } [FormerlySerializedAs("m_AudioFocusOffLevelDB")] [SerializeField, Range(-24f, 0f)] float _audioFocusOffLevelDB = 0f; public float AudioFocusOffLevelDB { get { return _audioFocusOffLevelDB; } set { _audioFocusOffLevelDB = value; } } // Network [SerializeField] HttpHeaderData _httpHeaders = new HttpHeaderData(); public HttpHeaderData HttpHeaders { get { return _httpHeaders; } set { _httpHeaders = value; } } [SerializeField] KeyAuthData _keyAuth = new KeyAuthData(); public KeyAuthData KeyAuth { get { return _keyAuth; } set { _keyAuth = value; } } // Events [FormerlySerializedAs("m_events")] [SerializeField] MediaPlayerEvent _events = null; public MediaPlayerEvent Events { get { if (_events == null) { _events = new MediaPlayerEvent(); } return _events; } } [FormerlySerializedAs("m_eventMask")] [SerializeField] int _eventMask = -1; public int EventMask { get { return _eventMask; } set { _eventMask = value; } } [SerializeField] bool _pauseMediaOnAppPause = true; public bool PauseMediaOnAppPause { get { return _pauseMediaOnAppPause; } set { _pauseMediaOnAppPause = value; } } [SerializeField] bool _playMediaOnAppUnpause = true; public bool PlayMediaOnAppUnpause { get { return _playMediaOnAppUnpause; } set { _playMediaOnAppUnpause = value; } } // Misc options [FormerlySerializedAs("m_Persistent")] [SerializeField] bool _persistent = false; public bool Persistent { get { return _persistent; } set { _persistent = value; } } [FormerlySerializedAs("m_forceFileFormat")] [SerializeField] FileFormat _forceFileFormat = FileFormat.Unknown; public FileFormat ForceFileFormat { get { return _forceFileFormat; } set { _forceFileFormat = value; } } // Interfaces private BaseMediaPlayer _baseMediaPlayer; private IMediaControl _controlInterface; private ITextureProducer _textureInterface; private IMediaInfo _infoInterface; private IMediaPlayer _playerInterface; private IMediaSubtitles _subtitlesInterface; private IMediaCache _cacheInterface; private IBufferedDisplay _bufferedDisplayInterface; private IVideoTracks _videoTracksInterface; private IAudioTracks _audioTracksInterface; private ITextTracks _textTracksInterface; private System.IDisposable _disposeInterface; public virtual IMediaInfo Info { get { return _infoInterface; } } public virtual IMediaControl Control { get { return _controlInterface; } } public virtual IMediaPlayer Player { get { return _playerInterface; } } public virtual ITextureProducer TextureProducer { get { return _textureInterface; } } public virtual IMediaSubtitles Subtitles { get { return _subtitlesInterface; } } public virtual IVideoTracks VideoTracks { get { return _videoTracksInterface; } } public virtual IAudioTracks AudioTracks { get { return _audioTracksInterface; } } public virtual ITextTracks TextTracks { get { return _textTracksInterface; } } public virtual IMediaCache Cache { get { return _cacheInterface; } } public virtual IBufferedDisplay BufferedDisplay { get { return _bufferedDisplayInterface; } } // State private bool _isMediaOpened = false; public bool MediaOpened { get { return _isMediaOpened; } } private bool _autoPlayOnStartTriggered = false; private bool _wasPlayingOnPause = false; private Coroutine _renderingCoroutine = null; // Global init private static bool s_GlobalStartup = false; private static bool s_TrialVersion = false; // Subtitle state private MediaPath _queueSubtitlePath; private Coroutine _loadSubtitlesRoutine; // Extract frame private static Camera _dummyCamera = null; public delegate void ProcessExtractedFrame(Texture2D extractedFrame); /// /// Methods /// #if UNITY_EDITOR static MediaPlayer() { SetupEditorPlayPauseSupport(); } #endif void Awake() { if (_persistent) { // TODO: set "this.transform.root.gameObject" to also DontDestroyOnLoad? DontDestroyOnLoad(this.gameObject); } } protected void Initialise() { BaseMediaPlayer mediaPlayer = CreateMediaPlayer(); if (mediaPlayer != null) { // Set-up interface _baseMediaPlayer = mediaPlayer; _controlInterface = mediaPlayer; _textureInterface = mediaPlayer; _infoInterface = mediaPlayer; _playerInterface = mediaPlayer; _subtitlesInterface = mediaPlayer; _videoTracksInterface = mediaPlayer; _audioTracksInterface = mediaPlayer; _textTracksInterface = mediaPlayer; _disposeInterface = mediaPlayer; _cacheInterface = mediaPlayer; _bufferedDisplayInterface = mediaPlayer; string nativePluginVersion = mediaPlayer.GetVersion(); string expectedNativePluginVersion = mediaPlayer.GetExpectedVersion(); // Check that the plugin version number is not too old if (!nativePluginVersion.StartsWith(expectedNativePluginVersion)) { Debug.LogError("[AVProVideo] Plugin version number " + nativePluginVersion + " doesn't match the expected version number " + expectedNativePluginVersion + ". It looks like the plugin didn't upgrade correctly. To resolve this please restart Unity and try to upgrade the package again."); } s_TrialVersion = nativePluginVersion.Contains("-trial"); if (!s_GlobalStartup) { Helper.LogInfo(string.Format("Initialising AVPro Video v{0} (native plugin v{1}) on {2}/{3} (MT {4}) on {5}", Helper.AVProVideoVersion, nativePluginVersion, SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceVersion, SystemInfo.graphicsMultiThreaded, Application.platform)); #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 UNITY_WEBGL _useResampler = false; #endif if (_controlInterface == null) { if (Application.isPlaying) { Initialise(); if (_controlInterface != null) { if (_autoOpen) { OpenMedia(_autoPlayOnStart); if (_sideloadSubtitles && _subtitlesInterface != null && _subtitlePath != null && !string.IsNullOrEmpty(_subtitlePath.Path)) { EnableSubtitles(_subtitlePath); } } StartRenderCoroutine(); } } } } public bool OpenMedia(MediaPath path, bool autoPlay = true) { return OpenMedia(path.PathType, path.Path, autoPlay); } public bool OpenMedia(MediaPathType pathType, string path, bool autoPlay = true) { _mediaSource = MediaSource.Path; _mediaPath.Path = path; _mediaPath.PathType = pathType; return OpenMedia(autoPlay); } public bool OpenMedia(MediaReference mediaReference, bool autoPlay = true) { _mediaSource = MediaSource.Reference; _mediaReference = mediaReference; return OpenMedia(autoPlay); } public bool OpenMedia(bool autoPlay = true) { _autoPlayOnStart = autoPlay; if (_controlInterface == null) { //_autoOpen = false; // If OpenVideoFromFile() is called before Start() then set _autoOpen to false so that it doesn't load the video a second time during Start() Initialise(); } return InternalOpenMedia(); } private bool InternalOpenMedia() { bool result = false; // Open the video file if (_controlInterface != null) { CloseMedia(); _isMediaOpened = true; _autoPlayOnStartTriggered = !_autoPlayOnStart; _finishedFrameOpenCheck = true; long fileOffset = GetPlatformFileOffset(); // TODO: replace this with MediaReference MediaPath mediaPath = null; MediaHints mediaHints = _fallbackMediaHints; if (_mediaSource == MediaSource.Reference) { if (_mediaReference != null) { mediaPath = _mediaReference.GetCurrentPlatformMediaReference().MediaPath; mediaHints = _mediaReference.GetCurrentPlatformMediaReference().Hints; if (string.IsNullOrEmpty(mediaPath.Path)) { mediaPath = null; } } else { Debug.LogError("[AVProVideo] No MediaReference specified", this); } } else if (_mediaSource == MediaSource.Path) { if (!string.IsNullOrEmpty(_mediaPath.Path)) { mediaPath = _mediaPath; } else { Debug.LogError("[AVProVideo] No file path specified", this); } } if (null != mediaPath) { string fullPath = mediaPath.GetResolvedFullPath(); string customHttpHeaders = null; bool checkForFileExist = true; bool isURL = fullPath.Contains("://"); if (isURL) { checkForFileExist = false; customHttpHeaders = GetPlatformHttpHeadersAsString(); } #if (!UNITY_EDITOR && UNITY_ANDROID) 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) // NOTE: We don't need to call SetAudioChannelMode on Android, // as it's set when the AndroidMediaPlayer object is created if (_optionsWindows._audioMode == Windows.AudioOutput.FacebookAudio360) { _controlInterface.SetAudioChannelMode(_optionsWindows.audio360ChannelMode); } else { _controlInterface.SetAudioChannelMode(Audio360ChannelMode.INVALID); } #elif (!UNITY_EDITOR && UNITY_WSA_10_0) if (_optionsWindowsUWP._audioMode == WindowsUWP.AudioOutput.FacebookAudio360) { _controlInterface.SetAudioChannelMode(_optionsWindowsUWP.audio360ChannelMode); } else { _controlInterface.SetAudioChannelMode(Audio360ChannelMode.INVALID); } #endif PlatformOptions options = GetCurrentPlatformOptions(); bool startWithHighestBitrate = false; if (options != null) { startWithHighestBitrate = options.StartWithHighestBandwidth(); } SetLoadOptions(); SetPlaybackOptions(); if (_controlInterface.OpenMedia(fullPath, fileOffset, customHttpHeaders, mediaHints, (int)_forceFileFormat, startWithHighestBitrate)) { StartRenderCoroutine(); result = true; } else { Debug.LogError("[AVProVideo] Failed to open " + fullPath, this); } } } else { Debug.LogError("[AVProVideo] No file path specified", this); } } return result; } private void SetLoadOptions() { // On some platforms we can update the loading options without having to recreate the player #if !AVPROVIDEO_FORCE_NULL_MEDIAPLAYER #if (UNITY_EDITOR_OSX && UNITY_IOS) || (!UNITY_EDITOR && UNITY_IOS) #elif (UNITY_EDITOR_OSX && UNITY_TVOS) || (!UNITY_EDITOR && UNITY_TVOS) #elif (UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX)) #elif (UNITY_EDITOR_WIN) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN) #elif (!UNITY_EDITOR && UNITY_WSA_10_0) #elif (!UNITY_EDITOR && UNITY_ANDROID) #elif (!UNITY_EDITOR && UNITY_WEBGL) ((WebGLMediaPlayer)_baseMediaPlayer).SetOptions(_optionsWebGL); #endif #endif // Encryption support PlatformOptions options = GetCurrentPlatformOptions(); if (options != null) { _controlInterface.SetKeyServerAuthToken(options.GetKeyServerAuthToken()); //_controlInterface.SetKeyServerURL(options.GetKeyServerURL()); _controlInterface.SetOverrideDecryptionKey(options.GetOverrideDecryptionKey()); } } private void SetPlaybackOptions() { // Set playback options if (_controlInterface != null) { _controlInterface.SetLooping(_loop); _controlInterface.SetPlaybackRate(_playbackRate); _controlInterface.SetVolume(_audioVolume); _controlInterface.SetBalance(_audioBalance); #if !UNITY_EDITOR _controlInterface.MuteAudio(_audioMuted); #else _controlInterface.MuteAudio(_audioMuted || UnityEditor.EditorUtility.audioMasterMute); #endif _controlInterface.SetTextureProperties(_textureFilterMode, _textureWrapMode, _textureAnisoLevel); } } public void CloseMedia() { // Close the media file if (_controlInterface != null) { if (_events != null && _isMediaOpened && _events.HasListeners() && IsHandleEvent(MediaPlayerEvent.EventType.Closing)) { _events.Invoke(this, MediaPlayerEvent.EventType.Closing, ErrorCode.None); } _autoPlayOnStartTriggered = false; _isMediaOpened = false; ResetEvents(); if (_loadSubtitlesRoutine != null) { StopCoroutine(_loadSubtitlesRoutine); _loadSubtitlesRoutine = null; } _controlInterface.CloseMedia(); } if (_resampler != null) { _resampler.Reset(); } StopRenderCoroutine(); } public void RewindPrerollPause() { PlatformOptionsWindows.pauseOnPrerollComplete = true; if (BufferedDisplay != null) { BufferedDisplay.SetBufferedDisplayOptions(true); } Rewind(false); Play(); } public virtual void Play() { if (_controlInterface != null && _controlInterface.CanPlay()) { _controlInterface.Play(); // Mark this event as done because it's irrelevant once playback starts _eventFired_ReadyToPlay = true; } else { // Can't play, perhaps it's still loading? Queuing play using _autoPlayOnStart to play after loading _autoPlayOnStart = true; _autoPlayOnStartTriggered = false; } } public virtual void Pause() { if (_controlInterface != null && _controlInterface.IsPlaying()) { _controlInterface.Pause(); } _wasPlayingOnPause = false; #if AVPROVIDEO_BETA_SUPPORT_TIMESCALE _timeScaleIsControlling = false; #endif } public void Stop() { if (_controlInterface != null) { _controlInterface.Stop(); } #if AVPROVIDEO_BETA_SUPPORT_TIMESCALE _timeScaleIsControlling = false; #endif } public void Rewind(bool pause) { if (_controlInterface != null) { if (pause) { Pause(); } _controlInterface.Rewind(); } } public void SeekToLiveTime(double offset = 0.0) { if (_controlInterface != null) { double liveTime = _controlInterface.GetBufferedTimes().MaxTime; if (liveTime > 0.0) { _controlInterface.Seek(liveTime - offset); } } } #if UNITY_EDITOR && AVPROVIDEO_SUPPORT_LIVEEDITMODE public bool EditorUpdate() { if (_playerInterface != null) { Update(); _playerInterface.BeginRender(); _playerInterface.Render(); return true; } return false; } #endif protected virtual void Update() { if (_controlInterface != null) { // Auto start the playback if (_isMediaOpened && _autoPlayOnStart && !_autoPlayOnStartTriggered && _controlInterface.CanPlay()) { _autoPlayOnStartTriggered = true; Play(); } if (Application.isPlaying) { if (_renderingCoroutine == null && _controlInterface.CanPlay()) { StartRenderCoroutine(); } } if (_subtitlesInterface != null && _queueSubtitlePath != null && !string.IsNullOrEmpty(_queueSubtitlePath.Path)) { EnableSubtitles(_queueSubtitlePath); _queueSubtitlePath = null; } #if AVPROVIDEO_BETA_SUPPORT_TIMESCALE UpdateTimeScale(); #endif UpdateAudioHeadTransform(); UpdateAudioFocus(); _playerInterface.Update(); // Render (done in co-routine) //_playerInterface.Render(); UpdateErrors(); UpdateEvents(); _playerInterface.EndUpdate(); } #if UNITY_EDITOR CheckEditorAudioMute(); #endif } private void LateUpdate() { UpdateResampler(); // RJT TODO: Better location? if (_playerInterface != null) { _playerInterface.BeginRender(); } } private void UpdateResampler() { #if !UNITY_WEBGL if (_useResampler) { if (_resampler == null) { _resampler = new Resampler(this, gameObject.name, _resampleBufferSize, _resampleMode); } } #else _useResampler = false; #endif if (_resampler != null) { _resampler.Update(); _resampler.UpdateTimestamp(); } } void OnEnable() { if (_controlInterface != null && _wasPlayingOnPause) { _autoPlayOnStart = true; _autoPlayOnStartTriggered = false; _wasPlayingOnPause = false; } if(_playerInterface != null) { _playerInterface.OnEnable(); StartRenderCoroutine(); } } void OnDisable() { if (_controlInterface != null) { if (_controlInterface.IsPlaying()) { Pause(); // Force an update to ensure the player state is synchronised with the plugin Update(); // Needs to follow Pause() otherwise it will be reset. _wasPlayingOnPause = true; } } StopRenderCoroutine(); } protected virtual void OnDestroy() { CloseMedia(); _baseMediaPlayer = null; _controlInterface = null; _textureInterface = null; _infoInterface = null; _playerInterface = null; _subtitlesInterface = null; _cacheInterface = null; _bufferedDisplayInterface = null; _videoTracksInterface = null; _audioTracksInterface = null; _textTracksInterface = null; if (_disposeInterface != null) { _disposeInterface.Dispose(); _disposeInterface = null; } if (_resampler != null) { _resampler.Release(); _resampler = null; } // TODO: possible bug if MediaPlayers are created and destroyed manually (instantiated), OnApplicationQuit won't be called! } public void ForceDispose() { OnDisable(); OnDestroy(); } #if UNITY_EDITOR public static void EditorAllPlayersDispose() { AllPlayersDispose(); } #endif private static void AllPlayersDispose() { // 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].ForceDispose(); } } } void OnApplicationQuit() { if (s_GlobalStartup) { Helper.LogInfo("Shutdown"); AllPlayersDispose(); #if UNITY_EDITOR #if UNITY_EDITOR_WIN WindowsMediaPlayer.DeinitPlatform(); WindowsRtMediaPlayer.DeinitPlatform(); #endif #else #if (UNITY_STANDALONE_WIN) WindowsMediaPlayer.DeinitPlatform(); WindowsRtMediaPlayer.DeinitPlatform(); #elif (UNITY_ANDROID) #if false AndroidMediaPlayer.DeinitPlatform(); #endif #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 (_playerInterface != null) { _playerInterface.Render(); } } } } #endregion // Rendering Coroutine #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.macOS; #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.macOS; #elif (UNITY_IPHONE || UNITY_IOS) result = Platform.iOS; #elif (UNITY_TVOS) result = Platform.tvOS; #elif (UNITY_VISIONOS) result = Platform.visionOS; #elif (UNITY_ANDROID) result = Platform.Android; #elif (UNITY_WSA_10_0) result = Platform.WindowsUWP; #elif (UNITY_WEBGL) result = Platform.WebGL; #endif #endif return result; } public PlatformOptions GetCurrentPlatformOptions() { PlatformOptions result = null; #if UNITY_EDITOR #if (UNITY_EDITOR_OSX && UNITY_EDITOR_64) result = _options_macOS; #elif UNITY_EDITOR_WIN result = _optionsWindows; #endif #else // Setup for running builds #if (UNITY_STANDALONE_WIN) result = _optionsWindows; #elif (UNITY_STANDALONE_OSX) result = _options_macOS; #elif (UNITY_IPHONE || UNITY_IOS) result = _options_iOS; #elif (UNITY_TVOS) result = _options_tvOS; #elif (UNITY_VISIONOS) result = _options_visionOS; #elif (UNITY_ANDROID) result = _optionsAndroid; #elif (UNITY_WSA_10_0) result = _optionsWindowsUWP; #elif (UNITY_WEBGL) result = _optionsWebGL; #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.macOS: result = _options_macOS; break; case Platform.Android: result = _optionsAndroid; break; case Platform.iOS: result = _options_iOS; break; case Platform.tvOS: result = _options_tvOS; break; case Platform.visionOS: result = _options_visionOS; break; case Platform.WindowsUWP: result = _optionsWindowsUWP; break; case Platform.WebGL: result = _optionsWebGL; break; } return result; } public static string GetPlatformOptionsVariable(Platform platform) { string result = string.Empty; switch (platform) { case Platform.Windows: result = "_optionsWindows"; break; case Platform.macOS: result = "_options_macOS"; break; case Platform.iOS: result = "_options_iOS"; break; case Platform.tvOS: result = "_options_tvOS"; break; case Platform.visionOS: result = "_options_visionOS"; break; case Platform.Android: result = "_optionsAndroid"; break; case Platform.WindowsUWP: result = "_optionsWindowsUWP"; break; case Platform.WebGL: result = "_optionsWebGL"; break; } return result; } #endif 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_WSA_10_0 result = _optionsWindowsUWP.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 GetPlatformHttpHeadersAsString() { string result = null; #if UNITY_EDITOR #if UNITY_EDITOR_OSX result = _options_macOS.httpHeaders.ToValidatedString(); #elif UNITY_EDITOR_WIN result = _optionsWindows.httpHeaders.ToValidatedString(); #elif UNITY_EDITOR_LINUX #endif #else #if UNITY_STANDALONE_OSX result = _options_macOS.httpHeaders.ToValidatedString(); #elif UNITY_STANDALONE_WIN result = _optionsWindows.httpHeaders.ToValidatedString(); #elif UNITY_WSA_10_0 result = _optionsWindowsUWP.httpHeaders.ToValidatedString(); #elif UNITY_IOS || UNITY_IPHONE result = _options_iOS.httpHeaders.ToValidatedString(); #elif UNITY_TVOS result = _options_tvOS.httpHeaders.ToValidatedString(); #elif UNITY_VISIONOS result = _options_visionOS.httpHeaders.ToValidatedString(); #elif UNITY_ANDROID result = _optionsAndroid.httpHeaders.ToValidatedString(); #elif UNITY_WEBGL #endif #endif if (!string.IsNullOrEmpty(result)) { result = result.Trim(); } string globalHeaders = _httpHeaders.ToValidatedString(); if (!string.IsNullOrEmpty(globalHeaders)) { result += globalHeaders; result = result.Trim(); } return result; } private string GetResolvedFilePath(string filePath, MediaPathType fileLocation) { string result = string.Empty; result = Helper.GetFilePath(filePath, fileLocation); #if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)) if (result.Length > 200 && !result.Contains("://")) { result = Helper.ConvertLongPathToShortDOS83Path(result); } #endif return result; } #endregion // Platform and Path #region Create MediaPlayers #if (UNITY_EDITOR_WIN) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN) private static BaseMediaPlayer CreateMediaPlayer(OptionsWindows options) { BaseMediaPlayer result = null; if (options.videoApi == Windows.VideoApi.WinRT) { if (WindowsRtMediaPlayer.InitialisePlatform()) { result = new WindowsRtMediaPlayer(options); } else { Debug.LogWarning(string.Format("[AVProVideo] Failed to initialise WinRT API - platform {0} may not support it. Trying another video API...", SystemInfo.operatingSystem)); } } if (result == null) { if (WindowsMediaPlayer.InitialisePlatform()) { result = new WindowsMediaPlayer(options); } } return result; } #endif #if (!UNITY_EDITOR && UNITY_WSA_10_0) private static BaseMediaPlayer CreateMediaPlayer(OptionsWindowsUWP options) { BaseMediaPlayer result = null; if (options.videoApi == WindowsUWP.VideoApi.WinRT) { if (WindowsRtMediaPlayer.InitialisePlatform()) { result = new WindowsRtMediaPlayer(options); } else { Debug.LogWarning(string.Format("[AVProVideo] Failed to initialise WinRT API - platform {0} may not support it. Trying another video API...", SystemInfo.operatingSystem)); } } if (result == null) { if (WindowsMediaPlayer.InitialisePlatform()) { result = new WindowsMediaPlayer(options); } } return result; } #endif #if (!UNITY_EDITOR && UNITY_ANDROID) private static BaseMediaPlayer CreateMediaPlayer(OptionsAndroid options) { PlatformMediaPlayer mediaPlayer = new PlatformMediaPlayer(options); return mediaPlayer; } #endif #if (UNITY_EDITOR_OSX) || (!UNITY_EDITOR && (UNITY_STANDALONE_OSX || UNITY_IPHONE || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS || UNITY_ANDROID)) private static BaseMediaPlayer CreateMediaPlayer(OptionsApple options) { PlatformMediaPlayer mediaPlayer = new PlatformMediaPlayer(options); return mediaPlayer; } #endif #if (!UNITY_EDITOR && UNITY_WEBGL) private static BaseMediaPlayer CreateMediaPlayer(OptionsWebGL options) { BaseMediaPlayer result = null; if (WebGLMediaPlayer.InitialisePlatform()) { result = new WebGLMediaPlayer(options); } return result; } #endif private static BaseMediaPlayer CreateMediaPlayerNull() { return new NullMediaPlayer(); } public virtual BaseMediaPlayer CreateMediaPlayer() { BaseMediaPlayer mediaPlayer = null; #if !AVPROVIDEO_FORCE_NULL_MEDIAPLAYER #if (UNITY_EDITOR_OSX && UNITY_IOS) || (!UNITY_EDITOR && UNITY_IOS) mediaPlayer = CreateMediaPlayer(_options_iOS); #elif (UNITY_EDITOR_OSX && UNITY_TVOS) || (!UNITY_EDITOR && UNITY_TVOS) mediaPlayer = CreateMediaPlayer(_options_tvOS); #elif (UNITY_EDITOR_OSX && UNITY_VISIONOS) || (!UNITY_EDITOR && UNITY_VISIONOS) mediaPlayer = CreateMediaPlayer(_options_visionOS); #elif (UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX)) mediaPlayer = CreateMediaPlayer(_options_macOS); #elif (UNITY_EDITOR_WIN) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN) mediaPlayer = CreateMediaPlayer(_optionsWindows); #elif (!UNITY_EDITOR && UNITY_WSA_10_0) mediaPlayer = CreateMediaPlayer(_optionsWindowsUWP); #elif (!UNITY_EDITOR && UNITY_ANDROID) mediaPlayer = CreateMediaPlayer(_optionsAndroid); #elif (!UNITY_EDITOR && UNITY_WEBGL) mediaPlayer = CreateMediaPlayer(_optionsWebGL); #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 = CreateMediaPlayerNull(); } return mediaPlayer; } #endregion // Create MediaPlayers private void UpdateAudioFocus() { // TODO: we could use gizmos to draw the focus area _controlInterface.SetAudioFocusEnabled(_audioFocusEnabled); _controlInterface.SetAudioFocusProperties(_audioFocusOffLevelDB, _audioFocusWidthDegrees); _controlInterface.SetAudioFocusRotation(_audioFocusTransform == null ? Quaternion.identity : _audioFocusTransform.rotation); } private void UpdateAudioHeadTransform() { if (_audioHeadTransform != null) { _controlInterface.SetAudioHeadRotation(_audioHeadTransform.rotation); } else { _controlInterface.ResetAudioHeadRotation(); } } private void UpdateErrors() { ErrorCode errorCode = _controlInterface.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 // TODO: Update this to handle case where media is MediaReference if (_mediaPath.Path.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 (_events != null && _events.HasListeners() && IsHandleEvent(MediaPlayerEvent.EventType.Error)) { _events.Invoke(this, MediaPlayerEvent.EventType.Error, errorCode); } } } public bool IsUsingAndroidOESPath() { // Android OES mode is not available in the trial #if !UNITY_EDITOR && UNITY_ANDROID return !s_TrialVersion && (PlatformOptionsAndroid.textureFormat == MediaPlayer.OptionsAndroid.TextureFormat.YCbCr420_OES); #else return false; #endif } #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) { string timecode = Mathf.FloorToInt((float)(Control.GetCurrentTime() * 1000.0)).ToString("D8"); System.IO.File.WriteAllBytes("frame-" + timecode + ".png", imageBytes); } Destroy(frame); } } [ContextMenu("Save Frame To EXR")] public void SaveFrameToExr() { Texture frame = (Texture)TextureProducer.GetTexture(0); if (frame != null) { RenderTexture rt = new RenderTexture(frame.width, frame.height, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear); rt.Create(); Graphics.Blit(frame, rt); Texture2D frameRead = new Texture2D(frame.width, frame.height, TextureFormat.RGBAFloat, false, true); RenderTexture.active = rt; frameRead.ReadPixels(new Rect(0f, 0f, frame.width, frame.height), 0, 0, false); frameRead.Apply(false, false); RenderTexture.active = null; byte[] imageBytes = frameRead.EncodeToEXR(); if (imageBytes != null) { string timecode = Mathf.FloorToInt((float)(Control.GetCurrentTime() * 1000.0)).ToString("D8"); System.IO.File.WriteAllBytes("frame-" + timecode + ".exr", imageBytes); } Destroy(frame); Texture2D.Destroy(frameRead); RenderTexture.Destroy(rt); } } #endif #endregion // Save Frame To PNG } }