/* * AudioPlayer for Unity on Android and iOS * * https://www.spaceflint.com/?p=91 * * Public domain, free to use however you wish. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using UnityEngine; using System.Collections; using System.Collections.Generic; public class AudioPlayer : MonoBehaviour { public int numChannels = 4; public bool sound = true; static AudioPlayer instance; void Start() { if (instance == null) { instance = this; Initialize(); } } void OnDestroy() { Destroy(); } public static bool GetSound() { return instance.sound; } public static void SetSound(bool sound) { instance.sound = sound; } #if UNITY_ANDROID && !UNITY_EDITOR struct AudioPlayerData { public int id; public float time; public bool flag; }; static Dictionary dictionary; static AudioPlayerData[] streams; static AndroidJavaObject androidSoundPool; static AndroidJavaObject androidMediaPlayer; static bool paused; void Initialize() { dictionary = new Dictionary(); streams = new AudioPlayerData[numChannels]; int maxStreams = numChannels; int streamType = 3; // android.media.AudioManager.STREAM_MUSIC int srcQuality = 0; androidSoundPool = new AndroidJavaObject( "android.media.SoundPool", maxStreams, streamType, srcQuality); androidMediaPlayer = new AndroidJavaObject("android.media.MediaPlayer"); } void Destroy() { if (androidMediaPlayer != null) { androidMediaPlayer.Call("release"); androidMediaPlayer.Dispose(); androidMediaPlayer = null; } if (androidSoundPool != null) { androidSoundPool.Call("release"); androidSoundPool.Dispose(); androidSoundPool = null; } streams = null; dictionary = null; } void Update() { if ((! instance.sound) || paused) return; float dt = Time.deltaTime; if (dt == 0f) return; for (int channel = 0; channel < instance.numChannels; ++channel) { if (streams[channel].id != 0) { streams[channel].time -= dt; if (streams[channel].time < 0f) streams[channel].time = 0f; } } } public static void Load(string name) { if (androidSoundPool != null && androidMediaPlayer != null && (! dictionary.ContainsKey(name))) { using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { using (AndroidJavaObject activity = unityPlayer.GetStatic("currentActivity")) { using (AndroidJavaObject assetManager = activity.Call("getAssets")) { using (AndroidJavaObject assetFileDescriptor = assetManager.Call("openFd", name + ".mp3")) { // // get duration of clip in seconds, for use by IsPlaying() // float time = 1f; // default time long offset = assetFileDescriptor.Call("getStartOffset"); long length = assetFileDescriptor.Call("getLength"); using (AndroidJavaObject fileDescriptor = assetFileDescriptor.Call("getFileDescriptor")) { androidMediaPlayer.Call("reset"); androidMediaPlayer.Call("setDataSource", fileDescriptor, offset, length); androidMediaPlayer.Call("prepare"); int duration = androidMediaPlayer.Call("getDuration"); time = duration / 1000f; } // // // int priority = 1; int id = androidSoundPool.Call("load", assetFileDescriptor, priority); if (id != 0) { AudioPlayerData data = new AudioPlayerData(); data.id = id; data.time = time; data.flag = true; dictionary.Add(name, data); } } } } } } } public static void Play(int channel, string name, float volume = 1f, bool loop = false) { if (androidSoundPool != null) { Stop(channel); if (instance.sound) { AudioPlayerData data; if (dictionary.TryGetValue(name, out data) && data.flag) { int priority = 0; int loop_int = (loop ? -1 : 0); float pitch = 1f; int id = androidSoundPool.Call("play", data.id, volume, volume, priority, loop_int, pitch); if (id != 0) { streams[channel].id = id; streams[channel].time = (loop ? Mathf.Infinity : data.time); } // // according to the Android SoundPool API, the pitch parameter // has a valid range of 0.5 to 2.0. but practically, pitch is // not supported correctly in all Android devices. for these // reasons, we always use pitch 1, i.e. normal play speed. // } } } } public static void Stop(int channel) { if ((androidSoundPool != null) && (streams[channel].id != 0)) { androidSoundPool.Call("stop", streams[channel].id); streams[channel].id = 0; streams[channel].time = 0f; } } public static bool IsPlaying(int channel) { if ((androidSoundPool != null) && (streams[channel].time != 0f)) return true; else return false; } public static void Pause() { if ((androidSoundPool != null) && (! paused)) { paused = true; androidSoundPool.Call("autoPause"); } } public static void Resume() { if ((androidSoundPool != null) && paused) { paused = false; if (instance.sound) androidSoundPool.Call("autoResume"); else { for (int channel = 0; channel < instance.numChannels; ++channel) Stop(channel); } } } #else static Dictionary dictionary; static AudioSource[] audioSources; void Initialize() { #if UNITY_EDITOR // make sure that in Project Settings > Audio, // the "DSP Buffer Size" is set to "Best latency" AudioConfiguration config = AudioSettings.GetConfiguration(); Debug.Assert(config.dspBufferSize <= 256, "DSP buffer size: " + config.dspBufferSize); #endif dictionary = new Dictionary (); audioSources = new AudioSource[numChannels]; for (int channel = 0; channel < numChannels; ++channel) { audioSources[channel] = gameObject.AddComponent(); if (audioSources[channel] != null) { audioSources[channel].playOnAwake = false; audioSources[channel].bypassEffects = true; audioSources[channel].loop = false; } } } void Destroy() { for (int channel = 0; channel < numChannels; ++channel) { if (audioSources[channel] != null) Destroy(audioSources[channel]); audioSources[channel] = null; } foreach (KeyValuePair clip in dictionary) { if (! clip.Value.preloadAudioData) clip.Value.UnloadAudioData(); } dictionary.Clear(); } public static void Load(string name) { if (! dictionary.ContainsKey(name)) { string path = "file://" + Application.streamingAssetsPath + "/" + name + ".mp3"; instance.StartCoroutine(instance.Load2(path, name)); } } IEnumerator Load2(string path, string name) { // www loading must be done in a coroutine WWW www = new WWW(path); yield return www; AudioClip clip = www.GetAudioClipCompressed(false); if (clip != null && clip.loadState == AudioDataLoadState.Loaded) { clip.name = name; dictionary.Add(name, clip); } #if UNITY_EDITOR else Debug.LogError("load clip " + name); #endif } public static void Play(int channel, string name, float volume = 1f, bool loop = false) { if ((audioSources[channel] != null) && instance.sound) { AudioClip clip; if (dictionary.TryGetValue(name, out clip)) { audioSources[channel].clip = clip; audioSources[channel].volume = volume; audioSources[channel].pitch = 1f; // see note in Play() for Android audioSources[channel].loop = loop; audioSources[channel].Play(); } #if UNITY_EDITOR else if (Time.timeSinceLevelLoad > 2f) Debug.LogError("play clip " + name); #endif } } public static void Stop(int channel) { if (audioSources[channel] != null) { audioSources[channel].Stop(); } } public static bool IsPlaying(int channel) { if (audioSources[channel] != null) return audioSources[channel].isPlaying; else return false; } public static void Pause() { for (int channel = 0; channel < instance.numChannels; ++channel) { if (audioSources[channel] != null) audioSources[channel].Pause(); } } public static void Resume() { for (int channel = 0; channel < instance.numChannels; ++channel) { if (audioSources[channel] != null) { if (instance.sound) audioSources[channel].UnPause(); else audioSources[channel].Stop(); } } } #endif }