123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- 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;
- }
-
- /// <summary>
- /// 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()
- /// </summary>
- 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<Camera>();
- _dummyCamera.hideFlags = HideFlags.HideInInspector | HideFlags.DontSave;
- _dummyCamera.cullingMask = 0;
- _dummyCamera.clearFlags = CameraClearFlags.Nothing;
- _dummyCamera.enabled = false;
- }
- else
- {
- _dummyCamera = go.GetComponent<Camera>();
- }
- }
- //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
- }
- }
|