Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Video Slow Seek Times On Android VideoPlayer

Discussion in 'Audio & Video' started by Davex6, Jul 11, 2023.

  1. Davex6

    Davex6

    Joined:
    Mar 29, 2023
    Posts:
    49
    Hi,
    I've a short, around 60s, video on disk (1080*2316). It plays fine both in editor and on device, however, trying to use the seek bar to select or drag to a new position takes maybe a couple of seconds to react on the android device. It is not perfect in the editor, but much more responsive than on the device.

    The logcat shows this error message constantly, for example, there were 4 entries at this timestamp of 11:21:03.206:
    2023/07/11 11:21:03.206  Debug Codec2Buffer ConstGraphicBlockBuffer::canCopy: wrapped ; buffer ref doesn't exist

    I've no clue if it is this that is causing the awful response times when seeking, but it can hardly help. I can neither find any examples of a progress bar in Android, but there are store assets with the feature so I guess it must be doable?

    Edit - the slow seek is a known issue on 2021 and possibly 2022 (https://forum.unity.com/threads/internal-videoplayer-changes-in-2021.1111537/).
    Anyone else having this issue on Android, the best solution I could find was this:
    https://oxmond.com/how-to-build-a-video-player-with-scrub-control-in-unity/
    It is reasonably responsive (though need to drag, not click- though this likely could be made to work with code changes) and they have a unity package download.

    Further edit - this is wrong, still getting them:
    The buffer ref doesn't exist error is likely due to the resolution - a video 1920 * 1080 played without the constant errors.
     
    Last edited: Jul 13, 2023
  2. The_Island

    The_Island

    Unity Technologies

    Joined:
    Jun 1, 2021
    Posts:
    502
    You are probably canceling Seek before it blit and it gives the impression that it is lagging. You need to wait for frameReady event before you seek again. Here is a small script demonstrating how to do it.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Assertions;
    5. using UnityEngine.UI;
    6. using UnityEngine.Video;
    7.  
    8. public class ScrubbingDemo : MonoBehaviour
    9. {
    10.     private const int kFrameIndexNone = -1;
    11.  
    12.     public VideoPlayer m_Player;
    13.     public Slider m_Slider;
    14.  
    15.     private bool m_IsInteracting;
    16.  
    17.     private float m_LastSeekFrame = kFrameIndexNone;
    18.  
    19.     private void Start()
    20.     {
    21.         m_Slider.maxValue = (float)m_Player.length;
    22.     }
    23.  
    24.     void Update()
    25.     {
    26.         if (m_IsInteracting)
    27.         {
    28.             Seek(m_Slider.value);
    29.         }
    30.         else
    31.         {
    32.             m_Slider.value = (float)m_Player.time;
    33.         }  
    34.     }
    35.  
    36.     public void Seek(float seekTime)
    37.     {
    38.         // Seek is an asynchrone operation and can take more than one frame to complete.
    39.         // If a seek is pending and you do another seek, the previous seek will be cancel.
    40.         // It is especially problematic when doing scrubbing since if you cancel all seek,
    41.         // the player will look frozen from the user point of view since, it never have time to blit the image.
    42.         // We handle this by using frameReady to know that the seek is completed and the frame is blitted.
    43.  
    44.         // If you are currently seeking there is no point to seek again.
    45.         // Otherwise, you will cancel the last seek before it has finished
    46.         if (m_LastSeekFrame >= 0)
    47.             return;
    48.  
    49.         seekTime = Mathf.Clamp(seekTime, 0, (float)m_Player.length);
    50.  
    51.         // This calculation only works for videos with constant framerate.
    52.         // Seeking on video with variable framerate is not supported.
    53.         var frameIndex = Mathf.RoundToInt(seekTime * m_Player.frameRate);
    54.  
    55.         if (Mathf.Abs(m_Player.frame - frameIndex) <= 2)
    56.             return;
    57.  
    58.         m_LastSeekFrame = frameIndex;
    59.         m_Player.frame = frameIndex;
    60.     }
    61.  
    62.     private void BeginSeek()
    63.     {
    64.         m_IsInteracting = true;
    65.  
    66.         m_Player.frameReady += ScrubNewFrameReceived;
    67.         m_Player.sendFrameReadyEvents = true;
    68.  
    69.         m_Player.Pause();
    70.     }
    71.  
    72.     private void EndSeek()
    73.     {
    74.         // If we were seeking, it is important to delay the end seek.
    75.         // Otherwise, the script will update the slider with the previous time
    76.         // and when the seek finish, it will jump to the new position.
    77.         // It is especially visible when you do a big seek.
    78.  
    79.         // In case, the event was already registered or this function was called 2 times before a frameReady
    80.         m_Player.frameReady -= DelayedEndSeek;
    81.  
    82.         // Remove old events
    83.         m_Player.frameReady -= ScrubNewFrameReceived;
    84.  
    85.         if (m_LastSeekFrame >= 0)
    86.         {
    87.             // If we are still seeking, delay the cleanup after the seek end.
    88.             // Otherwise, the scrub will end while still seeking which can caused weird behavior
    89.             // ex. Progress bar jumping because the time just got updated with the last seek
    90.             m_Player.frameReady += DelayedEndSeek;
    91.             m_Player.sendFrameReadyEvents = true;
    92.         }
    93.         else
    94.         {
    95.             // If we already received the frame ready event
    96.             CleanupAfterSeek();
    97.         }
    98.     }
    99.  
    100.     private void CleanupAfterSeek()
    101.     {
    102.         m_Player.sendFrameReadyEvents = false;
    103.         m_Player.frameReady -= DelayedEndSeek;
    104.  
    105.         m_LastSeekFrame = kFrameIndexNone;
    106.  
    107.         m_Player.Play();
    108.  
    109.         m_IsInteracting = false;
    110.     }
    111.  
    112.     public void OnSliderPointerDown()
    113.     {
    114.         if (m_IsInteracting)
    115.             return;
    116.  
    117.         BeginSeek();
    118.     }
    119.  
    120.     public void OnSliderPointerUp()
    121.     {
    122.         if(!m_IsInteracting)
    123.             return;
    124.  
    125.         EndSeek();
    126.     }
    127.  
    128.     private void DelayedEndSeek(VideoPlayer source, long frameIdx) => CleanupAfterSeek();
    129.  
    130.     private void ScrubNewFrameReceived(VideoPlayer source, long frameIdx)
    131.     {
    132.         m_LastSeekFrame = kFrameIndexNone;
    133.     }
    134. }
    135.  
    The second thing you can look at is the distance between your keyframes. You generally want 1 keyframe every ~2 seconds.
     
  3. Davex6

    Davex6

    Joined:
    Mar 29, 2023
    Posts:
    49
    Thanks, but am now using https://oxmond.com/how-to-build-a-video-player-with-scrub-control-in-unity/. It is responsive enough in Android, and seems to work well.
    The errors are still there, and slightly concerning. Maybe I'II make a new post about it:
    ConstGraphicBlockBuffer::canCopy: wrapped ; buffer ref doesn't exist