Changing VideoPlayer frame from code doesn't update frame [SOLVED]

Hello,

I think I found an issue with some versions of Unity and the VideoPlayer component.
I made a script to manually update the VideoPlayer frames so I can change the speed of the video given certain parameters. Example:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;

public class VideoTest : MonoBehaviour
{
    public VideoPlayer player;

    public float speed = 1f;

    float percent = 0f;

    long frame = 0;

    void Start()
    {
        player.playOnAwake = false;
        player.isLooping = false;
        player.renderMode = VideoRenderMode.RenderTexture;
        player.audioOutputMode = VideoAudioOutputMode.None;

        player.frame = 1;
        player.Play();
    }

    // Update is called once per frame
    void Update()
    {
        percent += Time.deltaTime * speed;

        if (percent > 1f)
        {
            frame++;
            percent = 0f;
        }


        player.frame = frame;

        player.Play();
    }
}

This works just fine in Unity 2019.4.16f1, but in newer versions (2019.4.40f1, 2020.3.15f1, 2021.3.9f1) the video does not changes the frame.

I found this bug report that looks like this very same problem, but it’s marked as a duplicate of another bug that seems to be something related to video width and height.

Is this in fact a Unity bug or were there changes in the way we should work with VidePlayer in newer versions?

Thanks!

1 Like

I think I see the issue. Every time you call player.frame = frame, you are doing a seek operation. This operation is async, meaning it can finish the next frame. If you happen to request a new seek before the last one finish, we abort the last seek and start the new one. So from your point of view, it looks stuck, but in reality, you are cancelling all your seek. We observed some seek performance regression on some platforms, which probably was enough to break your code. This is the workflow I recommend you to use instead.

void Start()
{
    vp = GetComponent<VideoPlayer>();
    vp.seekCompleted += seekCompleted;
}

void seekCompleted(VideoPlayer par)
{
    StartCoroutine(WaitToUpdateRenderTextureBeforeEndingSeek());
}

public void UpdateVideoPlayerToFrame(int frame)
{
    //If you are currently seeking there is no point to seek again.
    if (!seekDone)
        return;
  
    // You should pause while you seek for better stability
    vp.Pause();
  
    vp.frame = frame;
    seekDone = false;
}

IEnumerator WaitToUpdateRenderTextureBeforeEndingSeek()
{
    yield return new WaitForEndOfFrame();
    seekDone = true;
}
3 Likes

That was it!.

Now I need to do slightly different time/speed calculations, but I was able to play the video.

Thank you @The_Island !

It will probably be easier if you use VideoPlayer.time.

You can otherwise test VideoPlayer.playbackSpeed. It is not supported on every platform, but it will give you a smoother playback.

1 Like

It’s feel very weird that this behavior felt hidden. And the state of seeking are not natively exposed as API, instead we need to keep tap of it ourselves

2 Likes