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