567 lines
18 KiB
C#
567 lines
18 KiB
C#
using System;
|
||||
|
|
using System.Collections;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.IO;
|
|||
|
|
using FairyGUI;
|
|||
|
|
using SGModule.Common;
|
|||
|
|
using SGModule.Common.Base;
|
|||
|
|
using Unity.VisualScripting;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.Networking;
|
|||
|
|
using UnityEngine.Video;
|
|||
|
|
|
|||
|
|
namespace BallKingdomCrush
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
public class LiveVideoManager : SingletonMonoBehaviour<LiveVideoManager>
|
|||
|
|
{
|
|||
|
|
public static string videoBaseUrl = "";
|
|||
|
|
|
|||
|
|
// 封面缓存
|
|||
|
|
private readonly Dictionary<string, Texture2D> coverCache = new Dictionary<string, Texture2D>();
|
|||
|
|
|
|||
|
|
// 封面提取队列(避免同时开多个VideoPlayer)
|
|||
|
|
private readonly Queue<CoverTask> coverQueue = new();
|
|||
|
|
|
|||
|
|
// 已下载视频缓存
|
|||
|
|
private readonly HashSet<string> downloadedVideos = new();
|
|||
|
|
|
|||
|
|
// 当前正在下载的视频,用于去重
|
|||
|
|
private readonly HashSet<string> downloadingSet = new();
|
|||
|
|
|
|||
|
|
// ==== 队列 ====
|
|||
|
|
|
|||
|
|
// 普通视频队列和优先队列
|
|||
|
|
private readonly Queue<VideoTask> normalQueue = new();
|
|||
|
|
private readonly Queue<VideoTask> priorityQueue = new();
|
|||
|
|
private bool isExtracting;
|
|||
|
|
|
|||
|
|
private Coroutine normalCoroutine;
|
|||
|
|
private Coroutine priorityCoroutine;
|
|||
|
|
|
|||
|
|
private string videoLocalDir => Path.Combine(TextureHelper.getResPath(), "LiveVideos");
|
|||
|
|
private string coverLocalDir => Path.Combine(TextureHelper.getResPath(), "LiveVideoCovers");
|
|||
|
|
|
|||
|
|
protected override void Awake()
|
|||
|
|
{
|
|||
|
|
base.Awake();
|
|||
|
|
InitDirs();
|
|||
|
|
InitCache();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void InitDirs()
|
|||
|
|
{
|
|||
|
|
if (!Directory.Exists(videoLocalDir))
|
|||
|
|
Directory.CreateDirectory(videoLocalDir);
|
|||
|
|
|
|||
|
|
if (!Directory.Exists(coverLocalDir))
|
|||
|
|
Directory.CreateDirectory(coverLocalDir);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void InitCache()
|
|||
|
|
{
|
|||
|
|
var files = Directory.GetFiles(videoLocalDir, "*.mp4");
|
|||
|
|
foreach (var file in files)
|
|||
|
|
{
|
|||
|
|
var fileName = Path.GetFileNameWithoutExtension(file);
|
|||
|
|
downloadedVideos.Add(fileName);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region 缓存清理
|
|||
|
|
|
|||
|
|
public void ClearAllCache()
|
|||
|
|
{
|
|||
|
|
foreach (var tex in coverCache.Values)
|
|||
|
|
if (tex != null)
|
|||
|
|
Destroy(tex);
|
|||
|
|
coverCache.Clear();
|
|||
|
|
|
|||
|
|
if (Directory.Exists(coverLocalDir))
|
|||
|
|
{
|
|||
|
|
Directory.Delete(coverLocalDir, true);
|
|||
|
|
Directory.CreateDirectory(coverLocalDir);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Directory.Exists(videoLocalDir))
|
|||
|
|
{
|
|||
|
|
Directory.Delete(videoLocalDir, true);
|
|||
|
|
Directory.CreateDirectory(videoLocalDir);
|
|||
|
|
downloadedVideos.Clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 统一加载视频封面
|
|||
|
|
|
|||
|
|
public IEnumerator LoadCover(string fileName, Action<Texture2D> callback)
|
|||
|
|
{
|
|||
|
|
Texture2D tex = null;
|
|||
|
|
|
|||
|
|
var persistentPath = Path.Combine(TextureHelper.getResPath(), "LiveVideoCovers", fileName + ".png");
|
|||
|
|
if (File.Exists(persistentPath))
|
|||
|
|
{
|
|||
|
|
tex = LoadTextureFromFile(persistentPath);
|
|||
|
|
if (tex != null)
|
|||
|
|
{
|
|||
|
|
callback?.Invoke(tex);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var saPath = Path.Combine(Application.streamingAssetsPath, "LiveVideoCovers", fileName + ".jpg");
|
|||
|
|
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(saPath))
|
|||
|
|
{
|
|||
|
|
yield return www.SendWebRequest();
|
|||
|
|
if (www.result == UnityWebRequest.Result.Success)
|
|||
|
|
tex = DownloadHandlerTexture.GetContent(www);
|
|||
|
|
else
|
|||
|
|
Debug.LogWarning($"LoadCover StreamingAssets失败: {www.error}, file: {fileName}");
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
if (File.Exists(saPath))
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var data = File.ReadAllBytes(saPath);
|
|||
|
|
tex = new Texture2D(2, 2);
|
|||
|
|
tex.LoadImage(data);
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"LoadCover StreamingAssets读取失败: {fileName}, error: {e}");
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
callback?.Invoke(tex);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
|
|||
|
|
public static IEnumerator LoadVideoToPlayer(VideoPlayer player, string fileName, GLoader loader,
|
|||
|
|
Action<VideoPlayer> onComplete, bool play = true)
|
|||
|
|
{
|
|||
|
|
string localPath = null;
|
|||
|
|
var isDone = false;
|
|||
|
|
Instance.GetVideoLocalPath(fileName, path =>
|
|||
|
|
{
|
|||
|
|
localPath = path;
|
|||
|
|
isDone = true;
|
|||
|
|
});
|
|||
|
|
Debug.Log("LoadVideoToPlayer ------1------" + isDone);
|
|||
|
|
|
|||
|
|
while (!isDone)
|
|||
|
|
yield return null;
|
|||
|
|
|
|||
|
|
if (string.IsNullOrEmpty(localPath))
|
|||
|
|
{
|
|||
|
|
onComplete?.Invoke(null);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (player.IsDestroyed())
|
|||
|
|
{
|
|||
|
|
onComplete?.Invoke(null);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
player.source = VideoSource.Url;
|
|||
|
|
Debug.Log("LoadVideoToPlayer diaoyongyici: " + fileName);
|
|||
|
|
player.url = Application.platform == RuntimePlatform.Android && !Application.isEditor
|
|||
|
|
? localPath
|
|||
|
|
: "file://" + localPath;
|
|||
|
|
|
|||
|
|
player.isLooping = true;
|
|||
|
|
player.playOnAwake = false;
|
|||
|
|
|
|||
|
|
var rtWidth = (int)loader.width;
|
|||
|
|
var rtHeight = (int)loader.height;
|
|||
|
|
|
|||
|
|
var rt = new RenderTexture(rtWidth, rtHeight, 0);
|
|||
|
|
rt.Create();
|
|||
|
|
|
|||
|
|
player.targetTexture = rt;
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (!loader.isDisposed)
|
|||
|
|
{
|
|||
|
|
Debug.Log("LoadVideoToPlayer loader is isDisposed: ");
|
|||
|
|
loader.texture = new NTexture(rt);
|
|||
|
|
loader.visible = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
player.Prepare();
|
|||
|
|
|
|||
|
|
var timeout = 3f;
|
|||
|
|
var timer = 0f;
|
|||
|
|
while (!player.IsDestroyed() && !player.isPrepared && timer < timeout)
|
|||
|
|
{
|
|||
|
|
yield return null;
|
|||
|
|
timer += Time.deltaTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (player.IsDestroyed())
|
|||
|
|
{
|
|||
|
|
onComplete?.Invoke(null);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!player.isPrepared)
|
|||
|
|
{
|
|||
|
|
if (rt != null && !rt.Equals(null)) rt.Release();
|
|||
|
|
onComplete?.Invoke(null);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (play)
|
|||
|
|
player.Play();
|
|||
|
|
else
|
|||
|
|
player.Pause();
|
|||
|
|
|
|||
|
|
if (!loader.isDisposed)
|
|||
|
|
loader.visible = true;
|
|||
|
|
|
|||
|
|
onComplete?.Invoke(player);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region 视频下载队列(支持普通 + 优先 + 硬取消 + 去重)
|
|||
|
|
|
|||
|
|
public void GetVideoLocalPath(string fileName, Action<string> callback, bool priority = true, int maxRetry = 3,
|
|||
|
|
bool hardCancel = false)
|
|||
|
|
{
|
|||
|
|
var localPath = Path.Combine(videoLocalDir, fileName + ".mp4");
|
|||
|
|
|
|||
|
|
// 已下载直接回调
|
|||
|
|
if (downloadedVideos.Contains(fileName) && File.Exists(localPath))
|
|||
|
|
{
|
|||
|
|
callback?.Invoke(localPath);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 去重:如果正在下载中,也直接等待回调
|
|||
|
|
if (downloadingSet.Contains(fileName))
|
|||
|
|
{
|
|||
|
|
StartCoroutine(WaitForDownload(fileName, callback));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var task = new VideoTask
|
|||
|
|
{
|
|||
|
|
FileName = fileName,
|
|||
|
|
LocalPath = localPath,
|
|||
|
|
Callback = callback,
|
|||
|
|
MaxRetry = maxRetry
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (priority)
|
|||
|
|
{
|
|||
|
|
if (hardCancel)
|
|||
|
|
{
|
|||
|
|
// 硬取消:停止当前优先队列协程,清空队列
|
|||
|
|
if (priorityCoroutine != null)
|
|||
|
|
{
|
|||
|
|
StopCoroutine(priorityCoroutine);
|
|||
|
|
priorityCoroutine = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
priorityQueue.Clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
priorityQueue.Enqueue(task);
|
|||
|
|
if (priorityCoroutine == null)
|
|||
|
|
priorityCoroutine = StartCoroutine(ProcessPriorityQueue());
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
normalQueue.Enqueue(task);
|
|||
|
|
if (normalCoroutine == null)
|
|||
|
|
normalCoroutine = StartCoroutine(ProcessNormalQueue());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 等待正在下载的视频完成再回调
|
|||
|
|
private IEnumerator WaitForDownload(string fileName, Action<string> callback)
|
|||
|
|
{
|
|||
|
|
while (downloadingSet.Contains(fileName))
|
|||
|
|
yield return null;
|
|||
|
|
|
|||
|
|
var localPath = Path.Combine(videoLocalDir, fileName + ".mp4");
|
|||
|
|
callback?.Invoke(File.Exists(localPath) ? localPath : null);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 优先队列协程(单任务)
|
|||
|
|
private IEnumerator ProcessPriorityQueue()
|
|||
|
|
{
|
|||
|
|
while (priorityQueue.Count > 0)
|
|||
|
|
{
|
|||
|
|
var task = priorityQueue.Dequeue();
|
|||
|
|
yield return DownloadVideoCoroutine(task);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
priorityCoroutine = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 普通队列协程(单任务)
|
|||
|
|
private IEnumerator ProcessNormalQueue()
|
|||
|
|
{
|
|||
|
|
while (normalQueue.Count > 0)
|
|||
|
|
{
|
|||
|
|
var task = normalQueue.Dequeue();
|
|||
|
|
yield return DownloadVideoCoroutine(task);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
normalCoroutine = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 核心下载协程
|
|||
|
|
private IEnumerator DownloadVideoCoroutine(VideoTask task)
|
|||
|
|
{
|
|||
|
|
// 再次检查本地是否已有,避免重复下载
|
|||
|
|
if (downloadedVideos.Contains(task.FileName) && File.Exists(task.LocalPath))
|
|||
|
|
{
|
|||
|
|
task.Callback?.Invoke(task.LocalPath);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
downloadingSet.Add(task.FileName);
|
|||
|
|
|
|||
|
|
var tmpPath = task.LocalPath + ".downloading";
|
|||
|
|
var url = videoBaseUrl + "LiveAlbums/" + task.FileName + ".mp4";
|
|||
|
|
|
|||
|
|
if (File.Exists(tmpPath)) File.Delete(tmpPath);
|
|||
|
|
|
|||
|
|
var attempt = 0;
|
|||
|
|
var success = false;
|
|||
|
|
|
|||
|
|
while (attempt < task.MaxRetry)
|
|||
|
|
{
|
|||
|
|
attempt++;
|
|||
|
|
using (var www = UnityWebRequest.Get(url))
|
|||
|
|
{
|
|||
|
|
www.downloadHandler = new DownloadHandlerFile(tmpPath, true);
|
|||
|
|
yield return www.SendWebRequest();
|
|||
|
|
|
|||
|
|
if (www.result == UnityWebRequest.Result.Success)
|
|||
|
|
{
|
|||
|
|
if (File.Exists(task.LocalPath)) File.Delete(task.LocalPath);
|
|||
|
|
|
|||
|
|
Debug.Log($"[DownloadVideo] 下载成功,开始解密视频 {task.FileName}");
|
|||
|
|
Rescrypt.DecryptFile(tmpPath, task.LocalPath);
|
|||
|
|
Debug.Log($"[DownloadVideo] 解密完成,保存路径:{task.LocalPath}");
|
|||
|
|
|
|||
|
|
downloadedVideos.Add(task.FileName);
|
|||
|
|
success = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Debug.LogWarning($"视频下载失败(第 {attempt} 次): {task.FileName}, {www.error}");
|
|||
|
|
if (attempt < task.MaxRetry)
|
|||
|
|
yield return new WaitForSeconds(1f);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!success)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"视频下载失败,超过最大重试次数:{task.FileName}");
|
|||
|
|
if (File.Exists(tmpPath)) File.Delete(tmpPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
downloadingSet.Remove(task.FileName);
|
|||
|
|
task.Callback?.Invoke(success ? task.LocalPath : null);
|
|||
|
|
|
|||
|
|
LiveVideoMemoryManager.RequestCleanup();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 视频封面
|
|||
|
|
|
|||
|
|
// 你的封面逻辑保持不变
|
|||
|
|
public void GetVideoCover(GLoader loader, string fileName, Action<Texture2D> onComplete)
|
|||
|
|
{
|
|||
|
|
if (coverCache.TryGetValue(fileName, out var cached))
|
|||
|
|
{
|
|||
|
|
onComplete?.Invoke(cached);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var coverPath = Path.Combine(coverLocalDir, fileName + ".png");
|
|||
|
|
if (File.Exists(coverPath))
|
|||
|
|
{
|
|||
|
|
var tex = LoadTextureFromFile(coverPath);
|
|||
|
|
coverCache[fileName] = tex;
|
|||
|
|
onComplete?.Invoke(tex);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StartCoroutine(LoadCover(fileName, onComplete));
|
|||
|
|
|
|||
|
|
coverQueue.Enqueue(new CoverTask { FileName = fileName, Callback = onComplete });
|
|||
|
|
|
|||
|
|
if (!isExtracting)
|
|||
|
|
ProcessCoverQueue(loader);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ProcessCoverQueue(GLoader loader)
|
|||
|
|
{
|
|||
|
|
isExtracting = true;
|
|||
|
|
|
|||
|
|
while (coverQueue.Count > 0)
|
|||
|
|
{
|
|||
|
|
var task = coverQueue.Dequeue();
|
|||
|
|
ProcessGetCoverCoroutine(loader, task.FileName, task.Callback);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
isExtracting = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ProcessGetCoverCoroutine(GLoader loader, string fileName, Action<Texture2D> callback)
|
|||
|
|
{
|
|||
|
|
TextureHelper.SetImgLoader(loader, fileName,
|
|||
|
|
(a) => {
|
|||
|
|
coverCache[fileName] = a.nativeTexture as Texture2D;
|
|||
|
|
callback?.Invoke(coverCache[fileName]);
|
|||
|
|
}, "LiveAlbums/", FolderNames.VideoCoversName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerator ExtractCoverFromVideo(string fileName, string videoPath, Action<Texture2D> callback)
|
|||
|
|
{
|
|||
|
|
var go = new GameObject("LiveVideoCoverExtractor_" + fileName);
|
|||
|
|
var vp = go.AddComponent<VideoPlayer>();
|
|||
|
|
vp.audioOutputMode = VideoAudioOutputMode.None;
|
|||
|
|
vp.source = VideoSource.Url;
|
|||
|
|
vp.url = Application.platform == RuntimePlatform.Android && !Application.isEditor
|
|||
|
|
? videoPath
|
|||
|
|
: "file://" + videoPath;
|
|||
|
|
|
|||
|
|
vp.isLooping = false;
|
|||
|
|
vp.playOnAwake = false;
|
|||
|
|
|
|||
|
|
var rt = new RenderTexture(460,690, 0);
|
|||
|
|
vp.targetTexture = rt;
|
|||
|
|
vp.Prepare();
|
|||
|
|
|
|||
|
|
var timeout = 5f;
|
|||
|
|
var timer = 0f;
|
|||
|
|
while (!vp.isPrepared && timer < timeout)
|
|||
|
|
{
|
|||
|
|
timer += Time.deltaTime;
|
|||
|
|
yield return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!vp.isPrepared)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"LiveVideoManager: Video '{fileName}' prepare timeout.");
|
|||
|
|
Destroy(go);
|
|||
|
|
callback?.Invoke(null);
|
|||
|
|
yield break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
vp.Pause();
|
|||
|
|
yield return new WaitForEndOfFrame();
|
|||
|
|
|
|||
|
|
var tex = CaptureFrame(vp);
|
|||
|
|
SaveCover(fileName, tex);
|
|||
|
|
coverCache[fileName] = tex;
|
|||
|
|
|
|||
|
|
callback?.Invoke(tex);
|
|||
|
|
Destroy(go);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private Texture2D CaptureFrame(VideoPlayer vp)
|
|||
|
|
{
|
|||
|
|
var rt = vp.targetTexture;
|
|||
|
|
RenderTexture.active = rt;
|
|||
|
|
|
|||
|
|
var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
|
|||
|
|
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
|
|||
|
|
tex.Apply();
|
|||
|
|
|
|||
|
|
RenderTexture.active = null;
|
|||
|
|
vp.Stop();
|
|||
|
|
vp.targetTexture = null;
|
|||
|
|
return tex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SaveCover(string fileName, Texture2D tex)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var pngData = tex.EncodeToPNG();
|
|||
|
|
var path = Path.Combine(coverLocalDir, fileName + ".png");
|
|||
|
|
File.WriteAllBytes(path, pngData);
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"Save cover PNG failed for '{fileName}': {e}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private Texture2D LoadTextureFromFile(string filePath)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var data = File.ReadAllBytes(filePath);
|
|||
|
|
var tex = new Texture2D(2, 2);
|
|||
|
|
tex.LoadImage(data);
|
|||
|
|
return tex;
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool ExistVideo(string fileName)
|
|||
|
|
{
|
|||
|
|
var path = Path.Combine(videoLocalDir, fileName + ".mp4");
|
|||
|
|
return File.Exists(path);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal class VideoTask
|
|||
|
|
{
|
|||
|
|
public Action<string> Callback;
|
|||
|
|
public string FileName;
|
|||
|
|
public string LocalPath;
|
|||
|
|
public int MaxRetry;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal class CoverTask
|
|||
|
|
{
|
|||
|
|
public Action<Texture2D> Callback;
|
|||
|
|
public string FileName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal static class LiveVideoMemoryManager
|
|||
|
|
{
|
|||
|
|
private const int CLEANUP_INTERVAL = 30;
|
|||
|
|
private const int CLEANUP_THRESHOLD = 10;
|
|||
|
|
private static readonly float lastCleanupTime = 0f;
|
|||
|
|
private static int downloadCount;
|
|||
|
|
|
|||
|
|
public static void RequestCleanup()
|
|||
|
|
{
|
|||
|
|
downloadCount++;
|
|||
|
|
if (downloadCount >= CLEANUP_THRESHOLD ||
|
|||
|
|
Time.realtimeSinceStartup - lastCleanupTime > CLEANUP_INTERVAL)
|
|||
|
|
LiveVideoManager.Instance.StartCoroutine(CleanupCoroutine());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static IEnumerator CleanupCoroutine()
|
|||
|
|
{
|
|||
|
|
yield return null;
|
|||
|
|
// Debug.Log("[LiveVideoMemoryManager] 清理内存...");
|
|||
|
|
// yield return Resources.UnloadUnusedAssets();
|
|||
|
|
// GC.Collect();
|
|||
|
|
//
|
|||
|
|
// lastCleanupTime = Time.realtimeSinceStartup;
|
|||
|
|
// downloadCount = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|