org.chromium.chrome.browser.media.remote.RemoteMediaPlayerController.java Source code

Java tutorial

Introduction

Here is the source code for org.chromium.chrome.browser.media.remote.RemoteMediaPlayerController.java

Source

// Copyright 2013 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.chrome.browser.media.remote;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.MediaRouteChooserDialogFragment;
import android.support.v7.app.MediaRouteControllerDialogFragment;
import android.support.v7.app.MediaRouteDialogFactory;

import com.google.android.gms.cast.CastMediaControlIntent;

import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.media.remote.MediaRouteController.MediaStateListener;
import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
import org.chromium.ui.widget.Toast;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * The singleton responsible managing the global resources for remote media playback (cast)
 */
public class RemoteMediaPlayerController implements MediaRouteController.UiListener {
    // Singleton instance of the class. May only be accessed from UI thread.
    private static RemoteMediaPlayerController sInstance;

    private static final String TAG = "MediaFling";

    private static final String DEFAULT_CASTING_MESSAGE = "Casting to Chromecast";

    private CastNotificationControl mNotificationControl;

    private Context mCastContextApplicationContext;
    // The Activity that was in the foreground when the video was cast.
    private WeakReference<Activity> mChromeVideoActivity;

    private List<MediaRouteController> mMediaRouteControllers;

    // points to mDefaultRouteSelector, mYouTubeRouteSelector or null
    private MediaRouteController mCurrentRouteController;

    // This is a key for meta-data in the package manifest.
    private static final String REMOTE_MEDIA_PLAYERS_KEY = "org.chromium.content.browser.REMOTE_MEDIA_PLAYERS";

    /**
     * The private constructor to make sure the object is only created by the instance() method.
     */
    private RemoteMediaPlayerController() {
        mChromeVideoActivity = new WeakReference<Activity>(null);
        mMediaRouteControllers = new ArrayList<MediaRouteController>();
    }

    /**
     * @return The poster image for the currently playing remote video, null if there's none.
     */
    public Bitmap getPoster() {
        if (mCurrentRouteController == null)
            return null;
        return mCurrentRouteController.getPoster();
    }

    /**
     * The singleton instance access method for native objects. Must be called on the UI thread
     * only.
     */
    public static RemoteMediaPlayerController instance() {
        ThreadUtils.assertOnUiThread();

        if (sInstance == null)
            sInstance = new RemoteMediaPlayerController();
        if (sInstance.mChromeVideoActivity.get() == null)
            sInstance.linkToBrowserActivity();

        return sInstance;
    }

    /**
     * Gets the MediaRouteController for a video, creating it if necessary.
     * @param frameUrl The Url of the frame containing the video
     * @return the MediaRouteController, or null if none.
     */
    public MediaRouteController getMediaRouteController(String sourceUrl, String frameUrl) {
        for (MediaRouteController controller : mMediaRouteControllers) {
            if (controller.canPlayMedia(sourceUrl, frameUrl)) {
                return controller;
            }
        }
        return null;
    }

    /**
     * Gets the default MediaRouteController, creating it if necessary.
     * @return the default MediaRouteController.
     */
    public List<MediaRouteController> getMediaRouteControllers() {
        return mMediaRouteControllers;
    }

    /**
     * Links this object to the Activity that owns the video, if it exists.
     *
     */
    private void linkToBrowserActivity() {

        Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
        if (currentActivity != null) {
            mChromeVideoActivity = new WeakReference<Activity>(currentActivity);

            mCastContextApplicationContext = currentActivity.getApplicationContext();
            createMediaRouteControllers(currentActivity);
        }
    }

    /**
     * Create the mediaRouteControllers
     * @param context - the current Android Context
     */
    public void createMediaRouteControllers(Context context) {
        // We only need to do this once
        if (!mMediaRouteControllers.isEmpty())
            return;
        try {
            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(),
                    PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;
            String classNameString = bundle.getString(REMOTE_MEDIA_PLAYERS_KEY);

            if (classNameString != null) {
                String[] classNames = classNameString.split(",");
                for (String className : classNames) {
                    Log.d(TAG, "Adding remote media route controller %s", className.trim());
                    Class<?> mediaRouteControllerClass = Class.forName(className.trim());
                    Object mediaRouteController = mediaRouteControllerClass.newInstance();
                    assert mediaRouteController instanceof MediaRouteController;
                    mMediaRouteControllers.add((MediaRouteController) mediaRouteController);
                }
            }
        } catch (NameNotFoundException | ClassNotFoundException | SecurityException | InstantiationException
                | IllegalAccessException | IllegalArgumentException e) {
            // Should never happen, implies corrupt AndroidManifest
            Log.e(TAG, "Couldn't instatiate MediaRouteControllers", e);
            assert false;
        }
    }

    private void onStateReset(MediaRouteController controller) {

        if (!controller.initialize())
            return;

        mNotificationControl = CastNotificationControl.getOrCreate(mChromeVideoActivity.get(), controller);
        mNotificationControl.setPosterBitmap(getPoster());
        controller.prepareMediaRoute();

        controller.addUiListener(this);
    }

