blob: 97d61ba73d551e785ad63f7aac6a41d335a64130 [file] [log] [blame]
/*
* Copyright (C) 2010 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.webkit;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* HTML5 support class for Audio.
*
* This class runs almost entirely on the WebCore thread. The exception is when
* accessing the WebView object to determine whether private browsing is
* enabled.
*/
class HTML5Audio extends Handler
implements MediaPlayer.OnBufferingUpdateListener,
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnPreparedListener,
MediaPlayer.OnSeekCompleteListener,
AudioManager.OnAudioFocusChangeListener {
// Logging tag.
private static final String LOGTAG = "HTML5Audio";
private MediaPlayer mMediaPlayer;
// The C++ MediaPlayerPrivateAndroid object.
private int mNativePointer;
// The private status of the view that created this player
private IsPrivateBrowsingEnabledGetter mIsPrivateBrowsingEnabledGetter;
private static int IDLE = 0;
private static int INITIALIZED = 1;
private static int PREPARED = 2;
private static int STARTED = 4;
private static int COMPLETE = 5;
private static int PAUSED = 6;
private static int STOPPED = -2;
private static int ERROR = -1;
private int mState = IDLE;
private String mUrl;
private boolean mAskToPlay = false;
private Context mContext;
// Timer thread -> UI thread
private static final int TIMEUPDATE = 100;
private static final String COOKIE = "Cookie";
private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
// The spec says the timer should fire every 250 ms or less.
private static final int TIMEUPDATE_PERIOD = 250; // ms
// The timer for timeupate events.
// See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
private Timer mTimer;
private final class TimeupdateTask extends TimerTask {
public void run() {
HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
}
}
// Helper class to determine whether private browsing is enabled in the
// given WebView. Queries the WebView on the UI thread. Calls to get()
// block until the data is available.
private class IsPrivateBrowsingEnabledGetter {
private boolean mIsReady;
private boolean mIsPrivateBrowsingEnabled;
IsPrivateBrowsingEnabledGetter(Looper uiThreadLooper, final WebView webView) {
new Handler(uiThreadLooper).post(new Runnable() {
@Override
public void run() {
synchronized(IsPrivateBrowsingEnabledGetter.this) {
mIsPrivateBrowsingEnabled = webView.isPrivateBrowsingEnabled();
mIsReady = true;
IsPrivateBrowsingEnabledGetter.this.notify();
}
}
});
}
synchronized boolean get() {
while (!mIsReady) {
try {
wait();
} catch (InterruptedException e) {
}
}
return mIsPrivateBrowsingEnabled;
}
};
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TIMEUPDATE: {
try {
if (mState != ERROR && mMediaPlayer.isPlaying()) {
int position = mMediaPlayer.getCurrentPosition();
nativeOnTimeupdate(position, mNativePointer);
}
} catch (IllegalStateException e) {
mState = ERROR;
}
}
}
}
// event listeners for MediaPlayer
// Those are called from the same thread we created the MediaPlayer
// (i.e. the webviewcore thread here)
// MediaPlayer.OnBufferingUpdateListener
public void onBufferingUpdate(MediaPlayer mp, int percent) {
nativeOnBuffering(percent, mNativePointer);
}
// MediaPlayer.OnCompletionListener;
public void onCompletion(MediaPlayer mp) {
resetMediaPlayer();
mState = IDLE;
nativeOnEnded(mNativePointer);
}
// MediaPlayer.OnErrorListener
public boolean onError(MediaPlayer mp, int what, int extra) {
mState = ERROR;
resetMediaPlayer();
mState = IDLE;
return false;
}
// MediaPlayer.OnPreparedListener
public void onPrepared(MediaPlayer mp) {
mState = PREPARED;
if (mTimer != null) {
mTimer.schedule(new TimeupdateTask(),
TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
}
nativeOnPrepared(mp.getDuration(), 0, 0, mNativePointer);
if (mAskToPlay) {
mAskToPlay = false;
play();
}
}
// MediaPlayer.OnSeekCompleteListener
public void onSeekComplete(MediaPlayer mp) {
nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
}
/**
* @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
*/
public HTML5Audio(WebViewCore webViewCore, int nativePtr) {
// Save the native ptr
mNativePointer = nativePtr;
resetMediaPlayer();
mContext = webViewCore.getContext();
mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
webViewCore.getContext().getMainLooper(), webViewCore.getWebView());
}
private void resetMediaPlayer() {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
} else {
mMediaPlayer.reset();
}
mMediaPlayer.setOnBufferingUpdateListener(this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnSeekCompleteListener(this);
if (mTimer != null) {
mTimer.cancel();
}
mTimer = new Timer();
mState = IDLE;
}
private void setDataSource(String url) {
mUrl = url;
try {
if (mState != IDLE) {
resetMediaPlayer();
}
String cookieValue = CookieManager.getInstance().getCookie(
url, mIsPrivateBrowsingEnabledGetter.get());
Map<String, String> headers = new HashMap<String, String>();
if (cookieValue != null) {
headers.put(COOKIE, cookieValue);
}
if (mIsPrivateBrowsingEnabledGetter.get()) {
headers.put(HIDE_URL_LOGS, "true");
}
mMediaPlayer.setDataSource(url, headers);
mState = INITIALIZED;
mMediaPlayer.prepareAsync();
} catch (IOException e) {
String debugUrl = url.length() > 128 ? url.substring(0, 128) + "..." : url;
Log.e(LOGTAG, "couldn't load the resource: "+ debugUrl +" exc: " + e);
resetMediaPlayer();
}
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mMediaPlayer == null) {
resetMediaPlayer();
} else if (mState != ERROR && !mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
mState = STARTED;
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback.
if (mState != ERROR && mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
mState = STOPPED;
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but we have to stop
// playback.
if (mState != ERROR && mMediaPlayer.isPlaying()) pause();
break;
}
}
private void play() {
if ((mState >= ERROR && mState < PREPARED) && mUrl != null) {
resetMediaPlayer();
setDataSource(mUrl);
mAskToPlay = true;
}
if (mState >= PREPARED) {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mMediaPlayer.start();
mState = STARTED;
}
}
}
private void pause() {
if (mState == STARTED) {
if (mTimer != null) {
mTimer.purge();
}
mMediaPlayer.pause();
mState = PAUSED;
}
}
private void seek(int msec) {
if (mState >= PREPARED) {
mMediaPlayer.seekTo(msec);
}
}
/**
* Called only over JNI when WebKit is happy to
* destroy the media player.
*/
private void teardown() {
mMediaPlayer.release();
mMediaPlayer = null;
mState = ERROR;
mNativePointer = 0;
}
private float getMaxTimeSeekable() {
return mMediaPlayer.getDuration() / 1000.0f;
}
private native void nativeOnBuffering(int percent, int nativePointer);
private native void nativeOnEnded(int nativePointer);
private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
private native void nativeOnTimeupdate(int position, int nativePointer);
}