RenderHeads / UnityPlugin-AVProVideo

AVPro Video is a multi-platform Unity plugin for advanced video playback
https://www.renderheads.com/products/avpro-video/
239 stars 29 forks source link

[Enhancement] Adaptive hls player considering network bandwidth and latency. #2072

Closed HyundongHwang closed 19 hours ago

HyundongHwang commented 20 hours ago

Which platform is your feature request for?

Windows, Android, macOS, iOS

Description

Hello. I am using an hls streaming player by preparing only one 1080p video profile. Of course, the initial loading is slow, and buffering occurs a lot during playback. Does AvProVideo have an Adaptive Player function for this? If there is a function, please let me know if I have not found it. And if there is no function, please add it.

Below is a rough code that I thought of without knowing the internals of AvProVideo. Rather than an implementation that relies on such simple logic, it would be better if there is an elegant and better method provided by built-ins such as ExoPlayer and AVPlayer.

using RenderHeads.Media.AVProVideo;
using UnityEngine;
using System.Collections.Generic;

public class AdaptiveVideoPlayer : MonoBehaviour
{
    [SerializeField] private MediaPlayer _mediaPlayer;

    private readonly Dictionary<int, string> _qualityUrls = new Dictionary<int, string>
    {
        { 1080, "https://hhd.com/v0/av_1080p.m3u8" },
        { 720, "https://hhd.com/v0/av_720p.m3u8" },
        { 480, "https://hhd.com/v0/av_480p.m3u8" },
        { 360, "https://hhd.com/v0/av_360p.m3u8" },
        { 240, "https://hhd.com/v0/av_240p.m3u8" }
    };

    private float _checkInterval = 5f;  // Check every 5 seconds
    private float _lastCheckTime;
    private string _currentUrl;

    void Start()
    {
        // Start with lowest quality
        ChangeQuality(240);
    }

    void Update()
    {
        if (Time.time - _lastCheckTime > _checkInterval)
        {
            CheckAndUpdateQuality();
            _lastCheckTime = Time.time;
        }
    }

    private void CheckAndUpdateQuality()
    {
        // Get current buffer metrics
        float bufferProgress = _mediaPlayer.Control.GetBufferingProgress();
        float bufferAhead = _mediaPlayer.Control.GetBufferedAheadTime();

        // Get current time to determine if we're experiencing buffering
        float currentTime = _mediaPlayer.Control.GetCurrentTimeMs();
        bool isBuffering = _mediaPlayer.Control.IsBuffering();

        int targetQuality = DetermineTargetQuality(bufferProgress, bufferAhead, isBuffering);
        ChangeQuality(targetQuality);
    }

    private int DetermineTargetQuality(float bufferProgress, float bufferAhead, bool isBuffering)
    {
        // If buffering or low buffer ahead time, decrease quality
        if (isBuffering || bufferAhead < 2f)
        {
            if (_currentUrl == _qualityUrls[1080]) return 720;
            if (_currentUrl == _qualityUrls[720]) return 480;
            if (_currentUrl == _qualityUrls[480]) return 360;
            if (_currentUrl == _qualityUrls[360]) return 240;
        }
        // If good buffer, try increasing quality
        else if (bufferAhead > 10f && bufferProgress > 0.9f)
        {
            if (_currentUrl == _qualityUrls[240]) return 360;
            if (_currentUrl == _qualityUrls[360]) return 480;
            if (_currentUrl == _qualityUrls[480]) return 720;
            if (_currentUrl == _qualityUrls[720]) return 1080;
        }

        // Keep current quality if conditions are stable
        foreach (var kvp in _qualityUrls)
        {
            if (kvp.Value == _currentUrl)
                return kvp.Key;
        }

        return 240; // Default to lowest quality
    }

    private void ChangeQuality(int quality)
    {
        string newUrl = _qualityUrls[quality];
        if (newUrl == _currentUrl) return;

        float currentTime = _mediaPlayer.Control.GetCurrentTimeMs() / 1000f; // Convert to seconds
        _currentUrl = newUrl;

        // Save current playback state
        bool wasPlaying = _mediaPlayer.Control.IsPlaying();

        // Change media source
        _mediaPlayer.OpenMedia(MediaPathType.Path, newUrl, false);

        // Wait for media to be loaded then seek to previous position
        StartCoroutine(SeekAfterLoad(currentTime, wasPlaying));
    }

    private System.Collections.IEnumerator SeekAfterLoad(float seekTime, bool autoPlay)
    {
        // Wait for media to be ready
        while (!_mediaPlayer.Control.CanPlay())
        {
            yield return null;
        }

        // Seek to previous position
        _mediaPlayer.Control.Seek(seekTime);

        if (autoPlay)
        {
            _mediaPlayer.Control.Play();
        }
    }
}
MorrisRH commented 19 hours ago

The purpose of HLS is to provide adaptive playback based on network conditions. You need to create a master manifest which contains your variants and then play that.

All the platforms supported by AVPro Video support adaptive playback with HLS.

A good source of information on how to create adaptive HLS can be found here