Search Unity

Video Looping A Portion Of A Video

Discussion in 'Audio & Video' started by JerryWitt, Apr 12, 2019.

  1. JerryWitt

    JerryWitt

    Joined:
    Feb 15, 2019
    Posts:
    3
    Okay, total newbie question. (I have someone helping me, but I am trying to get up to speed with C# too.)

    Say I have a VideoPlayer with a video attached in Unity. Say I have attached a KeyDown MonoBehavior and when a certain key is pressed, the video will jump to another section of the video and either pause or play. I have all this working.

    But now, I want the video to not just play from the point I jump to, but play for 5 seconds, jump back 5 seconds, and play for 5 seconds again and again. A video loop. How do I do that?

    I have tried to wedge some loop code into the Update() section of my KeyDown. But the closest I have gotten is to get it to rewind the 5 seconds and stop. No amount of fiddling will play it from there.

    I guess this makes sense. I mean why is the video looping code in the KeyDown? What (I think) I should be doing is when you hit a key, a global variable is set, the VideoPlayer jumps to that time, and ANOTHER script evaluates the looping.

    My questions:
    What other script should evaluate what the current time is of the VideoPlayer?
    I declared the variable var vp = GetComponent<UnityEngine.Video.VideoPlayer>(); in the keydown script. Can I use this in another script?
    Besides the sloppiness of all those if/else statements, is there a better approach to do what I want to do?

    Thank you!

    I added my code that is attached to the camera with the VideoPlayer.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class keyDown : MonoBehaviour
    6. {
    7.     // Start is called before the first frame update
    8.     void Start()
    9.     {
    10.  
    11.     }
    12.  
    13.     // Update is called once per frame
    14.     public void Update()
    15.     {
    16.         var vp = GetComponent<UnityEngine.Video.VideoPlayer>();
    17.  
    18.         if (vp.time > 5)
    19.         {
    20.  
    21.             vp.time = 0;
    22.             vp.Play();
    23.         }
    24.  
    25.  
    26.         if (Input.GetKeyUp(KeyCode.X))
    27.         {
    28.  
    29.  
    30.             if (vp.isPlaying)
    31.             {
    32.                 vp.Pause();
    33.             }
    34.             else
    35.             {
    36.                 vp.Play();
    37.             }
    38.         }
    39.         else if (Input.GetKeyUp(KeyCode.G))
    40.         {
    41.             vp.time = 31;
    42.             vp.Pause();
    43.         }
    44.         else if (Input.GetKeyUp(KeyCode.T))
    45.         {
    46.             vp.time = 23;
    47.             vp.Pause();
    48.         }
    49.         else if (Input.GetKeyUp(KeyCode.Y))
    50.         {
    51.             vp.time = 24;
    52.             vp.Pause();
    53.         }
    54.         else if (Input.GetKeyUp(KeyCode.U))
    55.         {
    56.             vp.time = 25;
    57.             vp.Pause();
    58.         }
    59.         else if (Input.GetKeyUp(KeyCode.I))
    60.         {
    61.             vp.time = 26;
    62.             vp.Pause();
    63.         }
    64.         else if (Input.GetKeyUp(KeyCode.O))
    65.         {
    66.             vp.time = 27;
    67.             vp.Pause();
    68.         }
    69.         else if (Input.GetKeyUp(KeyCode.P))
    70.         {
    71.             vp.time = 28;
    72.             vp.Pause();
    73.         }
    74.         else if (Input.GetKeyUp(KeyCode.Alpha1))
    75.         {
    76.             vp.time = 1;
    77.             vp.Play();
    78.         }
    79.         else if (Input.GetKeyUp(KeyCode.Alpha2))
    80.         {
    81.             vp.time = 10;
    82.             vp.Play();
    83.         }
    84.         else if (Input.GetKeyUp(KeyCode.Alpha3))
    85.         {
    86.             vp.time = 18;
    87.             vp.Play();
    88.         }
    89.     }
    90. }
    91.  
    92.  
    93.  
     
  2. ModelOX

    ModelOX

    Joined:
    Jan 18, 2016
    Posts:
    11
    I've been trying to do something similar, looping a specific part of a video instead of looping it in it's entirety, in my case the last X seconds of a video. Right now I've got a coroutine that handles video playing, waits for the video player to finish preparing, handles subtitles, etc, and to loop the video I'm just checking the current frame against the total frame count:

    Code (CSharp):
    1.        
    2. while (loop == true)
    3. {
    4.             if ((ulong)VideoPlayer.frame >= (VideoPlayer.frameCount - 1))
    5.             {
    6.                 VideoPlayer.time = endLoopTime;
    7.             }
    8.  
    9.             yield return null;
    10.  
    11.             //for some god forsaken reason sometimes the video player sometimes stops playing in builds, so we catch that here.
    12.             if (VideoPlayer.isPlaying == false && MenuManager.Instance.GameIsPaused == false)
    13.             {
    14.                 Debug.LogError("Video player stopped during loop! This shouldn't happen! Resuming video... " + " Video Frame:" + VideoPlayer.frame + " Video Frame Count:" + VideoPlayer.frameCount);
    15.                 VideoPlayer.Play();
    16.                 VideoPlayer.time = endLoopTime;
    17.             }
    18. }
    19.  
    This mostly works, but as you can see I've run into issues in builds where sometimes the video player will stop and need to be restarted. If your not looping the end of the clip specifically I don't imagine this'll be an issue though.

    If I can hijack this thread for a sec though, the fix I have for my specific case works but recently I've been testing builds on lower end machines (since I can't expect every player to have an amazing machine and have been hoping to port to mobile in the future) and on lower end machines there's a very noticeable pause between loops. I read elsewhere that setting the video time counts as performing a seek operation and I found VideoPlayer.seekCompleted, so I'm guessing that the pause between loops is the video player doing something similar to VideoPlayer.Prepare() where it's getting frames ready to display?

    Since I'm planning on re-using these frames would it be possible to store them to avoid pauses in the loop? The docs for seekCompleted also mention that codec performance and parameters could also effect how long this takes, so if storing and looping through a set of frames isn't possible what would the ideal codec be for loading frames as fast as possible? Currently all my videos are webm's using VP8 as their codec.
     
  3. ModelOX

    ModelOX

    Joined:
    Jan 18, 2016
    Posts:
    11
    Doing some more digging I came across this thread on grabbing textures from the video player, but even if that worked out since the video player's texture is read only I'm not sure how I'd display the saved frames... I guess I could just draw the saved texture directly to the video render texture? Though at that point it feels like I'm building my own video player, and I'm sure there's a whole host of headaches involved with that.

    Looking into speeding up the video on the codec side of things I found yet another thread which mentioned that the best thing for speeding up video playback is decreasing the video resolution or decreasing the frame rate. Makes sense, and good to know, but not really what I was hoping for...

    Right now I think my plan might just be to have separate video clips for the parts I want to loop and just using the video player's built in loop stuff. Or just... living with the pause between loops...
     
  4. The_Island

    The_Island

    Unity Technologies

    Joined:
    Jun 1, 2021
    Posts:
    502
    It happens because there is no way to know if the user still wants to replay the video or not. A better way to fix this issue is to first enable isLooping (even if you don't want to loop) so at the end, it doesn't stop by mistake. And instead of using an update loop to detect the end of the file, you should use loopPointReached.
    Yes. Every time, you change the time of the VideoPlayer you are seeking. What a seek does is flush every frame buffered, jump to the file position and start buffering new frames. So the pause is the time it takes to decode one frame. As mentioned, reducing the video resolution or the framerate should help but another thing you can do is to seek on a keyframe. The way seek works, it jumps to the nearest keyframe and decodes every frame until it reaches the time target. If you seek directly on a keyframe, you don't need to decode any frame afterward, so it should technically improve your seek time.

    I think the simplest solution would be to have separate video clips for the parts like you propose.

    Otherwise, if having multiple files is not possible (too many places to jump) and you only have 1-2 VideoPlayer in your scene, you can use 2 VideoPlayer to do the playback. One VideoPlayer is playing while the other is waiting on the frame you want to seek back. When the loopPointReached is received you switch the VideoPlayer and start playing with the new one and while it plays, you ask the other VideoPlayer to seek back to the frame you want and wait. It should be smooth but there is overhead doing it this way and too many VideoPlayer will cause other issues on some platforms. But, it is maybe ok for your case.

    Hope it helps!