    /**
     * Called when a lower layer requests that a video be cast. This will typically be a request
     * from Blink when the cast button is pressed on the default video controls.
     * @param player the player for which cast is being requested
     * @param frameUrl the URL of the frame containing the video, needed for YouTube videos
     */
    public void requestRemotePlayback(MediaRouteController.MediaStateListener player,
            MediaRouteController controller) {
        Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
        mChromeVideoActivity = new WeakReference<Activity>(currentActivity);

        if (mCurrentRouteController != null && controller != mCurrentRouteController) {
            mCurrentRouteController.release();
        }

        onStateReset(controller);
        showMediaRouteDialog(player, controller, currentActivity);

    }

    /**
     * Called when a lower layer requests control of a video that is being cast.
     * @param player The player for which remote playback control is being requested.
     */
    public void requestRemotePlaybackControl(MediaRouteController.MediaStateListener player) {
        // Player should match currently remotely played item, but there
        // can be a race between various
        // ways that the a video can stop playing remotely. Check that the
        // player is current, and ignore if not.

        if (mCurrentRouteController == null)
            return;
        if (mCurrentRouteController.getMediaStateListener() != player)
            return;

        showMediaRouteControlDialog(ApplicationStatus.getLastTrackedFocusedActivity());
    }

    private void showMediaRouteDialog(MediaStateListener player, MediaRouteController controller,
            Activity activity) {

        FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
        if (fm == null) {
            throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
        }

        MediaRouteDialogFactory factory = new MediaRouteChooserDialogFactory(player, controller, activity);

        if (fm.findFragmentByTag("android.support.v7.mediarouter:MediaRouteChooserDialogFragment") != null) {
            Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
            return;
        }
        MediaRouteChooserDialogFragment f = factory.onCreateChooserDialogFragment();

        f.setRouteSelector(controller.buildMediaRouteSelector());
        f.show(fm, "android.support.v7.mediarouter:MediaRouteChooserDialogFragment");
    }

    private void showMediaRouteControlDialog(Activity activity) {

        FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
        if (fm == null) {
            throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
        }
        MediaRouteDialogFactory factory = new MediaRouteControllerDialogFactory();

        if (fm.findFragmentByTag("android.support.v7.mediarouter:MediaRouteControllerDialogFragment") != null) {
            Log.w(TAG, "showDialog(): Route controller dialog already showing!");
            return;
        }
        MediaRouteControllerDialogFragment f = factory.onCreateControllerDialogFragment();

        f.show(fm, "android.support.v7.mediarouter:MediaRouteControllerDialogFragment");
    }

    /**
     * @return the currently playing MediaRouteController
     */
    public MediaRouteController getCurrentlyPlayingMediaRouteController() {
        return mCurrentRouteController;
    }

    /**
     * Set the current MediaRouteController
     * @param controller the controller
     */
    public void setCurrentMediaRouteController(MediaRouteController controller) {
        mCurrentRouteController = controller;
    }

    private CastNotificationControl getNotificationControl() {
        return mNotificationControl;
    }

    @Override
    public void onPrepared(MediaRouteController mediaRouteController) {
    }

    @Override
    public void onPlaybackStateChanged(PlayerState newState) {
    }

    @Override
    public void onError(int error, String errorMessage) {
        if (error == CastMediaControlIntent.ERROR_CODE_SESSION_START_FAILED) {
            showMessageToast(errorMessage);
        }
    }

    @Override
    public void onDurationUpdated(long durationMillis) {
    }

    @Override
    public void onPositionChanged(long positionMillis) {
    }

    @Override
    public void onTitleChanged(String title) {
    }

    @Override
    public void onRouteSelected(String routeName, MediaRouteController mediaRouteController) {
        if (mCurrentRouteController != mediaRouteController) {
            mCurrentRouteController = mediaRouteController;
            resetPlayingVideo();
        }
    }

    /**
     * Gets some text to tell the user that the video is being cast.
     * @param routeName The name of the route on which the video is being cast.
     * @return A String to be shown to the user.
     */
    public String getCastingMessage(String routeName) {
        String castingMessage = DEFAULT_CASTING_MESSAGE;
        if (mCastContextApplicationContext != null) {
            castingMessage = mCastContextApplicationContext.getString(R.string.cast_casting_video, routeName);
        }
        return castingMessage;
    }

    // Note that, after switching MediaRouteControllers onRouteUnselected may be called for
    // the old media route controller, so this should not do anything to
    // mCurrentRouteController
    @Override
    public void onRouteUnselected(MediaRouteController mediaRouteController) {
        if (mediaRouteController == mCurrentRouteController) {
            mCurrentRouteController = null;
        }
    }

    private void showMessageToast(String message) {
        Toast toast = Toast.makeText(mCastContextApplicationContext, message, Toast.LENGTH_SHORT);
        toast.show();
    }

    private void resetPlayingVideo() {
        if (mNotificationControl != null) {
            mNotificationControl.setRouteController(mCurrentRouteController);
        }
    }

    @VisibleForTesting
    static RemoteMediaPlayerController getIfExists() {
        return sInstance;
    }

}