fix:1、修改命名空间和文件夹名字
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
|
||||
using RedHotRoast;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AnimationCurveData : ScriptableObject
|
||||
{
|
||||
private static AnimationCurveData instance;
|
||||
|
||||
public static AnimationCurveData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = LoadKit.Instance.LoadAsset<AnimationCurveData>("Data.ScriptableObjectData",
|
||||
"AnimationCurveData");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationCurve LuckySpinAniCurve;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0a921ff368c47588249df420aa52091
|
||||
timeCreated: 1704964086
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace FutureCore
|
||||
{
|
||||
public static class Base64EncodeUtil
|
||||
{
|
||||
public static string Base64Encode(string source)
|
||||
{
|
||||
return Base64Encode(Encoding.UTF8, source);
|
||||
}
|
||||
|
||||
public static string Base64Decode(string result)
|
||||
{
|
||||
return Base64Decode(Encoding.UTF8, result);
|
||||
}
|
||||
|
||||
public static string Base64Encode(Encoding encoding, string source)
|
||||
{
|
||||
byte[] bytes = encoding.GetBytes(source);
|
||||
string encode = Convert.ToBase64String(bytes);
|
||||
return encode;
|
||||
}
|
||||
|
||||
public static string Base64Decode(Encoding encoding, string result)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(result);
|
||||
string decode = encoding.GetString(bytes);
|
||||
return decode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cb6f0cfcd844347bfe34d48a1f77555
|
||||
timeCreated: 1693215248
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using DG.Tweening;
|
||||
using FairyGUI;
|
||||
using RedHotRoast;
|
||||
|
||||
|
||||
public static class CommonExpand
|
||||
{
|
||||
public static GTweener FadeIn(this GObject obj, float duration = 0.3f, float delay = 0)
|
||||
{
|
||||
return CommonHelper.FadeIn(obj, duration, delay);
|
||||
}
|
||||
|
||||
public static GTweener FadeOut(this GObject obj, float duration = 0.3f, float delay = 0)
|
||||
{
|
||||
return CommonHelper.FadeOut(obj, duration, delay);
|
||||
}
|
||||
|
||||
public static void SetClick(this GObject button, Action action, bool isNetworkCheck = false,
|
||||
bool isQuickClickCheck = true)
|
||||
{
|
||||
CommonHelper.InitGButton(button, action, isNetworkCheck, isQuickClickCheck);
|
||||
}
|
||||
|
||||
public static void SafeKill(this Tween tween)
|
||||
{
|
||||
if (tween != null && tween.IsActive())
|
||||
{
|
||||
tween.Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 774c7c1119b44f86819883ab6e9240ec
|
||||
timeCreated: 1681803724
|
||||
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using FairyGUI;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RedHotRoast
|
||||
{
|
||||
public enum CountDownType
|
||||
{
|
||||
Second,
|
||||
Minute,
|
||||
Hour,
|
||||
Day
|
||||
}
|
||||
|
||||
public class CountDownKit
|
||||
{
|
||||
private int countDownTime;
|
||||
private int leftTime = 0;
|
||||
private GTextField textGComponent;
|
||||
private GProgressBar barGComponent;
|
||||
private Text textComponent;
|
||||
private CountDownType countDownType = CountDownType.Second;
|
||||
private Dictionary<int, Action> onTriggerEventDict = new Dictionary<int, Action>();
|
||||
private Tween timer;
|
||||
private Action onCompletedEvent;
|
||||
private long endTime;
|
||||
private bool isDependenceServerTime = true;
|
||||
|
||||
public CountDownKit SetTime(int second)
|
||||
{
|
||||
countDownTime = second;
|
||||
leftTime = countDownTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CountDownKit SetCountDownType(CountDownType countDownType)
|
||||
{
|
||||
this.countDownType = countDownType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CountDownKit SetText(GTextField gText)
|
||||
{
|
||||
textComponent = null;
|
||||
textGComponent = gText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CountDownKit SetBar(GProgressBar gProgressBar)
|
||||
{
|
||||
barGComponent = null;
|
||||
barGComponent = gProgressBar;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int interval = 1;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
endTime = (int)(DateTimeManager.Instance.GetServerCurrTimestamp() / 1000) + leftTime;
|
||||
UpdateView();
|
||||
timer = DOVirtual.DelayedCall(interval, () =>
|
||||
{
|
||||
leftTime -= interval;
|
||||
if (isDependenceServerTime)
|
||||
{
|
||||
var current = DateTimeManager.Instance.GetServerCurrTimestamp() / 1000;
|
||||
if (endTime <= current)
|
||||
{
|
||||
leftTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateView();
|
||||
if (leftTime <= 0)
|
||||
{
|
||||
Stop();
|
||||
OnCompleted();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnTrigger();
|
||||
}
|
||||
}, true).SetLoops(-1);
|
||||
}
|
||||
|
||||
private void UpdateView()
|
||||
{
|
||||
UpdateText();
|
||||
UpdateBar();
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (timer != null)
|
||||
{
|
||||
timer.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (timer != null)
|
||||
{
|
||||
timer.Play();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (timer != null && timer.IsActive())
|
||||
{
|
||||
timer?.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
Stop();
|
||||
leftTime = 0;
|
||||
|
||||
textGComponent = null;
|
||||
|
||||
textComponent = null;
|
||||
onTriggerEventDict = null;
|
||||
onCompletedEvent = null;
|
||||
}
|
||||
|
||||
public void ConsumeTime(int consumeTime)
|
||||
{
|
||||
leftTime -= consumeTime;
|
||||
UpdateView();
|
||||
if (leftTime <= 0)
|
||||
{
|
||||
Stop();
|
||||
OnCompleted();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnTrigger();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTrigger()
|
||||
{
|
||||
if (onTriggerEventDict.ContainsKey(leftTime))
|
||||
{
|
||||
onTriggerEventDict[leftTime]?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearTriggerEvent()
|
||||
{
|
||||
onTriggerEventDict.Clear();
|
||||
}
|
||||
|
||||
public CountDownKit SetCompletedEvent(Action onCompleted)
|
||||
{
|
||||
onCompletedEvent = onCompleted;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void OnCompleted()
|
||||
{
|
||||
onCompletedEvent?.Invoke();
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
{
|
||||
if (textGComponent != null)
|
||||
{
|
||||
textGComponent.text = CommonHelper.TimeFormat(leftTime, countDownType);
|
||||
}
|
||||
|
||||
if (textComponent != null)
|
||||
{
|
||||
textComponent.text = CommonHelper.TimeFormat(leftTime, countDownType);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBar()
|
||||
{
|
||||
if (barGComponent != null)
|
||||
{
|
||||
if (barGComponent.max == 0)
|
||||
{
|
||||
barGComponent.max = countDownTime;
|
||||
}
|
||||
|
||||
barGComponent.TweenValue(leftTime, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetLeftTime()
|
||||
{
|
||||
return leftTime;
|
||||
}
|
||||
|
||||
public CountDownKit SetServerTimeDependence(bool _isDependenceServerTime)
|
||||
{
|
||||
isDependenceServerTime = _isDependenceServerTime;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34ed4a143324499ab1864f3c64019e16
|
||||
timeCreated: 1681804392
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
public class FXPool<T> : IDisposable where T : Enum
|
||||
{
|
||||
private Dictionary<T, List<Object>> PoolDic;
|
||||
private Dictionary<T, Transform> PollPar;
|
||||
private List<T> temp;
|
||||
private Transform objectPoolPar;
|
||||
public Func<T, Object> NewObjFunc;
|
||||
public Action<T, Object> RecObjFunc;
|
||||
public Action<T, Object> GetObjFunc;
|
||||
|
||||
public FXPool(Transform _objectPoolPar = null)
|
||||
{
|
||||
objectPoolPar = _objectPoolPar;
|
||||
if (objectPoolPar == null)
|
||||
{
|
||||
objectPoolPar = new GameObject("ObjectPool").transform;
|
||||
objectPoolPar.localPosition = Vector3.zero;
|
||||
objectPoolPar.localEulerAngles = Vector3.zero;
|
||||
}
|
||||
|
||||
PoolDic = new Dictionary<T, List<Object>>();
|
||||
PollPar = new Dictionary<T, Transform>();
|
||||
temp = new List<T>();
|
||||
}
|
||||
|
||||
public Obj GetObject<Obj>(T key) where Obj : Object
|
||||
{
|
||||
Obj obj = null;
|
||||
if (!PoolDic.ContainsKey(key))
|
||||
{
|
||||
AddKey(key);
|
||||
}
|
||||
|
||||
if (PoolDic[key].Count > 0)
|
||||
{
|
||||
obj = PoolDic[key][0] as Obj;
|
||||
PoolDic[key].RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = LoadObject<Obj>(key);
|
||||
}
|
||||
|
||||
GetObjFunc?.Invoke(key, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
private void AddKey(T key)
|
||||
{
|
||||
PoolDic.Add(key, new List<Object>());
|
||||
Transform par = new GameObject(key.ToString()).transform;
|
||||
par.SetParent(objectPoolPar);
|
||||
par.localScale = Vector3.one;
|
||||
par.localPosition = Vector3.zero;
|
||||
par.localEulerAngles = Vector3.zero;
|
||||
PollPar.Add(key, par.transform);
|
||||
}
|
||||
|
||||
private Obj LoadObject<Obj>(T key) where Obj : UnityEngine.Object
|
||||
{
|
||||
Obj obj = NewObjFunc?.Invoke(key) as Obj;
|
||||
if (obj != null)
|
||||
{
|
||||
GameObject go = obj as GameObject ?? (obj as Component).gameObject;
|
||||
|
||||
if (PollPar.TryGetValue(key, out Transform par) && go != null)
|
||||
{
|
||||
go.transform.SetParent(par);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public void RecObject(T key, Object obj)
|
||||
{
|
||||
if (obj == null) return;
|
||||
if (!PoolDic.ContainsKey(key))
|
||||
{
|
||||
PoolDic.Add(key, new List<Object>());
|
||||
}
|
||||
|
||||
GameObject go = obj as GameObject ?? (obj as Component).gameObject;
|
||||
if (PollPar.TryGetValue(key, out Transform par))
|
||||
{
|
||||
go.transform.parent = (par);
|
||||
}
|
||||
|
||||
RecObjFunc?.Invoke(key, obj);
|
||||
PoolDic[key].Add(obj);
|
||||
}
|
||||
|
||||
public void RemoveKey(T key)
|
||||
{
|
||||
if (PoolDic.ContainsKey(key))
|
||||
{
|
||||
foreach (Object item in PoolDic[key])
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
GameObject go = item as GameObject;
|
||||
if (go == null)
|
||||
{
|
||||
Component cot = item as Component;
|
||||
go = cot.gameObject;
|
||||
}
|
||||
|
||||
GameObject.Destroy(go);
|
||||
}
|
||||
}
|
||||
|
||||
PoolDic[key].Clear();
|
||||
PoolDic.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
temp.Clear();
|
||||
foreach (var item in PoolDic)
|
||||
{
|
||||
temp.Add(item.Key);
|
||||
}
|
||||
|
||||
foreach (var item in temp)
|
||||
{
|
||||
RemoveKey(item);
|
||||
}
|
||||
|
||||
temp.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
RemoveAll();
|
||||
PoolDic = null;
|
||||
PollPar = null;
|
||||
temp = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3de141293f3240ffbdc26570a545e96c
|
||||
timeCreated: 1681806263
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RedHotRoast
|
||||
{
|
||||
public static class LegendConstant
|
||||
{
|
||||
public const string croas = "Assets/RedHotRoastAssets/";
|
||||
public const string setbun = ".assetbundle";
|
||||
public static readonly string tbund = $"{Application.dataPath}/../AssetBundle/{undles}";
|
||||
public static readonly string fgklpk = "|";
|
||||
public const string undles = "AssetBundles";
|
||||
public const string nifest = ".manifest";
|
||||
public static readonly string lesest = $"{undles}{nifest}";
|
||||
public const string zyzootx = "LoveLegendFile.txt";
|
||||
public const string nifldg = "[ LoveLegend ]";
|
||||
public const string admsie = "4s2f6ac15sa6ds45";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73ce1d7d9d7bd5245878994917b71208
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,565 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FairyGUI;
|
||||
using SGModule.Common.Base;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace RedHotRoast
|
||||
{
|
||||
public class LiveVideoManager : SingletonMonoBehaviour<LiveVideoManager>
|
||||
{
|
||||
public static string videoBaseUrl = "";
|
||||
|
||||
// 封面缓存
|
||||
// private readonly Dictionary<string, Texture2D> coverCache = new();
|
||||
|
||||
// 封面提取队列(避免同时开多个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]);
|
||||
callback?.Invoke(a.nativeTexture as Texture2D);
|
||||
}, "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(389, 583, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8989cb844e004fd8b83f722f9b5ee575
|
||||
timeCreated: 1754887895
|
||||
@@ -0,0 +1,205 @@
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace RedHotRoast
|
||||
{
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public delegate void onLoaded(AssetBundle param);
|
||||
|
||||
public class LoveLegendInfo
|
||||
{
|
||||
public string assetBundleName;
|
||||
|
||||
public List<string> parentABNameList = new();
|
||||
|
||||
public long assetBundleSize;
|
||||
|
||||
|
||||
public AssetBundle assetBundle { get; internal set; }
|
||||
|
||||
public LoveLegendState assetBundleState;
|
||||
|
||||
public onLoaded ONLoaded;
|
||||
|
||||
public long waitUnloadCurrentTime;
|
||||
|
||||
public Action<Action<bool>> unloadAction;
|
||||
|
||||
public Action<bool> unloadCompletedAction;
|
||||
|
||||
public int waitUnloadTime;
|
||||
|
||||
|
||||
public static List<string> StatisticsCacheAssetList = new List<string>();
|
||||
|
||||
|
||||
private static bool isStatisticsCacheAssetList = false;
|
||||
|
||||
|
||||
public int ReferencedCount { get; set; }
|
||||
|
||||
|
||||
public float LastReferencedTimestamp { get; set; }
|
||||
|
||||
public LoveLegendInfo(string assetBundleName, UnityAction<AssetBundle> onCompletedLoaded)
|
||||
{
|
||||
this.assetBundleName = assetBundleName;
|
||||
assetBundleState = LoveLegendState.STATE_LOADING;
|
||||
Referenced(assetBundleName);
|
||||
LastReferencedTimestamp = Time.realtimeSinceStartup;
|
||||
if (onCompletedLoaded != null)
|
||||
{
|
||||
ONLoaded += a => onCompletedLoaded(a);
|
||||
}
|
||||
}
|
||||
|
||||
#region 引用关系
|
||||
|
||||
public int GetReferenced()
|
||||
{
|
||||
return ReferencedCount;
|
||||
}
|
||||
|
||||
|
||||
public void Referenced(string parentAbName)
|
||||
{
|
||||
parentAbName = parentAbName.ToLower();
|
||||
if (!parentABNameList.Contains(parentAbName))
|
||||
{
|
||||
ReferencedCount++;
|
||||
parentABNameList.Add(parentAbName);
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void UnReferenced(string parentAbName)
|
||||
{
|
||||
if (parentABNameList.Contains(parentAbName))
|
||||
{
|
||||
ReferencedCount--;
|
||||
parentABNameList.Remove(parentAbName);
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanReferenced()
|
||||
{
|
||||
ReferencedCount = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void callRes(string parentAbName, UnityAction<AssetBundle> action)
|
||||
{
|
||||
switch (assetBundleState)
|
||||
{
|
||||
case LoveLegendState.STATE_NONE:
|
||||
break;
|
||||
case LoveLegendState.STATE_LOADING:
|
||||
ONLoaded += a => { action(a); };
|
||||
break;
|
||||
case LoveLegendState.STATE_LOADED:
|
||||
Referenced(parentAbName);
|
||||
action(this.assetBundle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoaded(string parentAbName, AssetBundle assetBundle)
|
||||
{
|
||||
Referenced(parentAbName);
|
||||
this.assetBundle = assetBundle;
|
||||
this.assetBundleState = LoveLegendState.STATE_LOADED;
|
||||
ONLoaded(assetBundle);
|
||||
ONLoaded = null;
|
||||
OnAddtStatisticsCacheAssetList(assetBundle.name);
|
||||
}
|
||||
|
||||
public void Unload(bool isThorough)
|
||||
{
|
||||
CleanReferenced();
|
||||
if (assetBundle != null)
|
||||
assetBundle.Unload(isThorough);
|
||||
assetBundle = null;
|
||||
}
|
||||
|
||||
|
||||
private void OnAddtStatisticsCacheAssetList(string assetBundleName)
|
||||
{
|
||||
if (!isStatisticsCacheAssetList) return;
|
||||
if (!StatisticsCacheAssetList.Contains(assetBundleName))
|
||||
{
|
||||
StatisticsCacheAssetList.Add(assetBundleName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void OnStartStatisticsCacheAssetList()
|
||||
{
|
||||
isStatisticsCacheAssetList = true;
|
||||
StatisticsCacheAssetList.Clear();
|
||||
}
|
||||
|
||||
|
||||
public static string[] OnStopStatisticsCacheAssetList()
|
||||
{
|
||||
isStatisticsCacheAssetList = false;
|
||||
string[] tempList = StatisticsCacheAssetList.GetRange(0, StatisticsCacheAssetList.Count).ToArray();
|
||||
StatisticsCacheAssetList.Clear();
|
||||
return tempList;
|
||||
}
|
||||
|
||||
|
||||
public void UpdateWaitUnloadCurrentTime()
|
||||
{
|
||||
waitUnloadCurrentTime = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
|
||||
public long GetWaitUpdateTimeTD()
|
||||
{
|
||||
var tempTime = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
|
||||
return tempTime - waitUnloadCurrentTime;
|
||||
}
|
||||
|
||||
public void SetUnloadAction(Action<Action<bool>> action, Action<bool> onCompletedAction)
|
||||
{
|
||||
this.unloadAction = action;
|
||||
this.unloadCompletedAction = onCompletedAction;
|
||||
}
|
||||
|
||||
public void InvokeUnloadAction()
|
||||
{
|
||||
unloadAction?.Invoke(unloadCompletedAction);
|
||||
}
|
||||
|
||||
public void SetWaitUnloadTime(int time)
|
||||
{
|
||||
waitUnloadTime = time;
|
||||
}
|
||||
|
||||
public int GetWaitUnloadTime()
|
||||
{
|
||||
return waitUnloadTime;
|
||||
}
|
||||
|
||||
public void SetAssetBundleSize(long size)
|
||||
{
|
||||
if (size > 0)
|
||||
{
|
||||
assetBundleSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
public long GetSize()
|
||||
{
|
||||
return assetBundleSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f8e1bf1569620a45ba3a117072e9b57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,853 @@
|
||||
namespace RedHotRoast
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.Events;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Object = UnityEngine.Object;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
public class LoveLegendKit : Singleton<LoveLegendKit>, ILoadAsset
|
||||
{
|
||||
private const bool IsMD5Encrypt = false;
|
||||
|
||||
|
||||
private string _assetBundleRootPath = LegendFileKit.GetFilePath();
|
||||
|
||||
|
||||
private static readonly Dictionary<string, LoveLegendInfo> _cacheAssetBundleInfoDict =
|
||||
new Dictionary<string, LoveLegendInfo>();
|
||||
|
||||
|
||||
private static readonly Dictionary<string, string[]> _cacheDependencyDict = new Dictionary<string, string[]>();
|
||||
|
||||
private static readonly List<LoveLegendInfo> _waitUnloadList = new List<LoveLegendInfo>();
|
||||
|
||||
private static AssetBundleManifest _manifest;
|
||||
|
||||
private static readonly Dictionary<string, string> decryptAssetBundleDict = new Dictionary<string, string>();
|
||||
|
||||
|
||||
private const int UnloadWaitTime = 30;
|
||||
|
||||
#region 加载AssetBundle的基础方法
|
||||
|
||||
public IEnumerator LoadABFromFileAsync(string assetBundlePath, string bundlePassword,
|
||||
UnityAction<AssetBundle> onCompleted)
|
||||
{
|
||||
yield return LoadDecryptAssetBundle(assetBundlePath, bundlePassword, onCompleted);
|
||||
}
|
||||
|
||||
private IEnumerator LoadDecryptAssetBundle(string assetBundlePath, string bundlePassword,
|
||||
UnityAction<AssetBundle> onCompleted)
|
||||
{
|
||||
string decryptPath;
|
||||
if (decryptAssetBundleDict.TryGetValue(assetBundlePath, out var outPath) && File.Exists(outPath))
|
||||
{
|
||||
decryptPath = outPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
var cachePath1 = Path.Combine(UnityEngine.Application.persistentDataPath, "Jarvis");
|
||||
if (!Directory.Exists(cachePath1))
|
||||
{
|
||||
Directory.CreateDirectory(cachePath1);
|
||||
}
|
||||
|
||||
var cachePath = Path.Combine(cachePath1, $"decryptedBundle{Guid.NewGuid():N}");
|
||||
|
||||
|
||||
using (Stream stream1 = File.OpenWrite(cachePath))
|
||||
{
|
||||
var decryptedData = AESForFileKit.DecryptToBytes(assetBundlePath, bundlePassword, out var dataSize);
|
||||
yield return stream1.WriteAsync(decryptedData, 0, dataSize);
|
||||
}
|
||||
|
||||
decryptPath = cachePath;
|
||||
decryptAssetBundleDict.TryAdd(assetBundlePath, decryptPath);
|
||||
}
|
||||
|
||||
var request = AssetBundle.LoadFromFileAsync(decryptPath);
|
||||
yield return request;
|
||||
onCompleted?.Invoke(request.assetBundle);
|
||||
}
|
||||
|
||||
private AssetBundle LoadDecryptAssetBundleSync(string assetBundlePath, string bundlePassword)
|
||||
{
|
||||
string decryptPath;
|
||||
if (decryptAssetBundleDict.TryGetValue(assetBundlePath, out var outPath) && File.Exists(outPath))
|
||||
{
|
||||
decryptPath = outPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
var decryptedData = AESForFileKit.DecryptToBytes(assetBundlePath, bundlePassword, out var dataSize);
|
||||
var cachePath1 = Path.Combine(UnityEngine.Application.persistentDataPath, "Jarvis");
|
||||
if (!Directory.Exists(cachePath1))
|
||||
{
|
||||
Directory.CreateDirectory(cachePath1);
|
||||
}
|
||||
|
||||
var cachePath = Path.Combine(cachePath1, $"decryptedBundle{Guid.NewGuid():N}");
|
||||
while (File.Exists(cachePath))
|
||||
{
|
||||
File.Delete(cachePath);
|
||||
cachePath = Path.Combine(cachePath1, $"decryptedBundle{Guid.NewGuid():N}");
|
||||
}
|
||||
|
||||
using (Stream stream = File.OpenWrite(cachePath))
|
||||
{
|
||||
stream.Write(decryptedData, 0, dataSize);
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
decryptPath = cachePath;
|
||||
decryptAssetBundleDict.TryAdd(assetBundlePath, decryptPath);
|
||||
}
|
||||
|
||||
var assetBundle = AssetBundle.LoadFromFile(decryptPath);
|
||||
return assetBundle;
|
||||
}
|
||||
|
||||
private IEnumerator LoadAssetFromAssetBundle<T>(AssetBundle assetBundle, string assetName,
|
||||
UnityAction<T> onLoadCompleted) where T : Object
|
||||
{
|
||||
T resultAsset = null;
|
||||
|
||||
if (typeof(T) == typeof(Sprite))
|
||||
{
|
||||
var texture2D = assetBundle.LoadAsset<Texture2D>(assetName);
|
||||
if (texture2D != null)
|
||||
{
|
||||
var sprite = Sprite.Create(texture2D,
|
||||
new Rect(0, 0, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f));
|
||||
|
||||
resultAsset = sprite as T;
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] assetNameArray = assetName.Split('.');
|
||||
if (assetNameArray.Length > 1)
|
||||
{
|
||||
var resultAsset1 = assetBundle.LoadAssetWithSubAssets<T>(assetNameArray[0]);
|
||||
resultAsset = Array.Find(resultAsset1, item => item.name == assetNameArray[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var assetLoadRequest = assetBundle.LoadAssetAsync<T>(assetName);
|
||||
yield return assetLoadRequest;
|
||||
var gameObject = assetLoadRequest.asset;
|
||||
if (gameObject == null)
|
||||
{
|
||||
var subAssetLoadRequest = assetBundle.LoadAssetWithSubAssetsAsync<T>(assetName);
|
||||
yield return subAssetLoadRequest;
|
||||
resultAsset = subAssetLoadRequest.asset as T;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultAsset = gameObject as T;
|
||||
}
|
||||
}
|
||||
|
||||
onLoadCompleted?.Invoke(resultAsset);
|
||||
}
|
||||
|
||||
private void LoadAssetInternal(string assetBundleName, UnityAction<AssetBundle> onCompleted)
|
||||
{
|
||||
var assetBundlePath = GenerateAssetBundlePath(assetBundleName);
|
||||
|
||||
GetAssetBundle(assetBundlePath, assetBundleName, onCompleted);
|
||||
}
|
||||
|
||||
public AssetBundle GetAssetBundle(string assetBundlePath, string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
CrazyAsyKit.StopAction(assetBundleName + "Unload");
|
||||
UnWaitUnload(assetBundleName);
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
CacheDependency(assetBundleName, new[] { assetBundleName });
|
||||
|
||||
var LoveLegendInfo = GetCacheAssetBundle(assetBundleName);
|
||||
return LoveLegendInfo?.assetBundle;
|
||||
}
|
||||
else
|
||||
{
|
||||
var LoveLegendInfo = new LoveLegendInfo(assetBundleName, null);
|
||||
CacheAssetBundle(assetBundleName, LoveLegendInfo);
|
||||
var isFileLoadType = File.Exists(assetBundlePath);
|
||||
if (isFileLoadType)
|
||||
{
|
||||
GetAssetBundleDependency(assetBundleName);
|
||||
var assetBundle =
|
||||
LoadDecryptAssetBundleSync(assetBundlePath, LegendConstant.admsie);
|
||||
LoveLegendInfo.assetBundle = assetBundle;
|
||||
return assetBundle;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void GetAssetBundle(string assetBundlePath, string assetBundleName, UnityAction<AssetBundle> onCompleted)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
CrazyAsyKit.StopAction(assetBundleName + "Unload");
|
||||
UnWaitUnload(assetBundleName);
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
CacheDependency(assetBundleName, new[] { assetBundleName });
|
||||
|
||||
var LoveLegendInfo = GetCacheAssetBundle(assetBundleName);
|
||||
LoveLegendInfo?.callRes(assetBundleName, onCompleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
var LoveLegendInfo = new LoveLegendInfo(assetBundleName, onCompleted);
|
||||
CacheAssetBundle(assetBundleName, LoveLegendInfo);
|
||||
var isFileLoadType = File.Exists(assetBundlePath);
|
||||
CrazyAsyKit.StartCoroutine(GetAssetBundleDependency(assetBundleName, delegate
|
||||
{
|
||||
if (isFileLoadType)
|
||||
{
|
||||
CrazyAsyKit.StartCoroutine(LoadABFromFileAsync(assetBundlePath,
|
||||
LegendConstant.admsie, bundle =>
|
||||
{
|
||||
if (bundle == null)
|
||||
{
|
||||
Debug.LogError("加载AssetBundle 失败:" + assetBundleName);
|
||||
UnCacheAssetBundle(assetBundleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoveLegendInfo abInfo = GetCacheAssetBundle(assetBundleName);
|
||||
#if UNITY_EDITOR
|
||||
FileInfo file = new FileInfo(assetBundlePath);
|
||||
|
||||
abInfo.SetAssetBundleSize(file.Length);
|
||||
#endif
|
||||
abInfo.onLoaded(assetBundleName, bundle);
|
||||
}
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
CrazyAsyKit.StartCoroutine(LoadABFromFileAsync(assetBundlePath,
|
||||
LegendConstant.admsie, delegate (AssetBundle bundle)
|
||||
{
|
||||
if (bundle == null)
|
||||
{
|
||||
Debug.LogError("加载AssetBundle 失败:" + assetBundleName);
|
||||
UnCacheAssetBundle(assetBundleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var abInfoV = GetCacheAssetBundle(assetBundleName);
|
||||
var file = new FileInfo(assetBundlePath);
|
||||
abInfoV.SetAssetBundleSize(file.Length);
|
||||
abInfoV.onLoaded(assetBundleName, bundle);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#region AssetBundle 包的依赖相关
|
||||
|
||||
private IEnumerator GetAssetBundleDependency(string assetBundleName, Action action)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
|
||||
var allDependencies = new List<string>() { assetBundleName };
|
||||
allDependencies.AddRange(GetABDependency(assetBundleName));
|
||||
var index = 0;
|
||||
CacheDependency(assetBundleName, allDependencies.ToArray());
|
||||
var allDepends = allDependencies.Where(dependencyAbName => dependencyAbName != assetBundleName).ToList();
|
||||
|
||||
foreach (var dependencyAbName in allDepends)
|
||||
{
|
||||
yield return LoadDependencyAssetBundle(assetBundleName, dependencyAbName, delegate { index++; });
|
||||
}
|
||||
|
||||
while (index != allDepends.Count)
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
action?.Invoke();
|
||||
}
|
||||
|
||||
private void GetAssetBundleDependency(string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
|
||||
var allDependencies = new List<string>() { assetBundleName };
|
||||
allDependencies.AddRange(GetABDependency(assetBundleName));
|
||||
CacheDependency(assetBundleName, allDependencies.ToArray());
|
||||
var allDepends = allDependencies.Where(dependencyAbName => dependencyAbName != assetBundleName).ToList();
|
||||
|
||||
foreach (var dependencyAbName in allDepends)
|
||||
{
|
||||
LoadDependencyAssetBundleSync(assetBundleName, dependencyAbName);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator LoadDependencyAssetBundle(string parentAssetBundleName, string assetBundleName,
|
||||
UnityAction<AssetBundle> onCompleted)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
var assetBundlePath = GenerateAssetBundlePath(assetBundleName, IsMD5Encrypt);
|
||||
|
||||
CrazyAsyKit.StopAction(assetBundleName + "Unload");
|
||||
UnWaitUnload(assetBundleName);
|
||||
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
CacheAssetBundle(assetBundleName, null, true);
|
||||
var LoveLegendInfo = GetCacheAssetBundle(assetBundleName);
|
||||
LoveLegendInfo?.callRes(parentAssetBundleName, onCompleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
var LoveLegendInfo = new LoveLegendInfo(assetBundleName, onCompleted);
|
||||
CacheAssetBundle(assetBundleName, LoveLegendInfo);
|
||||
if (File.Exists(assetBundlePath))
|
||||
{
|
||||
yield return LoadABFromFileAsync(assetBundlePath, LegendConstant.admsie,
|
||||
delegate (AssetBundle bundle)
|
||||
{
|
||||
if (bundle == null)
|
||||
{
|
||||
Debug.LogError("加载AssetBundle 失败:" + assetBundleName);
|
||||
UnCacheAssetBundle(assetBundleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetCacheAssetBundle(assetBundleName).onLoaded(parentAssetBundleName, bundle);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
CrazyAsyKit.StartCoroutine(LoadABFromFileAsync(assetBundlePath,
|
||||
LegendConstant.admsie, delegate (AssetBundle bundle)
|
||||
{
|
||||
if (bundle == null)
|
||||
{
|
||||
Debug.LogError("加载AssetBundle 失败:" + assetBundleName);
|
||||
UnCacheAssetBundle(assetBundleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetCacheAssetBundle(assetBundleName).onLoaded(parentAssetBundleName, bundle);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AssetBundle LoadDependencyAssetBundleSync(string parentAssetBundleName, string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
var assetBundlePath = GenerateAssetBundlePath(assetBundleName, IsMD5Encrypt);
|
||||
CrazyAsyKit.StopAction(assetBundleName + "Unload");
|
||||
UnWaitUnload(assetBundleName);
|
||||
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
CacheAssetBundle(assetBundleName, null, true);
|
||||
var assetBundleInfos = GetCacheAssetBundle(assetBundleName);
|
||||
return assetBundleInfos?.assetBundle;
|
||||
}
|
||||
|
||||
var LoveLegendInfo = new LoveLegendInfo(assetBundleName, null);
|
||||
CacheAssetBundle(assetBundleName, LoveLegendInfo);
|
||||
if (File.Exists(assetBundlePath))
|
||||
{
|
||||
GetAssetBundleDependency(assetBundleName);
|
||||
var assetBundle =
|
||||
LoadDecryptAssetBundleSync(assetBundlePath, LegendConstant.admsie);
|
||||
LoveLegendInfo.assetBundle = assetBundle;
|
||||
return assetBundle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private string[] GetABDependency(string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
return GetManifest().GetDirectDependencies(assetBundleName);
|
||||
}
|
||||
|
||||
#region 依赖关系相关
|
||||
|
||||
private bool IsCacheDependency(string assetBundleName)
|
||||
{
|
||||
if (_cacheDependencyDict == null || _cacheDependencyDict.Count == 0) return false;
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
var isDependency = _cacheDependencyDict.ContainsKey(assetBundleName);
|
||||
|
||||
return isDependency;
|
||||
}
|
||||
|
||||
|
||||
private bool IsOtherDependency(string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
var isDependency = false;
|
||||
if (_cacheDependencyDict == null || _cacheDependencyDict.Count == 0) return false;
|
||||
foreach (var keyValuePair in _cacheDependencyDict)
|
||||
{
|
||||
isDependency = keyValuePair.Value.Contains(assetBundleName);
|
||||
if (isDependency)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isDependency;
|
||||
}
|
||||
|
||||
private void CacheDependency(string assetBundleName, string[] assetBundleNameArray)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
if (!IsCacheDependency(assetBundleName))
|
||||
{
|
||||
for (int i = 0; i < assetBundleNameArray.Length; i++)
|
||||
{
|
||||
assetBundleNameArray[i] = assetBundleNameArray[i].ToLower();
|
||||
}
|
||||
|
||||
_cacheDependencyDict.Add(assetBundleName, assetBundleNameArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UnCacheDependency(string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
if (IsCacheDependency(assetBundleName))
|
||||
{
|
||||
_cacheDependencyDict.Remove(assetBundleName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private string[] GetCacheDependency(string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
string[] dependencyList = { };
|
||||
if (IsCacheDependency(assetBundleName))
|
||||
{
|
||||
dependencyList = _cacheDependencyDict[assetBundleName];
|
||||
}
|
||||
|
||||
return dependencyList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 管理已加载的AssetBundle文件
|
||||
|
||||
private void CacheAssetBundle(string assetBundleName, LoveLegendInfo assetBundleInfoV,
|
||||
bool isDependency = false)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
if (isDependency)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_cacheAssetBundleInfoDict.Add(assetBundleName, assetBundleInfoV);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UnCacheAssetBundle(string assetBundleName)
|
||||
{
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
_cacheAssetBundleInfoDict.Remove(assetBundleName.ToLower());
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private LoveLegendInfo GetCacheAssetBundle(string assetBundleName)
|
||||
{
|
||||
LoveLegendInfo assetBundle = null;
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
assetBundle = _cacheAssetBundleInfoDict[assetBundleName.ToLower()];
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
return assetBundle;
|
||||
}
|
||||
|
||||
|
||||
private bool IsCacheAssetBundle(string assetBundleName)
|
||||
{
|
||||
assetBundleName = assetBundleName.ToLower();
|
||||
if (_cacheAssetBundleInfoDict == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cacheAssetBundleInfoDict.ContainsKey(assetBundleName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 卸载AssetBundle相关
|
||||
|
||||
#region 卸载AssetBundle相关信息管理
|
||||
|
||||
private bool WaitUnload(LoveLegendInfo assetBundleInfoV)
|
||||
{
|
||||
var isInWait = IsWaitUnload(assetBundleInfoV.assetBundleName);
|
||||
if (isInWait) return true;
|
||||
assetBundleInfoV.UpdateWaitUnloadCurrentTime();
|
||||
_waitUnloadList.Add(assetBundleInfoV);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private bool IsWaitUnloadKey(string assetBundleName)
|
||||
{
|
||||
var isWaitUnload = false;
|
||||
if (_waitUnloadList == null || _waitUnloadList.Count <= 0) return false;
|
||||
foreach (var bundleInfo in _waitUnloadList)
|
||||
{
|
||||
if (bundleInfo == null || !string.IsNullOrEmpty(bundleInfo.assetBundleName)) continue;
|
||||
if (!bundleInfo.assetBundleName.Equals(assetBundleName)) continue;
|
||||
isWaitUnload = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return isWaitUnload;
|
||||
}
|
||||
|
||||
|
||||
private bool IsWaitUnload(string assetBundleName)
|
||||
{
|
||||
var isWaitUnload = false;
|
||||
if (_waitUnloadList == null || _waitUnloadList.Count == 0) return false;
|
||||
if (!IsWaitUnloadKey(assetBundleName))
|
||||
{
|
||||
foreach (var waitUnloadAssetBundle in _waitUnloadList)
|
||||
{
|
||||
if (waitUnloadAssetBundle == null ||
|
||||
!string.IsNullOrEmpty(waitUnloadAssetBundle.assetBundleName)) continue;
|
||||
isWaitUnload = GetABDependency(waitUnloadAssetBundle.assetBundleName).Contains(assetBundleName);
|
||||
if (isWaitUnload)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isWaitUnload = true;
|
||||
}
|
||||
|
||||
return isWaitUnload;
|
||||
}
|
||||
|
||||
|
||||
private bool UnWaitUnload(string assetBundleName)
|
||||
{
|
||||
var isRemove = false;
|
||||
|
||||
foreach (var bundleInfo in _waitUnloadList)
|
||||
{
|
||||
if (bundleInfo == null) continue;
|
||||
if (!bundleInfo.assetBundleName.Equals(assetBundleName)) continue;
|
||||
isRemove = _waitUnloadList.Remove(bundleInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
return isRemove;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public void UnloadAssetBundle(string assetBundleName,
|
||||
bool ignoreDependency = false,
|
||||
int unloadWaitTime = 0,
|
||||
Action<bool> onCompleted = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetBundleName))
|
||||
{
|
||||
onCompleted?.Invoke(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool unloadSucceed;
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
var abInfo = GetCacheAssetBundle(assetBundleName);
|
||||
if (abInfo == null)
|
||||
{
|
||||
onCompleted?.Invoke(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (WaitUnload(abInfo) && !ignoreDependency)
|
||||
{
|
||||
unloadSucceed = false;
|
||||
|
||||
UnCacheDependency(assetBundleName);
|
||||
|
||||
onCompleted?.Invoke(unloadSucceed);
|
||||
}
|
||||
else
|
||||
{
|
||||
CrazyAsyKit.StopAction(assetBundleName + "Unload");
|
||||
CrazyAsyKit.StartAction(assetBundleName + "Unload", delegate
|
||||
{
|
||||
unloadSucceed = IsCacheAssetBundle(assetBundleName) &&
|
||||
UnloadAssetBundleInternal(assetBundleName, ignoreDependency);
|
||||
|
||||
UnWaitUnload(assetBundleName);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#endif
|
||||
|
||||
onCompleted?.Invoke(unloadSucceed);
|
||||
}, unloadWaitTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
onCompleted?.Invoke(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool UnloadAssetBundleInternal(string assetBundleName, bool isIgnoreDependency = false,
|
||||
bool isThorough = false)
|
||||
{
|
||||
var LoveLegendInfo = GetCacheAssetBundle(assetBundleName);
|
||||
if (LoveLegendInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var dependencyArray = GetCacheDependency(assetBundleName);
|
||||
|
||||
|
||||
UnCacheDependency(assetBundleName);
|
||||
|
||||
|
||||
if (!isIgnoreDependency)
|
||||
{
|
||||
if (IsOtherDependency(assetBundleName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UnCacheAssetBundle(assetBundleName);
|
||||
|
||||
|
||||
LoveLegendInfo.Unload(isThorough);
|
||||
|
||||
|
||||
foreach (var dependencyAssetBundleName in dependencyArray)
|
||||
{
|
||||
if (dependencyAssetBundleName == assetBundleName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!IsOtherDependency(dependencyAssetBundleName))
|
||||
{
|
||||
UnloadAssetBundleInternal(dependencyAssetBundleName, false, isThorough);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 封装直接加载资源的方法
|
||||
|
||||
public T GetAsset<T>(string assetBundleName, string assetName) where T : Object
|
||||
{
|
||||
var assetBundlePath = GenerateAssetBundlePath(assetBundleName);
|
||||
var assetBundle = GetAssetBundle(assetBundlePath, assetBundleName);
|
||||
|
||||
return assetBundle.LoadAsset<T>(assetName);
|
||||
}
|
||||
|
||||
|
||||
public void GetAsset<T>(string assetBundleName, string assetName, UnityAction<T> onCompleted) where T : Object
|
||||
{
|
||||
LoadAssetInternal(assetBundleName, delegate (AssetBundle bundle)
|
||||
{
|
||||
if (bundle == null)
|
||||
{
|
||||
Debug.LogError("加载AssetBundle 失败:" + assetBundleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
CrazyAsyKit.StartCoroutine(LoadAssetFromAssetBundle(bundle, assetName, onCompleted));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void InitManifest(string assetBundleManifestName, UnityAction<bool> onCompleted)
|
||||
{
|
||||
CrazyAsyKit.StopAction(assetBundleManifestName + "Unload");
|
||||
if (UnWaitUnload(assetBundleManifestName))
|
||||
{
|
||||
}
|
||||
var assetBundles =
|
||||
AssetBundle.LoadFromFile($"{LegendFileKit.GetFilePath()}{LegendConstant.undles}");
|
||||
var manifest = assetBundles.LoadAsset<AssetBundleManifest>("assetbundlemanifest");
|
||||
_manifest = manifest;
|
||||
onCompleted?.Invoke(manifest != null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void RecycleAssetBundle(string assetBundleName, UnityAction onCompleted = null)
|
||||
{
|
||||
if (IsCacheAssetBundle(assetBundleName))
|
||||
{
|
||||
var abInfo = GetCacheAssetBundle(assetBundleName);
|
||||
abInfo.UnReferenced(assetBundleName);
|
||||
|
||||
RecycleAssetBundleDependency(assetBundleName, UnloadWaitTime);
|
||||
|
||||
if (abInfo.GetReferenced() <= 0)
|
||||
{
|
||||
UnloadAssetBundle(assetBundleName, false, UnloadWaitTime);
|
||||
}
|
||||
}
|
||||
|
||||
onCompleted?.Invoke();
|
||||
}
|
||||
|
||||
private void RecycleAssetBundleDependency(string abName, int waitTime)
|
||||
{
|
||||
var otherAbName = GetCacheDependency(abName);
|
||||
if (otherAbName.Length <= 0) return;
|
||||
foreach (var dependAbName in otherAbName)
|
||||
{
|
||||
if (dependAbName == abName) continue;
|
||||
var dependAbInfo = GetCacheAssetBundle(dependAbName);
|
||||
if (dependAbInfo == null) continue;
|
||||
dependAbInfo.UnReferenced(abName);
|
||||
|
||||
|
||||
if (dependAbInfo.GetReferenced() <= 0)
|
||||
{
|
||||
UnloadAssetBundle(dependAbName, false, waitTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region 其他方法
|
||||
|
||||
private string GenerateAssetBundlePath(string assetBundleName, bool isMD5 = false, bool isManifest = false,
|
||||
bool isAddABSuffix = true)
|
||||
{
|
||||
var fileName = assetBundleName.ToLower();
|
||||
if (isMD5)
|
||||
{
|
||||
fileName = MD5String(fileName) + ".data";
|
||||
}
|
||||
|
||||
var filePath = _assetBundleRootPath.Replace("file:///", "") + assetBundleName.ToLower();
|
||||
if (isAddABSuffix)
|
||||
{
|
||||
filePath += LegendConstant.setbun;
|
||||
}
|
||||
|
||||
if (!isManifest)
|
||||
return filePath;
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private static string MD5String(string str)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
var hash = md5.ComputeHash(Encoding.GetEncoding("utf-8").GetBytes(str));
|
||||
var tmp = new StringBuilder();
|
||||
foreach (var i in hash)
|
||||
{
|
||||
tmp.Append(i.ToString("x2"));
|
||||
}
|
||||
|
||||
return tmp.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void RecycleAsset(string assetUrl, UnityAction onCompleted)
|
||||
{
|
||||
RecycleAssetBundle(assetUrl, onCompleted);
|
||||
}
|
||||
|
||||
private AssetBundleManifest GetManifest()
|
||||
{
|
||||
if (_manifest == null)
|
||||
{
|
||||
Debug.LogError("AssetBundleManifest is null");
|
||||
var assetBundles =
|
||||
AssetBundle.LoadFromFile($"{LegendFileKit.GetFilePath()}{LegendConstant.undles}");
|
||||
var manifest = assetBundles.LoadAsset<AssetBundleManifest>("assetbundlemanifest");
|
||||
_manifest = manifest;
|
||||
}
|
||||
|
||||
return _manifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0049894e09758c34fb23600e4571504e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace RedHotRoast
|
||||
{
|
||||
public enum LoveLegendState
|
||||
{
|
||||
STATE_NONE,
|
||||
STATE_LOADING,
|
||||
STATE_LOADED,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e6f867b844a92f4e8799eeb338a9aa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
public class MD5Kit
|
||||
{
|
||||
|
||||
public static string GetFileMD5(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fs = new FileStream(file, FileMode.Open);
|
||||
MD5 md5 = new MD5CryptoServiceProvider();
|
||||
var retVal = md5.ComputeHash(fs);
|
||||
fs.Close();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var str in retVal)
|
||||
{
|
||||
sb.Append(str.ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("GetFileMD5 fail error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字符串的MD5值
|
||||
/// </summary>
|
||||
public static string GetStringMD5(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MD5 md5 = new MD5CryptoServiceProvider();
|
||||
byte[] bytResult = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
|
||||
string strResult = BitConverter.ToString(bytResult);
|
||||
strResult = strResult.Replace("-", string.Empty);
|
||||
return strResult;
|
||||
}
|
||||
|
||||
|
||||
public static string MD5String1(string text)
|
||||
{
|
||||
var buffer = Encoding.Default.GetBytes(text);
|
||||
var check = new MD5CryptoServiceProvider();
|
||||
var somme = check.ComputeHash(buffer);
|
||||
var result = new StringBuilder();
|
||||
foreach (var a in somme)
|
||||
{
|
||||
var value = a.ToString("X");
|
||||
if (a < 16)
|
||||
{
|
||||
result.Append(0);
|
||||
result.Append(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString().ToLower();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5697bf9fd10d9548977711311a96a00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using DG.Tweening;
|
||||
using FairyGUI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace RedHotRoast
|
||||
{
|
||||
public static class VideoPlayerHandover
|
||||
{
|
||||
private static VideoPlayer currentPlayer;
|
||||
private static Transform originalParent;
|
||||
private static GLoader originalLoader;
|
||||
|
||||
/// <summary>
|
||||
/// 接管播放器,切换父物体和绑定loader
|
||||
/// </summary>
|
||||
public static void TakeOver(VideoPlayer player, Transform newParent, GLoader newLoader, Action callback = null)
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
currentPlayer = player;
|
||||
|
||||
// 只记录第一次的父物体和loader(方便后面还原)
|
||||
if (originalParent == null)
|
||||
originalParent = player.transform.parent;
|
||||
if (originalLoader == null && newLoader != null)
|
||||
originalLoader = newLoader;
|
||||
|
||||
// 切父物体
|
||||
player.transform.SetParent(newParent, false);
|
||||
|
||||
// 绑定到新loader显示
|
||||
// if (newLoader != null)
|
||||
// {
|
||||
// newLoader.texture = new NTexture(player.targetTexture);
|
||||
// newLoader.visible = true;
|
||||
// }
|
||||
|
||||
DOTween.To(()=>0, _=> {
|
||||
if (player.targetTexture != null)
|
||||
{
|
||||
newLoader.texture = new NTexture(player.targetTexture);
|
||||
newLoader.visible = true;
|
||||
}
|
||||
callback?.Invoke();
|
||||
}, 0, 0.05f); // 延迟0.05s
|
||||
|
||||
// 播放状态不变,不暂停,不跳时间
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 归还播放器到原父物体和loader
|
||||
/// </summary>
|
||||
public static void Return()
|
||||
{
|
||||
if (currentPlayer == null) return;
|
||||
|
||||
// 切回原父物体
|
||||
currentPlayer.transform.SetParent(originalParent, false);
|
||||
|
||||
// 绑定回原loader显示
|
||||
if (originalLoader != null)
|
||||
{
|
||||
if (currentPlayer.targetTexture == null)
|
||||
{
|
||||
originalLoader.visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
originalLoader.texture = new NTexture(currentPlayer.targetTexture);
|
||||
originalLoader.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 播放状态保持不变
|
||||
|
||||
// 清除引用,下次接管会重新记录
|
||||
currentPlayer = null;
|
||||
originalParent = null;
|
||||
originalLoader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e60986dda5a45cbadba87e621556718
|
||||
timeCreated: 1754903656
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
public class VideoPlayerPool
|
||||
{
|
||||
private static VideoPlayerPool _instance;
|
||||
public static VideoPlayerPool Instance => _instance ??= new VideoPlayerPool();
|
||||
|
||||
private readonly Queue<VideoPlayer> pool = new Queue<VideoPlayer>();
|
||||
private GameObject parent;
|
||||
private int maxCount = 8;
|
||||
|
||||
// 已经分配出去的播放器(方便管理)
|
||||
private readonly HashSet<VideoPlayer> inUsePlayers = new HashSet<VideoPlayer>();
|
||||
|
||||
private VideoPlayerPool()
|
||||
{
|
||||
}
|
||||
|
||||
public void Init(GameObject parentObj, int maxPoolCount)
|
||||
{
|
||||
parent = parentObj;
|
||||
maxCount = maxPoolCount;
|
||||
|
||||
// 先清理旧的
|
||||
foreach (var player in pool)
|
||||
{
|
||||
if (player != null) GameObject.Destroy(player.gameObject);
|
||||
}
|
||||
|
||||
pool.Clear();
|
||||
inUsePlayers.Clear();
|
||||
|
||||
// 创建初始播放器
|
||||
for (int i = 0; i < maxCount; i++)
|
||||
{
|
||||
var go = new GameObject($"VideoPlayer_{i}");
|
||||
go.transform.SetParent(parent.transform);
|
||||
var player = go.AddComponent<VideoPlayer>();
|
||||
|
||||
player.playOnAwake = false;
|
||||
player.renderMode = VideoRenderMode.RenderTexture;
|
||||
player.name = $"VideoPlayer_{i}";
|
||||
|
||||
pool.Enqueue(player);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取播放器
|
||||
public VideoPlayer GetPlayer()
|
||||
{
|
||||
if (pool.Count == 0)
|
||||
{
|
||||
maxCount++;
|
||||
Debug.Log("RendererList----VideoPlayerPool空了,分配第 " + maxCount + " 个播放器");
|
||||
return null;
|
||||
// var go = new GameObject($"VideoPlayer_{maxCount - 1}");
|
||||
// go.transform.SetParent(parent.transform);
|
||||
// var player2 = go.AddComponent<VideoPlayer>();
|
||||
//
|
||||
// player2.playOnAwake = false;
|
||||
// player2.renderMode = VideoRenderMode.RenderTexture;
|
||||
// player2.name = $"VideoPlayer_{maxCount - 1}";
|
||||
//
|
||||
// pool.Enqueue(player2);
|
||||
}
|
||||
|
||||
var player = pool.Dequeue();
|
||||
inUsePlayers.Add(player);
|
||||
player.gameObject.SetActive(true);
|
||||
return player;
|
||||
}
|
||||
|
||||
// 归还播放器
|
||||
public void ReturnPlayer(VideoPlayer player)
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
if (inUsePlayers.Contains(player))
|
||||
{
|
||||
player.Stop();
|
||||
player.clip = null;
|
||||
|
||||
if (player.targetTexture != null)
|
||||
{
|
||||
player.targetTexture.Release();
|
||||
GameObject.Destroy(player.targetTexture);
|
||||
player.targetTexture = null;
|
||||
}
|
||||
|
||||
player.gameObject.SetActive(false);
|
||||
inUsePlayers.Remove(player);
|
||||
pool.Enqueue(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("尝试归还未被分配的播放器");
|
||||
}
|
||||
}
|
||||
|
||||
// 释放所有
|
||||
public void DisposeAll()
|
||||
{
|
||||
foreach (var player in pool)
|
||||
{
|
||||
if (player != null) GameObject.Destroy(player.gameObject);
|
||||
}
|
||||
|
||||
foreach (var player in inUsePlayers)
|
||||
{
|
||||
if (player != null) GameObject.Destroy(player.gameObject);
|
||||
}
|
||||
|
||||
pool.Clear();
|
||||
inUsePlayers.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1141a1b5d7c646838e101453b632358d
|
||||
timeCreated: 1754646767
|
||||
Reference in New Issue
Block a user