/*
* Copyright (C) 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.libraries.cast.companionlibrary.cast.player;
import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.MediaTrack;
import com.google.android.gms.cast.RemoteMediaPlayer;
import com.google.android.libraries.cast.companionlibrary.R;
import com.google.android.libraries.cast.companionlibrary.cast.CastConfiguration;
import com.google.android.libraries.cast.companionlibrary.cast.MediaQueue;
import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
import com.google.android.libraries.cast.companionlibrary.utils.FetchBitmapTask;
import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
import com.google.android.libraries.cast.companionlibrary.utils.Utils;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.View;
import android.widget.SeekBar;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* A fragment that provides a mechanism to retain the state and other needed objects for
* {@link VideoCastControllerActivity} (or more generally, for any class implementing
* {@link VideoCastController} interface). This can come very handy when set up of that activity
* allows for a configuration changes. Most of the logic required for
* {@link VideoCastControllerActivity} is maintained in this fragment to enable application
* developers provide a different implementation, if desired.
* <p/>
* This fragment also provides an implementation of {@link MediaAuthListener} which can be useful
* if a pre-authorization is required for playback of a media.
*/
public class VideoCastControllerFragment extends Fragment implements
OnVideoCastControllerListener, MediaAuthListener {
private static final String EXTRAS = "extras";
private static final String TAG = LogUtils.makeLogTag(VideoCastControllerFragment.class);
private MediaInfo mSelectedMedia;
private VideoCastManager mCastManager;
private MediaAuthService mMediaAuthService;
private Thread mAuthThread;
private Timer mMediaAuthTimer;
private Handler mHandler;
protected boolean mAuthSuccess = true;
private VideoCastController mCastController;
private FetchBitmapTask mImageAsyncTask;
private Timer mSeekbarTimer;
private int mPlaybackState;
private MyCastConsumer mCastConsumer;
private OverallState mOverallState = OverallState.UNKNOWN;
private UrlAndBitmap mUrlAndBitmap;
private static boolean sDialogCanceled = false;
private boolean mIsFresh = true;
private MediaStatus mMediaStatus;
private enum OverallState {
AUTHORIZING, PLAYBACK, UNKNOWN
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
sDialogCanceled = false;
mCastController = (VideoCastController) activity;
mHandler = new Handler();
mCastManager = VideoCastManager.getInstance();
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mCastConsumer = new MyCastConsumer();
Bundle bundle = getArguments();
if (bundle == null) {
return;
}
Bundle extras = bundle.getBundle(EXTRAS);
Bundle mediaWrapper = extras.getBundle(VideoCastManager.EXTRA_MEDIA);
// Retain this fragment across configuration changes.
setRetainInstance(true);
mCastManager.addTracksSelectedListener(this);
boolean explicitStartActivity = mCastManager.getPreferenceAccessor()
.getBooleanFromPreference(VideoCastManager.PREFS_KEY_START_ACTIVITY, false);
if (explicitStartActivity) {
mIsFresh = true;
}
mCastManager.getPreferenceAccessor().saveBooleanToPreference(
VideoCastManager.PREFS_KEY_START_ACTIVITY, false);
mCastController.setNextPreviousVisibilityPolicy(
mCastManager.getCastConfiguration().getNextPrevVisibilityPolicy());
if (extras.getBoolean(VideoCastManager.EXTRA_HAS_AUTH)) {
if (mIsFresh) {
mOverallState = OverallState.AUTHORIZING;
mMediaAuthService = mCastManager.getMediaAuthService();
handleMediaAuthTask(mMediaAuthService);
showImage(Utils.getImageUri(mMediaAuthService.getMediaInfo(), 1));
}
} else if (mediaWrapper != null) {
mOverallState = OverallState.PLAYBACK;
boolean shouldStartPlayback = extras.getBoolean(VideoCastManager.EXTRA_SHOULD_START);
String customDataStr = extras.getString(VideoCastManager.EXTRA_CUSTOM_DATA);
JSONObject customData = null;
if (!TextUtils.isEmpty(customDataStr)) {
try {
customData = new JSONObject(customDataStr);
} catch (JSONException e) {
LOGE(TAG, "Failed to unmarshalize custom data string: customData="
+ customDataStr, e);
}
}
MediaInfo info = Utils.bundleToMediaInfo(mediaWrapper);
int startPoint = extras.getInt(VideoCastManager.EXTRA_START_POINT, 0);
onReady(info, shouldStartPlayback && explicitStartActivity, startPoint, customData);
}
}
/*
* Starts a background thread for starting the Auth Service
*/
private void handleMediaAuthTask(final MediaAuthService authService) {
mCastController.showLoading(true);
if (authService == null) {
return;
}
mCastController.setSubTitle(authService.getPendingMessage() != null
? authService.getPendingMessage() : "");
mAuthThread = new Thread(new Runnable() {
@Override
public void run() {
authService.setMediaAuthListener(VideoCastControllerFragment.this);
authService.startAuthorization();
}
});
mAuthThread.start();
// start a timeout timer; we don't want authorization process to take too long
mMediaAuthTimer = new Timer();
mMediaAuthTimer.schedule(new MediaAuthServiceTimerTask(mAuthThread),
authService.getTimeout());
}
/*
* A TimerTask that will be called when the auth timer expires
*/
class MediaAuthServiceTimerTask extends TimerTask {
private final Thread mThread;
public MediaAuthServiceTimerTask(Thread thread) {
this.mThread = thread;
}
@Override
public void run() {
if (mThread != null) {
LOGD(TAG, "Timer is expired, going to interrupt the thread");
mThread.interrupt();
mHandler.post(new Runnable() {
@Override
public void run() {
mCastController.showLoading(false);
showErrorDialog(getString(R.string.ccl_failed_authorization_timeout));
mAuthSuccess = false;
if ((mMediaAuthService != null)
&& (mMediaAuthService.getStatus() == MediaAuthStatus.PENDING)) {
mMediaAuthService.abortAuthorization(MediaAuthStatus.TIMED_OUT);
}
}
});
}
}
}
private class MyCastConsumer extends VideoCastConsumerImpl {
@Override
public void onDisconnected() {
mCastController.closeActivity();
}
@Override
public void onApplicationDisconnected(int errorCode) {
mCastController.closeActivity();
}
@Override
public void onRemoteMediaPlayerMetadataUpdated() {
try {
mSelectedMedia = mCastManager.getRemoteMediaInformation();
updateClosedCaptionState();
updateMetadata();
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
LOGE(TAG, "Failed to update the metadata due to network issues", e);
}
}
@Override
public void onMediaLoadResult(int statusCode) {
if (CastStatusCodes.SUCCESS != statusCode) {
LOGD(TAG, "onMediaLoadResult(): Failed to load media with status code: "
+ statusCode);
Utils.showToast(getActivity(), R.string.ccl_failed_to_load_media);
mCastController.closeActivity();
}
}
@Override
public void onFailed(int resourceId, int statusCode) {
LOGD(TAG, "onFailed(): " + getString(resourceId) + ", status code: " + statusCode);
if (statusCode == RemoteMediaPlayer.STATUS_FAILED
|| statusCode == RemoteMediaPlayer.STATUS_TIMED_OUT) {
Utils.showToast(getActivity(), resourceId);
mCastController.closeActivity();
}
}
@Override
public void onRemoteMediaPlayerStatusUpdated() {
updatePlayerStatus();
}
@Override
public void onMediaQueueUpdated(List<MediaQueueItem> queueItems, MediaQueueItem item,
int repeatMode, boolean shuffle) {
int size = 0;
int position = 0;
if (queueItems != null) {
size = queueItems.size();
position = queueItems.indexOf(item);
}
mCastController.onQueueItemsUpdated(size, position);
}
@Override
public void onConnectionSuspended(int cause) {
mCastController.updateControllersStatus(false);
}
@Override
public void onConnectivityRecovered() {
mCastController.updateControllersStatus(true);
}
}
private class UpdateSeekbarTask extends TimerTask {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
int currentPos;
if (mPlaybackState == MediaStatus.PLAYER_STATE_BUFFERING) {
return;
}
if (!mCastManager.isConnected()) {
return;
}
try {
int duration = (int) mCastManager.getMediaDuration();
if (duration > 0) {
try {
currentPos = (int) mCastManager.getCurrentMediaPosition();
mCastController.updateSeekbar(currentPos, duration);
} catch (Exception e) {
LOGE(TAG, "Failed to get current media position", e);
}
}
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
LOGE(TAG, "Failed to update the progress bar due to network issues", e);
}
}
});
}
}
/**
* Loads the media on the cast device.
*
* @param mediaInfo The media to be loaded
* @param shouldStartPlayback If {@code true}, playback starts after load automatically
* @param startPoint The position to start the play back
* @param customData An optional custom data to be sent along the load api; it can be
* {@code null}
*/
private void onReady(MediaInfo mediaInfo, boolean shouldStartPlayback, int startPoint,
JSONObject customData) {
mSelectedMedia = mediaInfo;
updateClosedCaptionState();
try {
mCastController.setStreamType(mSelectedMedia.getStreamType());
if (shouldStartPlayback) {
// need to start remote playback
mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
mCastController.setPlaybackStatus(mPlaybackState);
mCastManager.loadMedia(mSelectedMedia, true, startPoint, customData);
} else {
// we don't change the status of remote playback
if (mCastManager.isRemoteMediaPlaying()) {
mPlaybackState = MediaStatus.PLAYER_STATE_PLAYING;
} else {
mPlaybackState = MediaStatus.PLAYER_STATE_PAUSED;
}
mCastController.setPlaybackStatus(mPlaybackState);
}
} catch (Exception e) {
LOGE(TAG, "Failed to get playback and media information", e);
mCastController.closeActivity();
}
MediaQueue mediaQueue = mCastManager.getMediaQueue();
int size = 0;
int position = 0;
if (mediaQueue != null) {
size = mediaQueue.getCount();
position = mediaQueue.getCurrentItemPosition();
}
mCastController.onQueueItemsUpdated(size, position);
updateMetadata();
restartTrickplayTimer();
}
private void updateClosedCaptionState() {
int state = VideoCastController.CC_HIDDEN;
if (mCastManager.isFeatureEnabled(CastConfiguration.FEATURE_CAPTIONS_PREFERENCE)
&& mSelectedMedia != null
&& mCastManager.getTracksPreferenceManager().isCaptionEnabled()) {
List<MediaTrack> tracks = mSelectedMedia.getMediaTracks();
state = Utils.hasAudioOrTextTrack(tracks) ? VideoCastController.CC_ENABLED
: VideoCastController.CC_DISABLED;
}
mCastController.setClosedCaptionState(state);
}
private void stopTrickplayTimer() {
LOGD(TAG, "Stopped TrickPlay Timer");
if (mSeekbarTimer != null) {
mSeekbarTimer.cancel();
}
}
private void restartTrickplayTimer() {
stopTrickplayTimer();
mSeekbarTimer = new Timer();
mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), 100, 1000);
LOGD(TAG, "Restarted TrickPlay Timer");
}
private void updateOverallState() {
MediaAuthService authService;
switch (mOverallState) {
case AUTHORIZING:
authService = mCastManager.getMediaAuthService();
if (authService != null) {
mCastController.setSubTitle(authService.getPendingMessage() != null
? authService.getPendingMessage() : "");
mCastController.showLoading(true);
}
break;
case PLAYBACK:
// nothing yet, may be needed in future
break;
default:
break;
}
}
private void updateMetadata() {
Uri imageUrl = null;
if (mSelectedMedia == null) {
if (mMediaAuthService != null) {
imageUrl = Utils.getImageUri(mMediaAuthService.getMediaInfo(), 1);
}
} else {
imageUrl = Utils.getImageUri(mSelectedMedia, 1);
}
showImage(imageUrl);
if (mSelectedMedia == null) {
return;
}
MediaMetadata mm = mSelectedMedia.getMetadata();
mCastController.setTitle(mm.getString(MediaMetadata.KEY_TITLE) != null
? mm.getString(MediaMetadata.KEY_TITLE) : "");
boolean isLive = mSelectedMedia.getStreamType() == MediaInfo.STREAM_TYPE_LIVE;
mCastController.adjustControllersForLiveStream(isLive);
}
private void updatePlayerStatus() {
int mediaStatus = mCastManager.getPlaybackStatus();
mMediaStatus = mCastManager.getMediaStatus();
LOGD(TAG, "updatePlayerStatus(), state: " + mediaStatus);
if (mSelectedMedia == null) {
return;
}
mCastController.setStreamType(mSelectedMedia.getStreamType());
if (mediaStatus == MediaStatus.PLAYER_STATE_BUFFERING) {
mCastController.setSubTitle(getString(R.string.ccl_loading));
} else {
mCastController.setSubTitle(getString(R.string.ccl_casting_to_device,
mCastManager.getDeviceName()));
}
switch (mediaStatus) {
case MediaStatus.PLAYER_STATE_PLAYING:
mIsFresh = false;
if (mPlaybackState != MediaStatus.PLAYER_STATE_PLAYING) {
mPlaybackState = MediaStatus.PLAYER_STATE_PLAYING;
mCastController.setPlaybackStatus(mPlaybackState);
}
break;
case MediaStatus.PLAYER_STATE_PAUSED:
mIsFresh = false;
if (mPlaybackState != MediaStatus.PLAYER_STATE_PAUSED) {
mPlaybackState = MediaStatus.PLAYER_STATE_PAUSED;
mCastController.setPlaybackStatus(mPlaybackState);
}
break;
case MediaStatus.PLAYER_STATE_BUFFERING:
mIsFresh = false;
if (mPlaybackState != MediaStatus.PLAYER_STATE_BUFFERING) {
mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
mCastController.setPlaybackStatus(mPlaybackState);
}
break;
case MediaStatus.PLAYER_STATE_IDLE:
LOGD(TAG, "Idle Reason: " + (mCastManager.getIdleReason()));
switch (mCastManager.getIdleReason()) {
case MediaStatus.IDLE_REASON_FINISHED:
if (!mIsFresh && (mMediaStatus == null || mMediaStatus.getLoadingItemId()
== MediaQueueItem.INVALID_ITEM_ID)) {
mCastController.closeActivity();
}
break;
case MediaStatus.IDLE_REASON_CANCELED:
try {
if (mCastManager.isRemoteStreamLive()) {
if (mPlaybackState != MediaStatus.PLAYER_STATE_IDLE) {
mPlaybackState = MediaStatus.PLAYER_STATE_IDLE;
mCastController.setPlaybackStatus(mPlaybackState);
}
} else {
mCastController.closeActivity();
}
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
LOGD(TAG, "Failed to determine if stream is live", e);
}
break;
case MediaStatus.IDLE_REASON_INTERRUPTED:
mPlaybackState = MediaStatus.PLAYER_STATE_IDLE;
mCastController.setPlaybackStatus(mPlaybackState);
break;
default:
break;
}
break;
default:
break;
}
}
@Override
public void onDestroy() {
LOGD(TAG, "onDestroy()");
stopTrickplayTimer();
cleanup();
super.onDestroy();
}
@Override
public void onResume() {
super.onResume();
try {
if (mCastManager.isRemoteMediaPaused() || mCastManager.isRemoteMediaPlaying()) {
if (mCastManager.getRemoteMediaInformation() != null
&& TextUtils.equals(mSelectedMedia.getContentId(),
mCastManager.getRemoteMediaInformation().getContentId())) {
mIsFresh = false;
}
}
if (!mCastManager.isConnecting()) {
boolean shouldFinish = !mCastManager.isConnected()
|| (mCastManager.getPlaybackStatus() == MediaStatus.PLAYER_STATE_IDLE
&& mCastManager.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED);
if (shouldFinish && !mIsFresh) {
mCastController.closeActivity();
return;
}
}
mMediaStatus = mCastManager.getMediaStatus();
mCastManager.addVideoCastConsumer(mCastConsumer);
if (!mIsFresh) {
updatePlayerStatus();
// updating metadata in case another client has changed it and we are resuming the
// activity
mSelectedMedia = mCastManager.getRemoteMediaInformation();
updateClosedCaptionState();
updateMetadata();
}
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
LOGE(TAG, "Failed to get media information or status of media playback", e);
if (e instanceof NoConnectionException) {
mCastController.closeActivity();
}
} finally {
mCastManager.incrementUiCounter();
}
}
@Override
public void onPause() {
mCastManager.removeVideoCastConsumer(mCastConsumer);
mCastManager.decrementUiCounter();
mIsFresh = false;
super.onPause();
}
/**
* Call this static method to create an instance of this fragment.
*/
public static VideoCastControllerFragment newInstance(Bundle extras) {
VideoCastControllerFragment f = new VideoCastControllerFragment();
Bundle b = new Bundle();
b.putBundle(EXTRAS, extras);
f.setArguments(b);
return f;
}
/*
* Gets the image at the given url and populates the image view with that. It tries to cache the
* image to avoid unnecessary network calls.
*/
private void showImage(final Uri uri) {
if (uri == null) {
mCastController.setImage(BitmapFactory.decodeResource(getActivity().getResources(),
R.drawable.album_art_placeholder_large));
return;
}
if (mUrlAndBitmap != null && mUrlAndBitmap.isMatch(uri)) {
// we can reuse mBitmap
mCastController.setImage(mUrlAndBitmap.mBitmap);
return;
}
mUrlAndBitmap = null;
if (mImageAsyncTask != null) {
mImageAsyncTask.cancel(true);
}
Point screenSize = Utils.getDisplaySize(getActivity());
mImageAsyncTask = new FetchBitmapTask(screenSize.x, screenSize.y, false) {
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
mUrlAndBitmap = new UrlAndBitmap();
mUrlAndBitmap.mBitmap = bitmap;
mUrlAndBitmap.mUrl = uri;
if (!isCancelled()) {
mCastController.setImage(bitmap);
}
}
if (this == mImageAsyncTask) {
mImageAsyncTask = null;
}
}
};
mImageAsyncTask.execute(uri);
}
/**
* A modal dialog with an OK button, where upon clicking on it, will finish the activity. We
* use a DialogFragment so during configuration changes, system manages the dialog for us.
*/
public static class ErrorDialogFragment extends DialogFragment {
private VideoCastController mController;
private static final String MESSAGE = "message";
public static ErrorDialogFragment newInstance(String message) {
ErrorDialogFragment frag = new ErrorDialogFragment();
Bundle args = new Bundle();
args.putString(MESSAGE, message);
frag.setArguments(args);
return frag;
}
@Override
public void onAttach(Activity activity) {
mController = (VideoCastController) activity;
super.onAttach(activity);
setCancelable(false);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String message = getArguments().getString(MESSAGE);
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.ccl_error)
.setMessage(message)
.setPositiveButton(R.string.ccl_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sDialogCanceled = true;
mController.closeActivity();
}
})
.create();
}
}
/*
* Shows an error dialog
*/
private void showErrorDialog(String message) {
ErrorDialogFragment.newInstance(message).show(getFragmentManager(), "dlg");
}
@Override
public void onStop() {
super.onStop();
if (mImageAsyncTask != null) {
mImageAsyncTask.cancel(true);
mImageAsyncTask = null;
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
try {
if (mPlaybackState == MediaStatus.PLAYER_STATE_PLAYING) {
mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
mCastController.setPlaybackStatus(mPlaybackState);
mCastManager.play(seekBar.getProgress());
} else if (mPlaybackState == MediaStatus.PLAYER_STATE_PAUSED) {
mCastManager.seek(seekBar.getProgress());
}
restartTrickplayTimer();
} catch (Exception e) {
LOGE(TAG, "Failed to complete seek", e);
mCastController.closeActivity();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
stopTrickplayTimer();
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onPlayPauseClicked(View v) throws CastException,
TransientNetworkDisconnectionException, NoConnectionException {
LOGD(TAG, "isConnected returning: " + mCastManager.isConnected());
togglePlayback();
}
private void togglePlayback() throws CastException, TransientNetworkDisconnectionException,
NoConnectionException {
switch (mPlaybackState) {
case MediaStatus.PLAYER_STATE_PAUSED:
mCastManager.play();
mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
restartTrickplayTimer();
break;
case MediaStatus.PLAYER_STATE_PLAYING:
mCastManager.pause();
mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
break;
case MediaStatus.PLAYER_STATE_IDLE:
if ((mSelectedMedia.getStreamType() == MediaInfo.STREAM_TYPE_LIVE)
&& (mCastManager.getIdleReason() == MediaStatus.IDLE_REASON_CANCELED)) {
mCastManager.play();
} else {
mCastManager.loadMedia(mSelectedMedia, true, 0);
}
mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
restartTrickplayTimer();
break;
default:
break;
}
mCastController.setPlaybackStatus(mPlaybackState);
}
@Override
public void onConfigurationChanged() {
updateOverallState();
if (mSelectedMedia == null) {
if (mMediaAuthService != null) {
showImage(Utils.getImageUri(mMediaAuthService.getMediaInfo(), 1));
}
} else {
updateMetadata();
updatePlayerStatus();
mCastController.updateControllersStatus(mCastManager.isConnected());
}
}
@Override
public void onAuthResult(MediaAuthStatus status, final MediaInfo info, final String message,
final int startPoint, final JSONObject customData) {
if (status == MediaAuthStatus.AUTHORIZED && mAuthSuccess) {
// successful authorization
mMediaAuthService = null;
if (mMediaAuthTimer != null) {
mMediaAuthTimer.cancel();
}
mSelectedMedia = info;
updateClosedCaptionState();
mHandler.post(new Runnable() {
@Override
public void run() {
mOverallState = OverallState.PLAYBACK;
onReady(info, true, startPoint, customData);
}
});
} else {
if (mMediaAuthTimer != null) {
mMediaAuthTimer.cancel();
}
mHandler.post(new Runnable() {
@Override
public void run() {
mOverallState = OverallState.UNKNOWN;
showErrorDialog(message);
}
});
}
}
@Override
public void onAuthFailure(final String failureMessage) {
if (mMediaAuthTimer != null) {
mMediaAuthTimer.cancel();
}
mHandler.post(new Runnable() {
@Override
public void run() {
mOverallState = OverallState.UNKNOWN;
showErrorDialog(failureMessage);
}
});
}
@Override
public void onTracksSelected(List<MediaTrack> tracks) {
mCastManager.setActiveTracks(tracks);
}
/*
* A simple class that holds a URL and a bitmap, mainly used to cache the fetched image
*/
private class UrlAndBitmap {
private Bitmap mBitmap;
private Uri mUrl;
private boolean isMatch(Uri url) {
return url != null && mBitmap != null && url.equals(mUrl);
}
}
/*
* Cleanup of threads and timers and bitmap and ...
*/
private void cleanup() {
MediaAuthService authService = mCastManager.getMediaAuthService();
if (mMediaAuthTimer != null) {
mMediaAuthTimer.cancel();
}
if (mAuthThread != null) {
mAuthThread = null;
}
if (mCastManager.getMediaAuthService() != null) {
authService.setMediaAuthListener(null);
mCastManager.removeMediaAuthService();
}
if (mCastManager != null) {
mCastManager.removeVideoCastConsumer(mCastConsumer);
}
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
if (mUrlAndBitmap != null) {
mUrlAndBitmap.mBitmap = null;
}
if (!sDialogCanceled && mMediaAuthService != null) {
mMediaAuthService.abortAuthorization(MediaAuthStatus.CANCELED_BY_USER);
}
mCastManager.removeTracksSelectedListener(this);
}
@Override
public void onSkipNextClicked(View view)
throws TransientNetworkDisconnectionException, NoConnectionException {
mCastController.showLoading(true);
mCastManager.queueNext(null);
}
@Override
public void onSkipPreviousClicked(View view)
throws TransientNetworkDisconnectionException, NoConnectionException {
mCastController.showLoading(true);
mCastManager.queuePrev(null);
}
}