Chromium Code Reviews| 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..1c008c404221fdb3bc0e8f6e05acab7ff6a9f624 |
| --- /dev/null |
| +++ b/content/public/android/java/src/org/chromium/content/browser/MediaThrottler.java |
| @@ -0,0 +1,199 @@ |
| +// 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. |
|
Raymond Toy
2015/09/30 22:37:54
Why are crashes considered "normal"? And why 60 s
qinmin
2015/09/30 22:56:04
This value is based on the discussion with the sec
Raymond Toy
2015/10/01 21:58:20
I think just saying anything larger than 5 sec or
qinmin
2015/10/01 22:53:34
Done.
|
| + 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. |
| + private static final int SERVER_CRASH_COUNT_THRESHOLD_FOR_THROTTLING = 4; |
|
Raymond Toy
2015/09/30 22:37:54
An explanation on how you decided 4 was enough wou
qinmin
2015/09/30 22:56:04
same as above. This value is just introduced to ki
Raymond Toy
2015/10/01 21:58:20
As above, just a short comment saying what you jus
qinmin
2015/10/01 22:53:34
Done.
|
| + |
| + /** |
| + * 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); |
|
Ted C
2015/10/01 02:19:57
What happens if ReleaseWatchDogTask had already be
qinmin
2015/10/01 18:14:02
If ReleaseWatchDogTask had already been executed,
|
| + mHandler.post(new Runnable() { |
| + @Override |
| + public void run() { |
| + new StartWatchDogTask().execute(); |
|
Ted C
2015/10/01 02:19:57
Why post the async task? As opposed to the runnab
qinmin
2015/10/01 18:14:02
Creating the watch dog player will need to initial
Ted C
2015/10/02 17:23:38
Not blocking the UI thread is good, so an AsyncTas
qinmin
2015/10/02 17:31:55
the issue is that async task has to be executed on
Ted C
2015/10/02 18:34:20
Ah ha! Gotcha!
|
| + } |
| + }); |
| + } |
| + } |
| + 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() { |
| + long currentTime = SystemClock.elapsedRealtime(); |
|
Ted C
2015/10/01 02:19:57
do you need to lock here? I guess you are already
qinmin
2015/10/01 18:14:02
Done.
|
| + 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; |
|
Ted C
2015/10/01 02:19:57
should you cancel mDelayedReleaseRunnable?
qinmin
2015/10/01 18:14:02
No need. If mDelayedReleaseRunnable is already sch
|
| + mLastCrashTime = UNKNOWN_LAST_SERVER_CRASH_TIME; |
| + } |
| + } |
| +} |