summaryrefslogtreecommitdiff
path: root/android/media/AsyncPlayer.java
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-09-15 17:58:39 -0400
commit10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch)
tree8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/media/AsyncPlayer.java
parent677516fb6b6f207d373984757d3d9450474b6b00 (diff)
downloadandroid-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \ --bid 4335822 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4335822.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/media/AsyncPlayer.java')
-rw-r--r--android/media/AsyncPlayer.java274
1 files changed, 274 insertions, 0 deletions
diff --git a/android/media/AsyncPlayer.java b/android/media/AsyncPlayer.java
new file mode 100644
index 00000000..c1a178a2
--- /dev/null
+++ b/android/media/AsyncPlayer.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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 android.media;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.PlayerBase;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.LinkedList;
+
+/**
+ * Plays a series of audio URIs, but does all the hard work on another thread
+ * so that any slowness with preparing or loading doesn't block the calling thread.
+ */
+public class AsyncPlayer {
+ private static final int PLAY = 1;
+ private static final int STOP = 2;
+ private static final boolean mDebug = false;
+
+ private static final class Command {
+ int code;
+ Context context;
+ Uri uri;
+ boolean looping;
+ AudioAttributes attributes;
+ long requestTime;
+
+ public String toString() {
+ return "{ code=" + code + " looping=" + looping + " attr=" + attributes
+ + " uri=" + uri + " }";
+ }
+ }
+
+ private final LinkedList<Command> mCmdQueue = new LinkedList();
+
+ private void startSound(Command cmd) {
+ // Preparing can be slow, so if there is something else
+ // is playing, let it continue until we're done, so there
+ // is less of a glitch.
+ try {
+ if (mDebug) Log.d(mTag, "Starting playback");
+ MediaPlayer player = new MediaPlayer();
+ player.setAudioAttributes(cmd.attributes);
+ player.setDataSource(cmd.context, cmd.uri);
+ player.setLooping(cmd.looping);
+ player.prepare();
+ player.start();
+ if (mPlayer != null) {
+ mPlayer.release();
+ }
+ mPlayer = player;
+ long delay = SystemClock.uptimeMillis() - cmd.requestTime;
+ if (delay > 1000) {
+ Log.w(mTag, "Notification sound delayed by " + delay + "msecs");
+ }
+ }
+ catch (Exception e) {
+ Log.w(mTag, "error loading sound for " + cmd.uri, e);
+ }
+ }
+
+ private final class Thread extends java.lang.Thread {
+ Thread() {
+ super("AsyncPlayer-" + mTag);
+ }
+
+ public void run() {
+ while (true) {
+ Command cmd = null;
+
+ synchronized (mCmdQueue) {
+ if (mDebug) Log.d(mTag, "RemoveFirst");
+ cmd = mCmdQueue.removeFirst();
+ }
+
+ switch (cmd.code) {
+ case PLAY:
+ if (mDebug) Log.d(mTag, "PLAY");
+ startSound(cmd);
+ break;
+ case STOP:
+ if (mDebug) Log.d(mTag, "STOP");
+ if (mPlayer != null) {
+ long delay = SystemClock.uptimeMillis() - cmd.requestTime;
+ if (delay > 1000) {
+ Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
+ }
+ mPlayer.stop();
+ mPlayer.release();
+ mPlayer = null;
+ } else {
+ Log.w(mTag, "STOP command without a player");
+ }
+ break;
+ }
+
+ synchronized (mCmdQueue) {
+ if (mCmdQueue.size() == 0) {
+ // nothing left to do, quit
+ // doing this check after we're done prevents the case where they
+ // added it during the operation from spawning two threads and
+ // trying to do them in parallel.
+ mThread = null;
+ releaseWakeLock();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private String mTag;
+ private Thread mThread;
+ private MediaPlayer mPlayer;
+ private PowerManager.WakeLock mWakeLock;
+
+ // The current state according to the caller. Reality lags behind
+ // because of the asynchronous nature of this class.
+ private int mState = STOP;
+
+ /**
+ * Construct an AsyncPlayer object.
+ *
+ * @param tag a string to use for debugging
+ */
+ public AsyncPlayer(String tag) {
+ if (tag != null) {
+ mTag = tag;
+ } else {
+ mTag = "AsyncPlayer";
+ }
+ }
+
+ /**
+ * Start playing the sound. It will actually start playing at some
+ * point in the future. There are no guarantees about latency here.
+ * Calling this before another audio file is done playing will stop
+ * that one and start the new one.
+ *
+ * @param context Your application's context.
+ * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)})
+ * @param looping Whether the audio should loop forever.
+ * (see {@link MediaPlayer#setLooping(boolean)})
+ * @param stream the AudioStream to use.
+ * (see {@link MediaPlayer#setAudioStreamType(int)})
+ * @deprecated use {@link #play(Context, Uri, boolean, AudioAttributes)} instead
+ */
+ public void play(Context context, Uri uri, boolean looping, int stream) {
+ PlayerBase.deprecateStreamTypeForPlayback(stream, "AsyncPlayer", "play()");
+ if (context == null || uri == null) {
+ return;
+ }
+ try {
+ play(context, uri, looping,
+ new AudioAttributes.Builder().setInternalLegacyStreamType(stream).build());
+ } catch (IllegalArgumentException e) {
+ Log.e(mTag, "Call to deprecated AsyncPlayer.play() method caused:", e);
+ }
+ }
+
+ /**
+ * Start playing the sound. It will actually start playing at some
+ * point in the future. There are no guarantees about latency here.
+ * Calling this before another audio file is done playing will stop
+ * that one and start the new one.
+ *
+ * @param context the non-null application's context.
+ * @param uri the non-null URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)})
+ * @param looping whether the audio should loop forever.
+ * (see {@link MediaPlayer#setLooping(boolean)})
+ * @param attributes the non-null {@link AudioAttributes} to use.
+ * (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
+ * @throws IllegalArgumentException
+ */
+ public void play(@NonNull Context context, @NonNull Uri uri, boolean looping,
+ @NonNull AudioAttributes attributes) throws IllegalArgumentException {
+ if (context == null || uri == null || attributes == null) {
+ throw new IllegalArgumentException("Illegal null AsyncPlayer.play() argument");
+ }
+ Command cmd = new Command();
+ cmd.requestTime = SystemClock.uptimeMillis();
+ cmd.code = PLAY;
+ cmd.context = context;
+ cmd.uri = uri;
+ cmd.looping = looping;
+ cmd.attributes = attributes;
+ synchronized (mCmdQueue) {
+ enqueueLocked(cmd);
+ mState = PLAY;
+ }
+ }
+
+ /**
+ * Stop a previously played sound. It can't be played again or unpaused
+ * at this point. Calling this multiple times has no ill effects.
+ */
+ public void stop() {
+ synchronized (mCmdQueue) {
+ // This check allows stop to be called multiple times without starting
+ // a thread that ends up doing nothing.
+ if (mState != STOP) {
+ Command cmd = new Command();
+ cmd.requestTime = SystemClock.uptimeMillis();
+ cmd.code = STOP;
+ enqueueLocked(cmd);
+ mState = STOP;
+ }
+ }
+ }
+
+ private void enqueueLocked(Command cmd) {
+ mCmdQueue.add(cmd);
+ if (mThread == null) {
+ acquireWakeLock();
+ mThread = new Thread();
+ mThread.start();
+ }
+ }
+
+ /**
+ * We want to hold a wake lock while we do the prepare and play. The stop probably is
+ * optional, but it won't hurt to have it too. The problem is that if you start a sound
+ * while you're holding a wake lock (e.g. an alarm starting a notification), you want the
+ * sound to play, but if the CPU turns off before mThread gets to work, it won't. The
+ * simplest way to deal with this is to make it so there is a wake lock held while the
+ * thread is starting or running. You're going to need the WAKE_LOCK permission if you're
+ * going to call this.
+ *
+ * This must be called before the first time play is called.
+ *
+ * @hide
+ */
+ public void setUsesWakeLock(Context context) {
+ if (mWakeLock != null || mThread != null) {
+ // if either of these has happened, we've already played something.
+ // and our releases will be out of sync.
+ throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+ + " mThread=" + mThread);
+ }
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+ }
+
+ private void acquireWakeLock() {
+ if (mWakeLock != null) {
+ mWakeLock.acquire();
+ }
+ }
+
+ private void releaseWakeLock() {
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ }
+ }
+}
+