diff --git a/Assets/OxGFrame/CHANGELOG.md b/Assets/OxGFrame/CHANGELOG.md index 8feb4f8a..d2b4ed38 100644 --- a/Assets/OxGFrame/CHANGELOG.md +++ b/Assets/OxGFrame/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## [2.10.3] -2024-04-01 +- Added CacheType to AudioManager, which will be used for caching when AudioBase is using the request method. +- Modified AudioBase to not rely on the methods of the OxGKit.Utilities' Requester. +- Modified Acax encoding type (use UTF-8). + ## [2.10.2] - 2024-03-19 - Fixed When scene load with suspend (activateOnLoad = false) cannot return BundlePack correctly . ```C# diff --git a/Assets/OxGFrame/MediaFrame/Scripts/Editor/AudioBaseEditor.cs b/Assets/OxGFrame/MediaFrame/Scripts/Editor/AudioBaseEditor.cs index 630c8c02..b2bf0ea7 100644 --- a/Assets/OxGFrame/MediaFrame/Scripts/Editor/AudioBaseEditor.cs +++ b/Assets/OxGFrame/MediaFrame/Scripts/Editor/AudioBaseEditor.cs @@ -1,4 +1,5 @@ -using OxGFrame.MediaFrame.AudioFrame; +using Cysharp.Threading.Tasks; +using OxGFrame.MediaFrame.AudioFrame; using UnityEditor; using UnityEngine; @@ -36,39 +37,41 @@ protected virtual async void DrawAudioLengthView() GUI.backgroundColor = Color.cyan; serializedObject.Update(); - EditorGUILayout.PropertyField(this._audioLength); if (GUILayout.Button("Preload")) { // must set dirty (save will be success) EditorUtility.SetDirty(this._target); - AudioClip audioClip = null; - switch (this._target.sourceType) + UniTask.Void(async () => { - case SourceType.Audio: - audioClip = this._target.audioClip; - if (audioClip != null) this._audioLength.floatValue = this._target.audioLength = this._target.audioClip.length; - else Debug.LogError("Cannot found AudioClip"); - break; - - case SourceType.StreamingAssets: - audioClip = await this._target.GetAudioFromStreamingAssets(false); - if (audioClip != null) this._audioLength.floatValue = this._target.audioLength = audioClip.length; - break; - - case SourceType.Url: - audioClip = await this._target.GetAudioFromURL(false); - if (audioClip != null) this._audioLength.floatValue = this._target.audioLength = audioClip.length; - break; - } - - if (audioClip != null) - { - Debug.Log($"AudioClip Info => Channel: {audioClip.channels}, Frequency: {audioClip.frequency}, Sample: {audioClip.samples}, Length: {audioClip.length}, State: {audioClip.loadState}"); - } - - serializedObject.ApplyModifiedProperties(); + AudioClip audioClip = null; + switch (this._target.sourceType) + { + case SourceType.Audio: + audioClip = this._target.audioClip; + if (audioClip != null) this._audioLength.floatValue = this._target.audioLength = this._target.audioClip.length; + else Debug.LogError("Cannot found AudioClip"); + break; + + case SourceType.StreamingAssets: + audioClip = await this._target.GetAudioFromStreamingAssets(false); + if (audioClip != null) this._audioLength.floatValue = this._target.audioLength = audioClip.length; + break; + + case SourceType.Url: + audioClip = await this._target.GetAudioFromURL(false); + if (audioClip != null) this._audioLength.floatValue = this._target.audioLength = audioClip.length; + break; + } + + if (audioClip != null) + Debug.Log($"AudioClip Info => Channel: {audioClip.channels}, Frequency: {audioClip.frequency}, Sample: {audioClip.samples}, Length: {audioClip.length}, State: {audioClip.loadState}"); + else + Debug.Log($"AudioClip request failed!!!"); + + serializedObject.ApplyModifiedProperties(); + }); } else { diff --git a/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioBase.cs b/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioBase.cs index e0761aa8..55b93d7a 100644 --- a/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioBase.cs +++ b/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioBase.cs @@ -1,7 +1,6 @@ using Cysharp.Threading.Tasks; using MyBox; using OxGKit.LoggingSystem; -using OxGKit.Utilities.Request; using UnityEngine; using UnityEngine.Audio; @@ -19,7 +18,7 @@ public class AudioBase : MediaBase [Tooltip("Drag audio clip. This is not supports [WebGL]"), ConditionalField(nameof(sourceType), false, SourceType.Audio)] public AudioClip audioClip = null; // SourceType => StreamingAssets, Url - [Tooltip("Depends on Requester.InitCacheCapacityForAudio"), ConditionalField(nameof(sourceType), true, SourceType.Audio)] + [Tooltip("Can select the \"CacheType\" from the AudioManager's inspector."), ConditionalField(nameof(sourceType), true, SourceType.Audio)] public bool requestCached = true; // SourceType => StreamingAssets [Tooltip("Default path is [StreamingAssets]. Just set that inside path and file name, Don't forget file name must with extension, ex: Audio/example.mp3"), ConditionalField(nameof(sourceType), false, SourceType.StreamingAssets)] @@ -99,7 +98,7 @@ private async UniTask _InitAudio() public async UniTask GetAudioFromStreamingAssets(bool cached) { string pathName = System.IO.Path.Combine(GetRequestStreamingAssetsPath(), this.fullPathName); - var audioClip = await Requester.RequestAudio(pathName, this.audioFileType, null, null, null, cached); + var audioClip = await AudioManager.GetInstance().RequestAudio(pathName, this.audioFileType, null, null, null, cached); return audioClip; } @@ -108,7 +107,7 @@ public async UniTask GetAudioFromURL(bool cached) string urlCfg = await this.urlSet.urlCfg.GetFileText(); string urlSet = this.urlSet.getUrlPathFromCfg ? GetValueFromUrlCfg(urlCfg, AUDIO_URLSET) : string.Empty; string url = (!string.IsNullOrEmpty(urlSet)) ? $"{urlSet.Trim()}{this.urlSet.url.Trim()}" : this.urlSet.url.Trim(); - var audioClip = await Requester.RequestAudio(url, this.audioFileType, null, null, null, cached); + var audioClip = await AudioManager.GetInstance().RequestAudio(url, this.audioFileType, null, null, null, cached); return audioClip; } diff --git a/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioManager.cs b/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioManager.cs index ff6ddb64..518107e7 100644 --- a/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioManager.cs +++ b/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/AudioFrame/AudioManager.cs @@ -1,21 +1,39 @@ using Cysharp.Threading.Tasks; using MyBox; using OxGKit.LoggingSystem; +using OxGKit.Utilities.Cacher; using System; using System.Collections.Generic; +using System.Threading; using UnityEngine; using UnityEngine.Audio; +using UnityEngine.Networking; namespace OxGFrame.MediaFrame.AudioFrame { internal class AudioManager : MediaManager { + public enum CacheType + { + None, + LRU, + ARC + } + [Separator("Audio Mixer")] [SerializeField, Tooltip("Setup AudioMixer in list")] private List _listMixer = new List(); // 中控混音器 private Dictionary _dictMixerExpParams = new Dictionary(); // 用於記錄 Exposed Parameters 參數 private Dictionary _dictNodes = new Dictionary(); // 節點物件 + [Separator("Audio Cacher")] + [SerializeField, Tooltip("The caching method requires ensuring that \"RequestCached\" is checked on AudioBase.")] + private CacheType _cacheType = CacheType.LRU; + [SerializeField, Tooltip("The capacity of the cache."), ConditionalField(nameof(_cacheType), true, CacheType.None)] + private int _cacheCapacity = 20; + private ARCCache _arcAudios = null; + private LRUCache _lruAudios = null; + private static readonly object _locker = new object(); private static AudioManager _instance = null; public static AudioManager GetInstance() @@ -51,6 +69,18 @@ private void Awake() this._dictNodes.Add(nodeName, this.CreateNode(nodeName, this.transform)); } } + + switch (this._cacheType) + { + case CacheType.None: + break; + case CacheType.LRU: + this._InitLRUCacheCapacityForAudio(this._cacheCapacity); + break; + case CacheType.ARC: + this._InitARCCacheCapacityForAudio(this._cacheCapacity); + break; ; + } } protected override void SetParent(AudioBase audBase, Transform parent) @@ -62,6 +92,147 @@ protected override void SetParent(AudioBase audBase, Transform parent) } } + #region Cacher + #region ARC Audio + private void _InitARCCacheCapacityForAudio(int capacity) + { + if (this._arcAudios == null) + this._arcAudios = new ARCCache(capacity); + + // Only allow one cache type (Clear LRU) + this._ClearLRUCacheCapacityForAudio(); + this._lruAudios = null; + } + + private void _ClearARCCacheCapacityForAudio() + { + if (this._arcAudios != null) + this._arcAudios.Clear(); + } + #endregion + + #region LRU Audio + private void _InitLRUCacheCapacityForAudio(int capacity) + { + if (this._lruAudios == null) + this._lruAudios = new LRUCache(capacity); + + // Only allow one cache type (Clear ARC) + this._ClearARCCacheCapacityForAudio(); + _arcAudios = null; + } + + private void _ClearLRUCacheCapacityForAudio() + { + if (this._lruAudios != null) + this._lruAudios.Clear(); + } + #endregion + + /// + /// Audio request + /// + /// + /// + /// + /// + /// + /// + public async UniTask RequestAudio(string url, UnityEngine.AudioType audioType = UnityEngine.AudioType.MPEG, Action successAction = null, Action errorAction = null, CancellationTokenSource cts = null, bool cached = true) + { + if (string.IsNullOrEmpty(url)) + { + Logging.Print($"Request failed, URL is null or empty."); + return null; + } + + if (cached) + { + // ARCCache + if (this._arcAudios != null) + { + AudioClip audioClip = this._arcAudios.Get(url); + if (audioClip != null) return audioClip; + } + // LRUCache + else if (this._lruAudios != null) + { + AudioClip audioClip = this._lruAudios.Get(url); + if (audioClip != null) return audioClip; + } + } + + UnityWebRequest request = null; + try + { + request = UnityWebRequestMultimedia.GetAudioClip(url, audioType); + ((DownloadHandlerAudioClip)request.downloadHandler).streamAudio = true; + + if (cts != null) await request.SendWebRequest().WithCancellation(cts.Token); + else await request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.ProtocolError || + request.result == UnityWebRequest.Result.ConnectionError) + { + request.Dispose(); + errorAction?.Invoke(); + Logging.Print($"Request failed, URL: {url}"); + return null; + } + + AudioClip audioClip = ((DownloadHandlerAudioClip)request.downloadHandler).audioClip; + if (cached) + { + // ARCCache + if (this._arcAudios != null) + { + this._arcAudios.Add(url, audioClip); + audioClip = this._arcAudios.Get(url); + } + // LRUCache + else if (this._lruAudios != null) + { + this._lruAudios.Add(url, audioClip); + audioClip = this._lruAudios.Get(url); + } + } + successAction?.Invoke(audioClip); + +#if UNITY_EDITOR + string GetBytesToString(ulong bytes) + { + if (bytes < (1024 * 1024 * 1f)) + { + return (bytes / 1024f).ToString("f2") + "KB"; + } + else if (bytes >= (1024 * 1024 * 1f) && bytes < (1024 * 1024 * 1024 * 1f)) + { + return (bytes / (1024 * 1024 * 1f)).ToString("f2") + "MB"; + } + else + { + return (bytes / (1024 * 1024 * 1024 * 1f)).ToString("f2") + "GB"; + } + } + + ulong sizeBytes = (ulong)request.downloadHandler.data.Length; + Logging.Print($"Request Audio => Channel: {audioClip.channels}, Frequency: {audioClip.frequency}, Sample: {audioClip.samples}, Length: {audioClip.length}, State: {audioClip.loadState}, Size: {GetBytesToString(sizeBytes)}"); +#endif + + request.Dispose(); + return audioClip; + } + catch (Exception ex) + { + request?.Dispose(); + errorAction?.Invoke(); + Logging.Print($"Request failed, URL: {url}"); + Logging.PrintException(ex); + return null; + } + } + #endregion + #region 中控 Mixer /// /// 依照 Mixer 的名稱與其中的 ExposedParam 合併成雙 key, 執行自動記錄 @@ -209,7 +380,7 @@ private void _Play(AudioBase audBase, int loops, float volume) // 處理長期沒有被 Unload 的 Audio if (!audBase.onDestroyAndUnload) this.TryLRUCache(audBase.assetName); - + this.LoadAndPlay(audBase, loops, volume); Logging.Print(string.Format("Play Audio: {0}, Current Length: {1} (s)", audBase?.mediaName, audBase?.CurrentLength())); diff --git a/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/Implement/MediaBase.cs b/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/Implement/MediaBase.cs index 21effe52..4f032638 100644 --- a/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/Implement/MediaBase.cs +++ b/Assets/OxGFrame/MediaFrame/Scripts/Runtime/Core/Implement/MediaBase.cs @@ -1,7 +1,6 @@ using Cysharp.Threading.Tasks; using MyBox; using OxGKit.LoggingSystem; -using OxGKit.Utilities.Request; using System; using System.Collections.Generic; using UnityEngine; @@ -54,7 +53,7 @@ public async UniTask GetFileText() case RequestType.StreamingAssets: string pathName = System.IO.Path.Combine(GetRequestStreamingAssetsPath(), this.fullPathName); - if (string.IsNullOrEmpty(_urlCfgContent)) _urlCfgContent = await Requester.RequestText(pathName, null, null, null, false); + if (string.IsNullOrEmpty(_urlCfgContent)) _urlCfgContent = await OxGKit.Utilities.Request.Requester.RequestText(pathName, null, null, null, false); return _urlCfgContent; } diff --git a/Assets/OxGFrame/package.json b/Assets/OxGFrame/package.json index 19f5bbe5..265d3835 100644 --- a/Assets/OxGFrame/package.json +++ b/Assets/OxGFrame/package.json @@ -2,7 +2,7 @@ "name": "com.michaelo.oxgframe", "displayName": "OxGFrame", "description": "The OxGFrame is a framework based on Unity for accelerating game development. Supports multi-platform Win, OSX, Android, iOS, WebGL.", - "version": "2.10.2", + "version": "2.10.3", "unity": "2021.3", "license": "MIT", "samples": [