//-------------------------------------------- // Movie Player // Copyright © 2014 SHUU Games //-------------------------------------------- using UnityEngine; using System; using System.IO; using MP; /// /// Movie player /// public class MoviePlayer : MoviePlayerBase { /// /// Package version /// public const string PACKAGE_VERSION = "v0.7"; #region ----- Public properties ------ /// /// RAW MJPEG AVI asset that is loaded at Start, can be NULL. /// /// If Load() is called on this component, this is not relevant any more. /// public TextAsset source; /// /// The audio clip to play, can be NULL. /// /// If the source already contains audio, then audioClip will override the audio in source. /// public AudioClip audioSource; /// /// Movie load options. The Load() methods on this component will use /// this unless you're provinding your own. /// public LoadOptions loadOptions = LoadOptions.Default; /// /// The current playhead time. Use this for seeking. /// public float videoTime; /// /// The current playhead frame index. Use this for seeking. /// public int videoFrame; /// /// If TRUE, the movie will automatically loop. /// public bool loop; /// /// Called when the movie reaches an end, right after /// it is rewinded back to the beginning. /// public event MovieEvent OnLoop; #endregion ------ /public properties ------ #region ------ public methods ------ /// /// Loads the movie from byte array. /// public bool Load (byte[] bytes, LoadOptions loadOptions = null) { return Load (new MemoryStream (bytes), loadOptions); } /// /// Loads the movie from TextAsset /// public bool Load (TextAsset textAsset, LoadOptions loadOptions = null) { this.source = textAsset; return Load (new MemoryStream (textAsset.bytes), loadOptions); } /// /// Loads the movie from file path. /// public bool Load (string path, LoadOptions loadOptions = null) { return Load (File.OpenRead (path), loadOptions); } public bool Load(Stream srcStream, LoadOptions loadOptions = null) { if(loadOptions == null) { loadOptions = this.loadOptions; } else { this.loadOptions = loadOptions; } // if we have audioSource set here to override audio in the source stream // don't load the audio in the demux. bool overrideAudio = audioSource != null && !loadOptions.skipAudio; if(overrideAudio) loadOptions.skipAudio = true; bool success = false; try { base.Load (new MovieSource() { stream = srcStream }, loadOptions); movie.videoDecoder.Decode(videoFrame); if(overrideAudio) audiobuffer = audioSource; success = true; } catch (Exception e) { Debug.LogError (e); //throw e; } return success; } /// /// Reloads the movie from "source". /// [ContextMenu("Reload")] public bool Reload () { bool success = true; if (source != null) { success = Load (source.bytes, loadOptions); lastVideoFrame = -1; // will make HandleFrameDecode decode one frame even if not play=true } return success; } void Start () { Reload (); } #endregion ------ / public methods ------ protected float lastVideoTime; protected int lastVideoFrame; void OnGUI () { if (movie == null || movie.demux == null || movie.demux.videoStreamInfo == null) return; // if we're playing the movie directly to screen if (drawToScreen && framebuffer != null) { DrawFramebufferToScreen (); } } void Update () { // if this.play changed, Play or Stop the movie HandlePlayStop (); // advance playhead time or handle seeking bool wasSeeked = HandlePlayheadMove (); // decode a frame when necessary HandleFrameDecode (wasSeeked); if (play) { // synchronize audio and video HandleAudioSync (); // movie has been played back. should we restart it or loop HandleLoop (); } } protected bool HandlePlayheadMove () { // let the videoTime advance normally, but in case // frameIndex has changed, use it to find new videoTime bool seekedByVideoFrame = videoFrame != lastVideoFrame; bool seekedByVideoTime = videoTime != lastVideoTime; if (seekedByVideoFrame) { videoTime = videoFrame / framerate; } else if (play) { videoTime += Time.deltaTime; } return seekedByVideoFrame || seekedByVideoTime; } protected void HandleFrameDecode (bool wasSeeked) { if (movie == null) return; // now when videoTime is known, find the corresponding // frameIndex and decode it if was not decoded last time videoFrame = Mathf.FloorToInt (videoTime * framerate); if (lastVideoFrame != videoFrame) { // Decode a video frame only if there is a decoder. if(movie.videoDecoder != null) { movie.videoDecoder.Decode (videoFrame); // we could compensate for loading frame decode time here, // but it seems to not make timing better for some reason //videoTime += movie.videoDecoder.lastFrameDecodeTime; } if (!wasSeeked && lastVideoFrame != videoFrame - 1) { int dropCnt = videoFrame - lastVideoFrame - 1; #if MP_DEBUG Debug.Log ("Frame drop. offset=" + (lastVideoFrame + 1) + ", count=" + dropCnt + " @ " + videoTime); #endif _framesDropped += dropCnt; } } lastVideoFrame = videoFrame; lastVideoTime = videoTime; } protected void HandleAudioSync () { if (GetComponent() == null || !GetComponent().enabled || GetComponent().clip == null) return; if (videoTime <= GetComponent().clip.length && (Mathf.Abs (videoTime - GetComponent().time) > (float)maxSyncErrorFrames / framerate)) { #if MP_DEBUG Debug.Log ("Synchronizing audio and video. Drift: " + (videoTime - audio.time)); #endif GetComponent().Stop (); GetComponent().time = videoTime; GetComponent().Play (); _syncEvents++; } } protected void HandleLoop () { if (movie == null || movie.demux == null || movie.demux.videoStreamInfo == null) return; if (videoTime >= movie.demux.videoStreamInfo.lengthSeconds) { if (loop) { // seek to the beginning videoTime = 0; #if MP_DEBUG Debug.Log ("LOOP"); #endif if (OnLoop != null) OnLoop (this); } else { // stop the playback play = false; } } } }