using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.Audio; public class BgmPlaybackManager : MonoBehaviour { public static BgmPlaybackManager Instance { get; private set; } private const string MainSceneName = "Main_main"; private const string GameplaySceneName = "gamePlay_gamePlay"; private const string BeforeEverythingSceneName = "beforeEverything"; [Header("Audio")] public AudioSource audioSource; public AudioMixerGroup outputMixerGroup; public AudioClip clip; [Tooltip("Optional Music_Data resource path for the lobby BGM. Example: Data/Music/allAlong")] public string musicDataResourcePath = "Data/Music/allAlong"; [Tooltip("Optional playlist. If empty, will load all clips in Resources/BGM.")] public List playlist = new List(); public int startIndex = 0; [Tooltip("Resources path without extension. Example: BGM/Bansonic OST")] public string resourcesPath = "BGM/Bansonic OST"; [Tooltip("Optional absolute file path to mp3/wav (fallback).")] public string fallbackFilePath = ""; public bool loop = true; public bool autoPlay = true; public string gameplaySceneName = "gamePlay_gamePlay"; public bool stopOnGameplayScene = true; private bool isLoading = false; private int currentIndex = 0; private Coroutine loadClipRoutine; public static BgmPlaybackManager EnsureInstance() { if (Instance != null) return Instance; var go = new GameObject("BGM_PlaybackManager"); Instance = go.AddComponent(); return Instance; } public void SetOutputMixerGroup(AudioMixerGroup mixerGroup) { outputMixerGroup = mixerGroup; ApplyOutputMixerGroup(); } private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); if (audioSource == null) audioSource = gameObject.AddComponent(); ApplyOutputMixerGroup(); audioSource.playOnAwake = false; audioSource.loop = loop; if (clip == null) clip = audioSource.clip; EnsurePlaylist(); if (playlist.Count > 0) { currentIndex = Mathf.Clamp(startIndex, 0, playlist.Count - 1); clip = playlist[currentIndex]; } else if (clip == null) { ResolveDefaultClip(); } if (clip != null) audioSource.clip = clip; SyncPlaybackForScene(SceneManager.GetActiveScene()); } private void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; StopLobbyAudioAndParticles(); } private void OnDestroy() { StopLobbyAudioAndParticles(); if (Instance == this) { Instance = null; } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { SyncPlaybackForScene(scene); } private void OnApplicationQuit() { StopLobbyAudioAndParticles(); } private void SyncPlaybackForScene(Scene scene) { if (!ShouldPlayInScene(scene.name)) { StopLobbyAudioAndParticles(); return; } // Allowed scenes should keep the lobby BGM alive. if (audioSource == null || !audioSource.isPlaying) EnsurePlaying(); } private bool ShouldPlayInScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) return false; if (string.Equals(sceneName, GameplaySceneName, StringComparison.OrdinalIgnoreCase)) return false; if (string.Equals(sceneName, MainSceneName, StringComparison.OrdinalIgnoreCase)) return false; if (string.Equals(sceneName, BeforeEverythingSceneName, StringComparison.OrdinalIgnoreCase)) return false; return true; } public void EnsurePlaying() { if (!ShouldPlayInScene(SceneManager.GetActiveScene().name)) { StopLobbyAudioAndParticles(); return; } if (audioSource == null) audioSource = gameObject.AddComponent(); ApplyOutputMixerGroup(); audioSource.loop = loop; if (audioSource.clip == null && clip != null) audioSource.clip = clip; if (audioSource.clip == null) { ResolveDefaultClip(); if (audioSource.clip == null && clip != null) audioSource.clip = clip; } if (audioSource.clip == null) { EnsurePlaylist(); if (playlist.Count > 0) { currentIndex = Mathf.Clamp(currentIndex, 0, playlist.Count - 1); audioSource.clip = playlist[currentIndex]; } } if (audioSource.clip == null && !isLoading) { if (loadClipRoutine != null) StopCoroutine(loadClipRoutine); loadClipRoutine = StartCoroutine(LoadClipRoutine()); return; } if (audioSource.clip != null && !audioSource.isPlaying) audioSource.Play(); } private IEnumerator LoadClipRoutine() { isLoading = true; if (audioSource.clip == null) { ResolveDefaultClip(); } // Try Resources first if (audioSource.clip == null && !string.IsNullOrEmpty(resourcesPath)) { var res = Resources.Load(resourcesPath); if (res != null) { audioSource.clip = res; } } // Fallback to file path if still missing if (audioSource.clip == null) { string path = fallbackFilePath; if (string.IsNullOrEmpty(path)) { // Default to the checked-in lobby track in Assets/BGM. var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); path = Path.Combine(projectRoot, "Assets", "BGM", "Bansonic OST.mp3"); } if (File.Exists(path)) { string uri = new System.Uri(path).AbsoluteUri; using (var req = UnityWebRequestMultimedia.GetAudioClip(uri, AudioType.MPEG)) { yield return req.SendWebRequest(); if (req.result == UnityWebRequest.Result.Success) { audioSource.clip = DownloadHandlerAudioClip.GetContent(req); } } } } isLoading = false; if (!ShouldPlayInScene(SceneManager.GetActiveScene().name)) { StopLobbyAudioAndParticles(); yield break; } if (audioSource.clip != null && !audioSource.isPlaying) audioSource.Play(); if (loadClipRoutine != null) { loadClipRoutine = null; } } private void EnsurePlaylist() { if (playlist == null) playlist = new List(); if (playlist.Count == 0) { if (ResolveDefaultClip(out AudioClip defaultClip)) { playlist.Add(defaultClip); return; } var clips = Resources.LoadAll("BGM"); if (clips != null && clips.Length > 0) { Array.Sort(clips, (a, b) => string.CompareOrdinal(a.name, b.name)); playlist.AddRange(clips); } } } private void ResolveDefaultClip() { if (clip != null) { audioSource.clip = clip; return; } if (ResolveDefaultClip(out AudioClip resolved)) { clip = resolved; audioSource.clip = resolved; } } private bool ResolveDefaultClip(out AudioClip resolved) { resolved = null; if (!string.IsNullOrEmpty(musicDataResourcePath)) { var musicData = Resources.Load(musicDataResourcePath); if (musicData != null && musicData.Clip != null) { resolved = musicData.Clip; return true; } } var musicDatas = Resources.LoadAll("Data/Music"); if (musicDatas != null) { for (int i = 0; i < musicDatas.Length; i++) { var data = musicDatas[i]; if (data != null && data.Clip != null) { resolved = data.Clip; return true; } } } return false; } public void PlayIndex(int index) { if (!ShouldPlayInScene(SceneManager.GetActiveScene().name)) { StopLobbyAudioAndParticles(); return; } EnsurePlaylist(); if (playlist == null || playlist.Count == 0) return; currentIndex = (index % playlist.Count + playlist.Count) % playlist.Count; audioSource.clip = playlist[currentIndex]; clip = audioSource.clip; audioSource.loop = loop; audioSource.time = 0f; audioSource.Play(); } public void Next() { EnsurePlaylist(); if (playlist == null || playlist.Count == 0) return; PlayIndex(currentIndex + 1); } public void Previous() { EnsurePlaylist(); if (playlist == null || playlist.Count == 0) return; PlayIndex(currentIndex - 1); } public void TogglePause() { if (audioSource == null || audioSource.clip == null) return; if (!ShouldPlayInScene(SceneManager.GetActiveScene().name)) { StopLobbyAudioAndParticles(); return; } if (audioSource.isPlaying) audioSource.Pause(); else audioSource.Play(); } private void StopLobbyAudioAndParticles() { if (loadClipRoutine != null) { StopCoroutine(loadClipRoutine); loadClipRoutine = null; } isLoading = false; if (audioSource != null) { audioSource.Stop(); } } private void ApplyOutputMixerGroup() { if (audioSource != null) { audioSource.outputAudioMixerGroup = outputMixerGroup; } } public float CurrentTime => audioSource != null ? audioSource.time : 0f; public float TotalTime => (audioSource != null && audioSource.clip != null) ? audioSource.clip.length : 0f; public bool IsReady => audioSource != null && audioSource.clip != null; }