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?
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.
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.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UI;
using UnityEngine.Video;
public class ScrubbingDemo : MonoBehaviour
{
private const int kFrameIndexNone = -1;
public VideoPlayer m_Player;
public Slider m_Slider;
private bool m_IsInteracting;
private float m_LastSeekFrame = kFrameIndexNone;
private void Start()
{
m_Slider.maxValue = (float)m_Player.length;
}
void Update()
{
if (m_IsInteracting)
{
Seek(m_Slider.value);
}
else
{
m_Slider.value = (float)m_Player.time;
}
}
public void Seek(float seekTime)
{
// Seek is an asynchrone operation and can take more than one frame to complete.
// If a seek is pending and you do another seek, the previous seek will be cancel.
// It is especially problematic when doing scrubbing since if you cancel all seek,
// the player will look frozen from the user point of view since, it never have time to blit the image.
// We handle this by using frameReady to know that the seek is completed and the frame is blitted.
// If you are currently seeking there is no point to seek again.
// Otherwise, you will cancel the last seek before it has finished
if (m_LastSeekFrame >= 0)
return;
seekTime = Mathf.Clamp(seekTime, 0, (float)m_Player.length);
// This calculation only works for videos with constant framerate.
// Seeking on video with variable framerate is not supported.
var frameIndex = Mathf.RoundToInt(seekTime * m_Player.frameRate);
if (Mathf.Abs(m_Player.frame - frameIndex) <= 2)
return;
m_LastSeekFrame = frameIndex;
m_Player.frame = frameIndex;
}
private void BeginSeek()
{
m_IsInteracting = true;
m_Player.frameReady += ScrubNewFrameReceived;
m_Player.sendFrameReadyEvents = true;
m_Player.Pause();
}
private void EndSeek()
{
// If we were seeking, it is important to delay the end seek.
// Otherwise, the script will update the slider with the previous time
// and when the seek finish, it will jump to the new position.
// It is especially visible when you do a big seek.
// In case, the event was already registered or this function was called 2 times before a frameReady
m_Player.frameReady -= DelayedEndSeek;
// Remove old events
m_Player.frameReady -= ScrubNewFrameReceived;
if (m_LastSeekFrame >= 0)
{
// If we are still seeking, delay the cleanup after the seek end.
// Otherwise, the scrub will end while still seeking which can caused weird behavior
// ex. Progress bar jumping because the time just got updated with the last seek
m_Player.frameReady += DelayedEndSeek;
m_Player.sendFrameReadyEvents = true;
}
else
{
// If we already received the frame ready event
CleanupAfterSeek();
}
}
private void CleanupAfterSeek()
{
m_Player.sendFrameReadyEvents = false;
m_Player.frameReady -= DelayedEndSeek;
m_LastSeekFrame = kFrameIndexNone;
m_Player.Play();
m_IsInteracting = false;
}
public void OnSliderPointerDown()
{
if (m_IsInteracting)
return;
BeginSeek();
}
public void OnSliderPointerUp()
{
if(!m_IsInteracting)
return;
EndSeek();
}
private void DelayedEndSeek(VideoPlayer source, long frameIdx) => CleanupAfterSeek();
private void ScrubNewFrameReceived(VideoPlayer source, long frameIdx)
{
m_LastSeekFrame = kFrameIndexNone;
}
}
The second thing you can look at is the distance between your keyframes. You generally want 1 keyframe every ~2 seconds.
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