All Downloads are FREE. Search and download functionalities are using the official Maven repository.

playn.android.AndroidAudio Maven / Gradle / Ivy

/**
 * Copyright 2011 The PlayN Authors
 *
 * 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 playn.android;

import java.io.FileDescriptor;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer;
import android.media.SoundPool;

import playn.core.Audio;
import playn.core.SoundImpl;

public class AndroidAudio extends Audio {

  protected interface Resolver {
    void resolve(AndroidSound sound);
  }

  final AndroidPlatform plat;
  private final Set> playing = new HashSet>();
  private final Map loadingSounds = new HashMap();
  private final SoundPool pool;

  private class PooledSound extends SoundImpl {
    public final int soundId;
    private int streamId;

    public PooledSound(int soundId) {
      super(plat.exec());
      this.soundId = soundId;
    }

    @Override
    public String toString() {
      return "pooled:" + soundId;
    }

    @Override
    protected boolean playingImpl() {
      return false; // no way to tell
    }

    @Override
    protected boolean playImpl() {
      streamId = pool.play(soundId, volume, volume, 1, looping ? -1 : 0, 1);
      return (streamId != 0);
    }

    @Override
    protected boolean prepareImpl() {
      pool.play(soundId, 0, 0, 0, 0, 1);
      return true; // Well, it's not prepared, but preparING...
    }

    @Override
    protected void stopImpl() {
      if (streamId != 0) {
        pool.stop(streamId);
        streamId = 0;
      }
    }

    @Override
    protected void setLoopingImpl(boolean looping) {
      if (streamId != 0) {
        pool.setLoop(streamId, looping ? -1 : 0);
      }
    }

    @Override
    protected void setVolumeImpl(float volume) {
      if (streamId != 0) {
        pool.setVolume(streamId, volume, volume);
      }
    }

    @Override
    protected void releaseImpl() {
      pool.unload(soundId);
    }
  }

  public AndroidAudio(final AndroidPlatform plat) {
    this.plat = plat;
    this.pool = new SoundPool(plat.activity.maxSimultaneousSounds(),
                              AudioManager.STREAM_MUSIC, 0);
    this.pool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
      public void onLoadComplete(SoundPool soundPool, int soundId, int status) {
        PooledSound sound = loadingSounds.remove(soundId);
        if (sound == null) {
          plat.log().warn("Got load complete for unknown sound [id=" + soundId + "]");
        } else if (status == 0) {
          sound.succeed(soundId);
        } else {
          sound.fail(new Exception("Sound load failed [errcode=" + status + "]"));
        }
      }
    });
  }

  /**
   * Creates a sound instance from the supplied asset file descriptor.
   */
  public SoundImpl createSound(AssetFileDescriptor fd) {
    PooledSound sound = new PooledSound(pool.load(fd, 1));
    loadingSounds.put(sound.soundId, sound);
    return sound;
  }

  /**
   * Creates a sound instance from the supplied file descriptor offset.
   */
  public SoundImpl createSound(FileDescriptor fd, long offset, long length) {
    PooledSound sound = new PooledSound(pool.load(fd, offset, length, 1));
    loadingSounds.put(sound.soundId, sound);
    return sound;
  }

  SoundImpl createSound(final String path) {
    try {
      return createSound(plat.assets().openAssetFd(path));
    } catch (IOException ioe) {
      PooledSound sound = new PooledSound(0);
      sound.fail(ioe);
      return sound;
    }
  }

  AndroidSound createMusic(final String path) {
    // MediaPlayer should really be used to play compressed sounds and other file formats
    // AudioTrack cannot handle. However, the MediaPlayer implementation is currently the only
    // version of AndroidSound we have written, so we'll use it here regardless of format.
    return new AndroidCompressedSound(this, new Resolver() {
      public void resolve (final AndroidSound sound) {
        // we need to create the media player before starting the background task because the media
        // player will dispatch callbacks based on the looper associated with the thread on which
        // it was created; resolve() will be called from the main PlayN thread and we want
        // callbacks to be dispatched on that same thread
        final MediaPlayer mp = new MediaPlayer();
        plat.exec().invokeAsync(new Runnable() {
          public void run () {
            try {
              AssetFileDescriptor fd = plat.assets().openAssetFd(path);
              mp.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
              fd.close();
              mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override public void onPrepared (MediaPlayer mp) { sound.succeed(mp); }
              });
              mp.setOnErrorListener(new OnErrorListener() {
                @Override public boolean onError(MediaPlayer mp, int what, int extra) {
                  String errmsg = "MediaPlayer prepare fail [what=" + what + ", x=" + extra + "]";
                  plat.log().warn(errmsg);
                  sound.fail(new Exception(errmsg));
                  return false;
                }
              });
              mp.prepareAsync();
            } catch (Throwable t) {
              plat.reportError("Sound load error '" + path + "'", t);
              sound.fail(t);
            }
          }
        });
      }
    });
  }

  public void onPause() {
    // handle our pooled sounds
    pool.autoPause();

    // now handle our musics
    if (!playing.isEmpty())
      AndroidPlatform.debugLog("Pausing " + playing.size() + " playing sounds.");
    for (AndroidSound sound : playing) {
      sound.onPause();
    }
  }

  public void onResume() {
    // handle our pooled sounds
    pool.autoResume();

    // copy and clear out the playing set, the playing sounds will re-add themselves once they are
    // (asynchronously) re-resolved and resume playing
    Set> wasPlaying = new HashSet>(playing);
    playing.clear();
    if (!wasPlaying.isEmpty())
      AndroidPlatform.debugLog("Resuming " + wasPlaying.size() + " playing sounds.");
    for (AndroidSound sound : wasPlaying) {
      sound.onResume();
    }
  }

  public void onDestroy() {
    for (AndroidSound sound : playing) {
      sound.release();
    }
    playing.clear();
    pool.release();
  }

  void onPlaying(AndroidSound sound) {
    AndroidPlatform.debugLog("Playing " + sound);
    playing.add(sound);
  }

  void onStopped(AndroidSound sound) {
    AndroidPlatform.debugLog("Stopped " + sound);
    playing.remove(sound);
  }
}