Index: content/public/android/java/src/org/chromium/content/browser/MediaThrottler.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/MediaThrottler.java b/content/public/android/java/src/org/chromium/content/browser/MediaThrottler.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5ab6d53536f493f045e64e7ed4130dddc73e0ffe |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/MediaThrottler.java |
@@ -0,0 +1,204 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.content.browser; |
+ |
+import android.content.Context; |
+import android.media.MediaPlayer; |
+import android.os.AsyncTask; |
+import android.os.Handler; |
+import android.os.Looper; |
+import android.os.SystemClock; |
+ |
+import org.chromium.base.Log; |
+import org.chromium.base.annotations.CalledByNative; |
+import org.chromium.base.annotations.JNINamespace; |
+import org.chromium.content.R; |
+ |
+/** |
+ * Class for listening to Android MediaServer Crashes to throttle media decoding |
+ * when needed. |
+ */ |
+@JNINamespace("content") |
+class MediaThrottler implements MediaPlayer.OnErrorListener { |
+ private static final String TAG = "cr_MediaThrottler"; |
+ private static final long UNKNOWN_LAST_SERVER_CRASH_TIME = -1; |
+ |
+ // Number of active decode requests. |
+ private int mRequestCount; |
+ |
+ // Application context. |
+ private final Context mContext; |
+ |
+ // Watch dog player. Used to listen to all media server crashes. |
+ private MediaPlayer mPlayer; |
+ |
+ // The last media server crash time since Chrome lauches. |
+ private long mLastCrashTime = UNKNOWN_LAST_SERVER_CRASH_TIME; |
+ |
+ // Server crash count since last reset() call. |
+ private int mServerCrashCount; |
+ |
+ // Object for synchronized access to memeber variables. |
+ private final Object mLock = new Object(); |
+ |
+ // Handler for posting delayed tasks. |
+ private Handler mHandler; |
+ |
+ // Intervals between media server crashes that are considered normal. It |
+ // takes about 5 seconds to restart the media server. So this value has to |
+ // be larger than 5 seconds. |
+ private static final long SERVER_CRASH_INTERVAL_THRESHOLD_IN_MILLIS = 60000; |
+ |
+ // Delay to keep the watch dog player alive When there are no decoding |
+ // requests. This is introduced to avoid recreating the watch dog over and |
+ // over if a burst of small decoding requests arrive. |
+ private static final int RELEASE_WATCH_DOG_PLAYER_DELAY_IN_MILLIS = 5000; |
+ |
+ // When |mServerCrashCount| reaches this threshold, throttling will start. |
+ // This is to prevent a page from loading a malformed video over and over |
+ // to crash the media server excessively. |
+ private static final int SERVER_CRASH_COUNT_THRESHOLD_FOR_THROTTLING = 4; |
+ |
+ /** |
+ * A background task to release the watch dog player. |
+ */ |
+ private class ReleaseWatchDogTask extends AsyncTask<Void, Void, Void> { |
+ @Override |
+ protected Void doInBackground(Void... voids) { |
+ synchronized (mLock) { |
+ if (mRequestCount == 0 && mPlayer != null) { |
+ mPlayer.release(); |
+ mPlayer = null; |
+ } |
+ } |
+ return null; |
+ } |
+ } |
+ |
+ private final Runnable mDelayedReleaseRunnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ new ReleaseWatchDogTask().execute(); |
+ } |
+ }; |
+ |
+ @CalledByNative |
+ private static MediaThrottler create(Context context) { |
+ return new MediaThrottler(context); |
+ } |
+ |
+ private MediaThrottler(Context context) { |
+ mContext = context; |
+ mHandler = new Handler(Looper.getMainLooper()); |
+ } |
+ |
+ /** |
+ * A background task to start the watch dog player. |
+ */ |
+ private class StartWatchDogTask extends AsyncTask<Void, Void, Void> { |
+ @Override |
+ protected Void doInBackground(Void... voids) { |
+ synchronized (mLock) { |
+ if (mPlayer != null || mRequestCount == 0) return null; |
+ mPlayer = MediaPlayer.create(mContext, R.raw.empty); |
+ if (mPlayer == null) { |
+ Log.e(TAG, "Unable to create watch dog player, treat it as server crash."); |
+ onMediaServerCrash(); |
+ } else { |
+ mPlayer.setOnErrorListener(MediaThrottler.this); |
+ } |
+ } |
+ return null; |
+ } |
+ } |
+ |
+ /** |
+ * Called to request the permission to decode media data. |
+ * |
+ * @return true if the request is permitted, or false otherwise. |
+ */ |
+ @CalledByNative |
+ private boolean requestDecoderResources() { |
+ synchronized (mLock) { |
+ long currentTime = SystemClock.elapsedRealtime(); |
+ if (mLastCrashTime != UNKNOWN_LAST_SERVER_CRASH_TIME |
+ && (currentTime - mLastCrashTime < SERVER_CRASH_INTERVAL_THRESHOLD_IN_MILLIS) |
+ && mServerCrashCount >= SERVER_CRASH_COUNT_THRESHOLD_FOR_THROTTLING) { |
+ Log.e(TAG, "Request to decode media data denied due to throttling."); |
+ return false; |
+ } |
+ mRequestCount++; |
+ if (mRequestCount == 1) { |
+ mHandler.removeCallbacks(mDelayedReleaseRunnable); |
+ mHandler.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ new StartWatchDogTask().execute(); |
+ } |
+ }); |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ /** |
+ * Called to signal that a decode request has been completed. |
+ */ |
+ @CalledByNative |
+ private void onDecodeRequestFinished() { |
+ synchronized (mLock) { |
+ mRequestCount--; |
+ if (mRequestCount == 0) { |
+ // Don't release the watch dog immediately, there could be a |
+ // number of small requests coming together. |
+ prepareToStopWatchDog(); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Posts a delayed task to stop the watch dog player. |
+ */ |
+ private void prepareToStopWatchDog() { |
+ mHandler.postDelayed(mDelayedReleaseRunnable, RELEASE_WATCH_DOG_PLAYER_DELAY_IN_MILLIS); |
+ } |
+ |
+ @Override |
+ public boolean onError(MediaPlayer mp, int what, int extra) { |
+ if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { |
+ synchronized (mLock) { |
+ onMediaServerCrash(); |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ /** |
+ * Called when media server crashes. |
+ */ |
+ private void onMediaServerCrash() { |
+ assert Thread.holdsLock(mLock); |
+ long currentTime = SystemClock.elapsedRealtime(); |
+ if (mLastCrashTime != UNKNOWN_LAST_SERVER_CRASH_TIME |
+ && (currentTime - mLastCrashTime < SERVER_CRASH_INTERVAL_THRESHOLD_IN_MILLIS)) { |
+ mServerCrashCount++; |
+ } else { |
+ mServerCrashCount = 1; |
+ } |
+ mLastCrashTime = currentTime; |
+ } |
+ |
+ /** |
+ * Resets the MediaThrottler to its initial state so that subsequent requests |
+ * will not be throttled. |
+ */ |
+ @CalledByNative |
+ private void reset() { |
+ synchronized (mLock) { |
+ mServerCrashCount = 0; |
+ mLastCrashTime = UNKNOWN_LAST_SERVER_CRASH_TIME; |
+ } |
+ } |
+} |