| 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;
|
| + }
|
| + }
|
| +}
|
|
|