HTML5Audio.java revision 85aa944888089852cf144c18f8fb6869e1a39d32
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.media.MediaPlayer;
20import android.media.MediaPlayer.OnBufferingUpdateListener;
21import android.media.MediaPlayer.OnCompletionListener;
22import android.media.MediaPlayer.OnErrorListener;
23import android.media.MediaPlayer.OnPreparedListener;
24import android.media.MediaPlayer.OnSeekCompleteListener;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Message;
28import android.util.Log;
29
30import java.io.IOException;
31import java.util.HashMap;
32import java.util.Map;
33import java.util.Timer;
34import java.util.TimerTask;
35
36/**
37 * HTML5 support class for Audio.
38 *
39 * This class runs almost entirely on the WebCore thread. The exception is when
40 * accessing the WebView object to determine whether private browsing is
41 * enabled.
42 */
43class HTML5Audio extends Handler
44                 implements MediaPlayer.OnBufferingUpdateListener,
45                            MediaPlayer.OnCompletionListener,
46                            MediaPlayer.OnErrorListener,
47                            MediaPlayer.OnPreparedListener,
48                            MediaPlayer.OnSeekCompleteListener {
49    // Logging tag.
50    private static final String LOGTAG = "HTML5Audio";
51
52    private MediaPlayer mMediaPlayer;
53
54    // The C++ MediaPlayerPrivateAndroid object.
55    private int mNativePointer;
56    // The private status of the view that created this player
57    private IsPrivateBrowsingEnabledGetter mIsPrivateBrowsingEnabledGetter;
58
59    private static int IDLE        =  0;
60    private static int INITIALIZED =  1;
61    private static int PREPARED    =  2;
62    private static int STARTED     =  4;
63    private static int COMPLETE    =  5;
64    private static int PAUSED      =  6;
65    private static int STOPPED     = -2;
66    private static int ERROR       = -1;
67
68    private int mState = IDLE;
69
70    private String mUrl;
71    private boolean mAskToPlay = false;
72
73    // Timer thread -> UI thread
74    private static final int TIMEUPDATE = 100;
75
76    private static final String COOKIE = "Cookie";
77    private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
78
79    // The spec says the timer should fire every 250 ms or less.
80    private static final int TIMEUPDATE_PERIOD = 250;  // ms
81    // The timer for timeupate events.
82    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
83    private Timer mTimer;
84    private final class TimeupdateTask extends TimerTask {
85        public void run() {
86            HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
87        }
88    }
89
90    // Helper class to determine whether private browsing is enabled in the
91    // given WebView. Queries the WebView on the UI thread. Calls to get()
92    // block until the data is available.
93    private class IsPrivateBrowsingEnabledGetter {
94        private boolean mIsReady;
95        private boolean mIsPrivateBrowsingEnabled;
96        IsPrivateBrowsingEnabledGetter(Looper uiThreadLooper, final WebView webView) {
97            new Handler(uiThreadLooper).post(new Runnable() {
98                @Override
99                public void run() {
100                    synchronized(IsPrivateBrowsingEnabledGetter.this) {
101                        mIsPrivateBrowsingEnabled = webView.isPrivateBrowsingEnabled();
102                        mIsReady = true;
103                        IsPrivateBrowsingEnabledGetter.this.notify();
104                    }
105                }
106            });
107        }
108        synchronized boolean get() {
109            while (!mIsReady) {
110                try {
111                    wait();
112                } catch (InterruptedException e) {
113                }
114            }
115            return mIsPrivateBrowsingEnabled;
116        }
117    };
118
119    @Override
120    public void handleMessage(Message msg) {
121        switch (msg.what) {
122            case TIMEUPDATE: {
123                try {
124                    if (mState != ERROR && mMediaPlayer.isPlaying()) {
125                        int position = mMediaPlayer.getCurrentPosition();
126                        nativeOnTimeupdate(position, mNativePointer);
127                    }
128                } catch (IllegalStateException e) {
129                    mState = ERROR;
130                }
131            }
132        }
133    }
134
135    // event listeners for MediaPlayer
136    // Those are called from the same thread we created the MediaPlayer
137    // (i.e. the webviewcore thread here)
138
139    // MediaPlayer.OnBufferingUpdateListener
140    public void onBufferingUpdate(MediaPlayer mp, int percent) {
141        nativeOnBuffering(percent, mNativePointer);
142    }
143
144    // MediaPlayer.OnCompletionListener;
145    public void onCompletion(MediaPlayer mp) {
146        resetMediaPlayer();
147        mState = IDLE;
148        nativeOnEnded(mNativePointer);
149    }
150
151    // MediaPlayer.OnErrorListener
152    public boolean onError(MediaPlayer mp, int what, int extra) {
153        mState = ERROR;
154        resetMediaPlayer();
155        mState = IDLE;
156        return false;
157    }
158
159    // MediaPlayer.OnPreparedListener
160    public void onPrepared(MediaPlayer mp) {
161        mState = PREPARED;
162        if (mTimer != null) {
163            mTimer.schedule(new TimeupdateTask(),
164                            TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
165        }
166        nativeOnPrepared(mp.getDuration(), 0, 0, mNativePointer);
167        if (mAskToPlay) {
168            mAskToPlay = false;
169            play();
170        }
171    }
172
173    // MediaPlayer.OnSeekCompleteListener
174    public void onSeekComplete(MediaPlayer mp) {
175        nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
176    }
177
178
179    /**
180     * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
181     */
182    public HTML5Audio(WebViewCore webViewCore, int nativePtr) {
183        // Save the native ptr
184        mNativePointer = nativePtr;
185        resetMediaPlayer();
186        mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
187                webViewCore.getContext().getMainLooper(), webViewCore.getWebView());
188    }
189
190    private void resetMediaPlayer() {
191        if (mMediaPlayer == null) {
192            mMediaPlayer = new MediaPlayer();
193        } else {
194            mMediaPlayer.reset();
195        }
196        mMediaPlayer.setOnBufferingUpdateListener(this);
197        mMediaPlayer.setOnCompletionListener(this);
198        mMediaPlayer.setOnErrorListener(this);
199        mMediaPlayer.setOnPreparedListener(this);
200        mMediaPlayer.setOnSeekCompleteListener(this);
201
202        if (mTimer != null) {
203            mTimer.cancel();
204        }
205        mTimer = new Timer();
206        mState = IDLE;
207    }
208
209    private void setDataSource(String url) {
210        mUrl = url;
211        try {
212            if (mState != IDLE) {
213                resetMediaPlayer();
214            }
215            String cookieValue = CookieManager.getInstance().getCookie(
216                    url, mIsPrivateBrowsingEnabledGetter.get());
217            Map<String, String> headers = new HashMap<String, String>();
218
219            if (cookieValue != null) {
220                headers.put(COOKIE, cookieValue);
221            }
222            if (mIsPrivateBrowsingEnabledGetter.get()) {
223                headers.put(HIDE_URL_LOGS, "true");
224            }
225
226            mMediaPlayer.setDataSource(url, headers);
227            mState = INITIALIZED;
228            mMediaPlayer.prepareAsync();
229        } catch (IOException e) {
230            String debugUrl = url.length() > 128 ? url.substring(0, 128) + "..." : url;
231            Log.e(LOGTAG, "couldn't load the resource: "+ debugUrl +" exc: " + e);
232            resetMediaPlayer();
233        }
234    }
235
236    private void play() {
237        if ((mState >= ERROR && mState < PREPARED) && mUrl != null) {
238            resetMediaPlayer();
239            setDataSource(mUrl);
240            mAskToPlay = true;
241        }
242
243        if (mState >= PREPARED) {
244            mMediaPlayer.start();
245            mState = STARTED;
246        }
247    }
248
249    private void pause() {
250        if (mState == STARTED) {
251            if (mTimer != null) {
252                mTimer.purge();
253            }
254            mMediaPlayer.pause();
255            mState = PAUSED;
256        }
257    }
258
259    private void seek(int msec) {
260        if (mState >= PREPARED) {
261            mMediaPlayer.seekTo(msec);
262        }
263    }
264
265    private void teardown() {
266        mMediaPlayer.release();
267        mState = ERROR;
268        mNativePointer = 0;
269    }
270
271    private float getMaxTimeSeekable() {
272        return mMediaPlayer.getDuration() / 1000.0f;
273    }
274
275    private native void nativeOnBuffering(int percent, int nativePointer);
276    private native void nativeOnEnded(int nativePointer);
277    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
278    private native void nativeOnTimeupdate(int position, int nativePointer);
279}
280