/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.santatracker.doodles.shared.sound;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.AsyncTask;
import android.util.Log;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A manager for all of the different sounds which will be played in the games.
*/
public class SoundManager {
private static final String TAG = SoundManager.class.getSimpleName();
private static final String PREFS_NAME = "PineappleSoundManager";
private static final String IS_MUTED_PREF = "MutePref";
private static SoundManager instance;
public static boolean soundsAreMuted;
private final Object mediaPlayerLock = new Object();
private final Object soundPoolLock = new Object();
// A map from resource ID to a media player which can play the sound clip.
private Map<Integer, PineappleMediaPlayer> mediaPlayerMap;
// A map from resource ID to a sound pool containing the sound clip.
private Map<Integer, SoundPoolContainer> soundPoolMap;
// A set of resource IDs for sounds which have been individually muted. These shouldn't be unmuted
// when the mute option is toggled.
private Set<Integer> mutedSounds;
private SoundManager() {
mediaPlayerMap = new HashMap<>();
soundPoolMap = new HashMap<>();
mutedSounds = new HashSet<>();
}
public static SoundManager getInstance() {
if (instance == null) {
instance = new SoundManager();
}
return instance;
}
public void loadLongSound(Context context, int resId, boolean looping, float volume) {
if (mediaPlayerMap.containsKey(resId)) {
return;
}
PineappleMediaPlayer mediaPlayer;
if (looping) {
mediaPlayer = LoopingMediaPlayer.create(context, resId);
} else {
mediaPlayer = PineappleMediaPlayerImpl.create(context, resId);
}
mediaPlayer.setVolume(volume);
if (soundsAreMuted) {
mediaPlayer.mute();
}
synchronized (mediaPlayerLock) {
mediaPlayerMap.put(resId, mediaPlayer);
}
}
public void loadLongSound(Context context, int resId, boolean looping) {
// Make this quiet by default so that it doesn't overpower the in-game sounds.
loadLongSound(context, resId, looping, 0.1f);
}
public void loadShortSound(final Context context, final int resId, final boolean looping,
final float volume) {
if (soundPoolMap.containsKey(resId)) {
return;
}
new AsyncTask<Void, Void, Void> () {
@Override
protected Void doInBackground(Void... params) {
SoundPoolContainer container = new SoundPoolContainer(context, resId, looping, volume);
synchronized (soundPoolLock) {
soundPoolMap.put(resId, container);
}
return null;
}
}.execute();
}
public void loadShortSound(Context context, int resId) {
loadShortSound(context, resId, false, 1f);
}
public void play(int resId) {
if (mediaPlayerMap.containsKey(resId)) {
mediaPlayerMap.get(resId).start();
} else if (soundPoolMap.containsKey(resId)) {
SoundPoolContainer container = soundPoolMap.get(resId);
float vol = soundsAreMuted ? 0 : container.volume;
container.streamId =
container.soundPool.play(container.soundId, vol, vol, 1, container.looping ? -1 : 0, 1);
}
}
public void pauseAll() {
synchronized (mediaPlayerLock) {
for (int resId : mediaPlayerMap.keySet()) {
pause(resId);
}
}
synchronized (soundPoolLock) {
for (int resId : soundPoolMap.keySet()) {
pause(resId);
}
}
}
public void pause(int resId) {
if (mediaPlayerMap.containsKey(resId)) {
PineappleMediaPlayer mediaPlayer = mediaPlayerMap.get(resId);
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
} else if (soundPoolMap.containsKey(resId)) {
soundPoolMap.get(resId).soundPool.autoPause();
}
}
public void pauseShortSounds() {
synchronized (soundPoolLock) {
for (SoundPoolContainer container : soundPoolMap.values()) {
container.soundPool.autoPause();
}
}
}
public void resumeShortSounds() {
synchronized (soundPoolLock) {
for (SoundPoolContainer container : soundPoolMap.values()) {
container.soundPool.autoResume();
}
}
}
public void playLongSoundsInSequence(int[] soundIds) {
for (int i = 0; i < soundIds.length; i++) {
if (isPlayingLongSound(soundIds[i])) {
// Don't try to play long sounds which are already playing.
return;
}
}
try {
PineappleMediaPlayer[] sounds = new PineappleMediaPlayer[soundIds.length];
for (int i = 0; i < soundIds.length; i++) {
sounds[i] = mediaPlayerMap.get(soundIds[i]);
if (sounds[i] == null) {
return;
}
if (i > 0) {
sounds[i - 1].setNextMediaPlayer(sounds[i].getMediaPlayer());
}
}
sounds[0].start();
} catch (IllegalStateException e) {
Log.d(TAG, "playLongSoundsInSequence() failed: " + e.toString());
}
}
public boolean isPlayingLongSound(int resId) {
if (mediaPlayerMap.containsKey(resId)) {
return mediaPlayerMap.get(resId).isPlaying();
}
return false;
}
public void releaseAll() {
synchronized (mediaPlayerLock) {
for (PineappleMediaPlayer mediaPlayer : mediaPlayerMap.values()) {
mediaPlayer.release();
}
mediaPlayerMap.clear();
}
synchronized (soundPoolLock) {
for (SoundPoolContainer container : soundPoolMap.values()) {
container.soundPool.release();
}
soundPoolMap.clear();
}
}
public void release(int resId) {
if (mediaPlayerMap.containsKey(resId)) {
mediaPlayerMap.get(resId).release();
mediaPlayerMap.remove(resId);
}
if (soundPoolMap.containsKey(resId)) {
soundPoolMap.get(resId).soundPool.release();
soundPoolMap.remove(resId);
}
}
public void mute() {
synchronized (mediaPlayerLock) {
for (int resId : mediaPlayerMap.keySet()) {
muteInternal(resId, false);
}
}
synchronized (soundPoolLock) {
for (int resId : soundPoolMap.keySet()) {
muteInternal(resId, false);
}
}
soundsAreMuted = true;
}
public void mute(int resId) {
muteInternal(resId, true);
}
private void muteInternal(int resId, boolean addToMutedSounds) {
if (mediaPlayerMap.containsKey(resId)) {
mediaPlayerMap.get(resId).mute();
}
if (soundPoolMap.containsKey(resId)) {
SoundPoolContainer container = soundPoolMap.get(resId);
container.soundPool.setVolume(container.streamId, 0, 0);
}
if (addToMutedSounds) {
mutedSounds.add(resId);
}
}
public void unmute() {
soundsAreMuted = false;
synchronized (mediaPlayerLock) {
for (int resId : mediaPlayerMap.keySet()) {
if (!mutedSounds.contains(resId)) {
unmuteInternal(resId, false);
}
}
}
synchronized (soundPoolLock) {
for (int resId : soundPoolMap.keySet()) {
if (!mutedSounds.contains(resId)) {
unmuteInternal(resId, false);
}
}
}
}
public void unmute(int resId) {
unmuteInternal(resId, true);
}
private void unmuteInternal(int resId, boolean removeFromMutedSounds) {
if (soundsAreMuted) {
return;
}
if (mediaPlayerMap.containsKey(resId)) {
mediaPlayerMap.get(resId).unmute();
}
if (soundPoolMap.containsKey(resId)) {
SoundPoolContainer container = soundPoolMap.get(resId);
container.soundPool.setVolume(container.streamId, container.volume, container.volume);
}
if (removeFromMutedSounds) {
mutedSounds.remove(resId);
}
}
public void storeMutePreference(final Context context) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(IS_MUTED_PREF, soundsAreMuted);
editor.commit();
return null;
}
}.execute();
}
public void loadMutePreference(final Context context) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, 0);
if (sharedPreferences.getBoolean(IS_MUTED_PREF, false)) {
mute();
} else {
unmute();
}
return null;
}
}.execute();
}
private static class SoundPoolContainer {
public final SoundPool soundPool;
public final int soundId;
public final boolean looping;
public final float volume;
public int streamId;
public SoundPoolContainer(Context context, int resId, boolean looping, float volume) {
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
soundId = soundPool.load(context, resId, 1);
this.looping = looping;
this.volume = volume;
}
}
}