using UnityEngine; using System.Collections; //----------------------------------------------------------------------------- // Copyright 2015-2022 RenderHeads Ltd. All rights reserved. //----------------------------------------------------------------------------- namespace RenderHeads.Media.AVProVideo { public partial class MediaPlayer : MonoBehaviour { #region Extract Frame 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) { _playerInterface.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); } _playerInterface.Render(); return result; } /// /// 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 (_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); _dummyCamera = go.AddComponent(); _dummyCamera.hideFlags = HideFlags.HideInInspector | HideFlags.DontSave; _dummyCamera.cullingMask = 0; _dummyCamera.clearFlags = CameraClearFlags.Nothing; _dummyCamera.enabled = false; } else { _dummyCamera = go.GetComponent(); } } //Debug.Assert(_dummyCamera != null); return _dummyCamera; } private IEnumerator ExtractFrameCoroutine(Texture2D target, ProcessExtractedFrame callback, double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100) { #if (!UNITY_EDITOR && UNITY_ANDROID) || UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX || UNITY_IOS || UNITY_TVOS Texture2D result = target; Texture frame = null; if (_controlInterface != null) { if (timeSeconds >= 0f) { Pause(); // If the right frame is already available (or close enough) just grab it if (TextureProducer.GetTexture() != null && (System.Math.Abs(_controlInterface.GetCurrentTime() - timeSeconds) < (timeThresholdMs / 1000.0))) { frame = TextureProducer.GetTexture(); } else { int preSeekFrameCount = _textureInterface.GetTextureFrameCount(); // Seek to the frame if (accurateSeek) { _controlInterface.Seek(timeSeconds); } else { _controlInterface.SeekFast(timeSeconds); } // Wait for the new frame to arrive if (!_controlInterface.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, double timeSeconds = -1.0, 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, double timeSeconds = -1.0, 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(double timeSeconds = -1.0, bool accurateSeek = true, int timeoutMs = 1000, int timeThresholdMs = 100) { Texture result = null; if (_controlInterface != null) { if (timeSeconds >= 0f) { Pause(); // If the right frame is already available (or close enough) just grab it if (TextureProducer.GetTexture() != null && (System.Math.Abs(_controlInterface.GetCurrentTime() - timeSeconds) < (timeThresholdMs / 1000.0))) { result = TextureProducer.GetTexture(); } else { // Store frame count before seek int frameCount = TextureProducer.GetTextureFrameCount(); // Seek to the frame if (accurateSeek) { _controlInterface.Seek(timeSeconds); } else { _controlInterface.SeekFast(timeSeconds); } // Wait for frame to change ForceWaitForNewFrame(frameCount, timeoutMs); result = TextureProducer.GetTexture(); } } else { result = TextureProducer.GetTexture(); } } return result; } #endregion // Extract Frame } }