chromium/media/base/android/java/src/org/chromium/media/MediaServerCrashListener.java

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.media;

import android.media.MediaPlayer;
import android.os.SystemClock;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;

/**
 * Class for listening to Android MediaServer crashes to throttle media decoding
 * when needed.
 */
@JNINamespace("media")
public class MediaServerCrashListener implements MediaPlayer.OnErrorListener {
    private static final String TAG = "crMediaCrashListener";
    private static final long UNKNOWN_TIME = -1;

    // Watchdog player. Used to listen to all media server crashes.
    private MediaPlayer mPlayer;

    // Protecting the creation/release of the watchdog player.
    private final Object mLock = new Object();

    // Approximate time necessary for the MediaServer to restart after a crash.
    private static final int APPROX_MEDIA_SERVER_RESTART_TIME_IN_MS = 5000;

    // The last time we reported a failure to create the watchdog as a server crash.
    private long mLastReportedWatchdogCreationFailure = UNKNOWN_TIME;

    private long mNativeMediaServerCrashListener;

    @CalledByNative
    private static MediaServerCrashListener create(long nativeMediaServerCrashListener) {
        return new MediaServerCrashListener(nativeMediaServerCrashListener);
    }

    private MediaServerCrashListener(long nativeMediaServerCrashListener) {
        mNativeMediaServerCrashListener = nativeMediaServerCrashListener;
    }

    @CalledByNative
    public void releaseWatchdog() {
        if (mPlayer == null) return;

        mPlayer.release();
        mPlayer = null;
    }

    @CalledByNative
    public boolean startListening() {
        if (mPlayer != null) return true;

        try {
            mPlayer = MediaPlayer.create(ContextUtils.getApplicationContext(), R.raw.empty);
        } catch (IllegalStateException e) {
            Log.e(TAG, "Exception while creating the watchdog player.", e);
        } catch (RuntimeException e) {
            Log.e(TAG, "Exception while creating the watchdog player.", e);
        }

        if (mPlayer != null) {
            mPlayer.setOnErrorListener(MediaServerCrashListener.this);

            // Reset the reported creation failure time on successful
            // watchdog creation.
            mLastReportedWatchdogCreationFailure = UNKNOWN_TIME;
            return true;
        }

        long currentTime = SystemClock.elapsedRealtime();

        // It takes ~5s for the MediaServer to restart. Do not report a
        // failure to create a watchdog MediaPlayer as a crash more than
        // once per 5s, to prevent a burst of calls to startListening() from
        // artificially inflating the number of crashes.
        if (mLastReportedWatchdogCreationFailure == UNKNOWN_TIME
                || (currentTime - mLastReportedWatchdogCreationFailure)
                        > APPROX_MEDIA_SERVER_RESTART_TIME_IN_MS) {
            Log.e(TAG, "Unable to create watchdog player, treating it as server crash.");
            MediaServerCrashListenerJni.get()
                    .onMediaServerCrashDetected(
                            mNativeMediaServerCrashListener, MediaServerCrashListener.this, false);
            mLastReportedWatchdogCreationFailure = currentTime;
        }
        return false;
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
            MediaServerCrashListenerJni.get()
                    .onMediaServerCrashDetected(
                            mNativeMediaServerCrashListener, MediaServerCrashListener.this, true);
            releaseWatchdog();
        }
        return true;
    }

    @NativeMethods
    interface Natives {
        void onMediaServerCrashDetected(
                long nativeMediaServerCrashListener,
                MediaServerCrashListener caller,
                boolean watchdogNeedsRelease);
    }
}