1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.speech.tts;
17
18import android.content.Context;
19import android.media.MediaPlayer;
20import android.net.Uri;
21import android.os.ConditionVariable;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Looper;
25import android.util.Log;
26
27/**
28 * A media player that allows blocking to wait for it to finish.
29 */
30class BlockingMediaPlayer {
31
32    private static final String TAG = "BlockMediaPlayer";
33
34    private static final String MEDIA_PLAYER_THREAD_NAME = "TTS-MediaPlayer";
35
36    private final Context mContext;
37    private final Uri mUri;
38    private final int mStreamType;
39    private final ConditionVariable mDone;
40    // Only accessed on the Handler thread
41    private MediaPlayer mPlayer;
42    private volatile boolean mFinished;
43
44    /**
45     * Creates a new blocking media player.
46     * Creating a blocking media player is a cheap operation.
47     *
48     * @param context
49     * @param uri
50     * @param streamType
51     */
52    public BlockingMediaPlayer(Context context, Uri uri, int streamType) {
53        mContext = context;
54        mUri = uri;
55        mStreamType = streamType;
56        mDone = new ConditionVariable();
57
58    }
59
60    /**
61     * Starts playback and waits for it to finish.
62     * Can be called from any thread.
63     *
64     * @return {@code true} if the playback finished normally, {@code false} if the playback
65     *         failed or {@link #stop} was called before the playback finished.
66     */
67    public boolean startAndWait() {
68        HandlerThread thread = new HandlerThread(MEDIA_PLAYER_THREAD_NAME);
69        thread.start();
70        Handler handler = new Handler(thread.getLooper());
71        mFinished = false;
72        handler.post(new Runnable() {
73            @Override
74            public void run() {
75                startPlaying();
76            }
77        });
78        mDone.block();
79        handler.post(new Runnable() {
80            @Override
81            public void run() {
82                finish();
83                // No new messages should get posted to the handler thread after this
84                Looper.myLooper().quit();
85            }
86        });
87        return mFinished;
88    }
89
90    /**
91     * Stops playback. Can be called multiple times.
92     * Can be called from any thread.
93     */
94    public void stop() {
95        mDone.open();
96    }
97
98    /**
99     * Starts playback.
100     * Called on the handler thread.
101     */
102    private void startPlaying() {
103        mPlayer = MediaPlayer.create(mContext, mUri);
104        if (mPlayer == null) {
105            Log.w(TAG, "Failed to play " + mUri);
106            mDone.open();
107            return;
108        }
109        try {
110            mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
111                @Override
112                public boolean onError(MediaPlayer mp, int what, int extra) {
113                    Log.w(TAG, "Audio playback error: " + what + ", " + extra);
114                    mDone.open();
115                    return true;
116                }
117            });
118            mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
119                @Override
120                public void onCompletion(MediaPlayer mp) {
121                    mFinished = true;
122                    mDone.open();
123                }
124            });
125            mPlayer.setAudioStreamType(mStreamType);
126            mPlayer.start();
127        } catch (IllegalArgumentException ex) {
128            Log.w(TAG, "MediaPlayer failed", ex);
129            mDone.open();
130        }
131    }
132
133    /**
134     * Stops playback and release the media player.
135     * Called on the handler thread.
136     */
137    private void finish() {
138        try {
139            mPlayer.stop();
140        } catch (IllegalStateException ex) {
141            // Do nothing, the player is already stopped
142        }
143        mPlayer.release();
144    }
145
146}