package com.kaltura.playersdk.players; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaMetadata; import com.google.android.gms.cast.MediaStatus; import com.google.android.gms.cast.framework.CastSession; import com.google.android.gms.cast.framework.media.RemoteMediaClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.images.WebImage; import com.kaltura.playersdk.interfaces.KCastMediaRemoteControl; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import static com.kaltura.playersdk.utils.LogUtils.LOGD; import static com.kaltura.playersdk.utils.LogUtils.LOGE; /** * Created by nissimpardo on 07/07/16. */ public class KChromeCastPlayer implements KCastMediaRemoteControl{ public static final String KEY_DESCRIPTION = "description"; public static final String KEY_ENTRY_ID = "entryid"; private CastSession mCastSession; private Handler mHandler = new Handler(Looper.getMainLooper()); private static int PLAYHEAD_UPDATE_INTERVAL = 200; private ArrayList<KCastMediaRemoteControlListener> mListeners = new ArrayList<>(); private RemoteMediaClient.Listener mRemoteMediaClientListener = null; private State mState; private String[] mMediaInfoParams; private boolean isEnded = true; private HashMap<String, Integer> mTextTracks; private List<Integer> mVideoTracks; private int currentSelectedTextTrack = 0; private String mEntryId = ""; private String mEntryName = ""; private String mEntryDescription = ""; private String mEntryThumbnailUrl = ""; String TAG = "KChromeCastPlayer"; public KChromeCastPlayer(CastSession castSession) { LOGD(TAG, "onStatusUpdated NEW OBJECT OF KChromeCastPlayer"); isEnded = true; setRemoteMediaClientListener(); mCastSession = castSession; mCastSession.getRemoteMediaClient().addListener(getRemoteMediaClientListener()); } private void setRemoteMediaClientListener() { mRemoteMediaClientListener = new RemoteMediaClient.Listener() { @Override public void onStatusUpdated() { if (mCastSession == null || mCastSession.getRemoteMediaClient() == null || mCastSession.getRemoteMediaClient().getMediaStatus() == null) { return; } MediaStatus mediaStatus = mCastSession.getRemoteMediaClient().getMediaStatus(); int playerState = mCastSession.getRemoteMediaClient().getMediaStatus().getPlayerState(); if (mediaStatus != null) { LOGD(TAG, "onStatusUpdated playerStatus = " + playerState); LOGD(TAG, "onStatusUpdated mediaStatus = " + mediaStatus.getIdleReason()); switch (playerState) { case MediaStatus.PLAYER_STATE_IDLE: LOGD(TAG, "onStatusUpdated isEnded = " + isEnded); if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) { if (isEnded) { LOGD(TAG, "onStatusUpdated ALREADY ENDED RETUEN"); return; } isEnded = true; stopTimer(); LOGD(TAG, "onStatusUpdated ENDED"); updateState(State.Ended); } break; case MediaStatus.PLAYER_STATE_PAUSED: //LOGD(TAG, "onStatusUpdated playerStatus = PLAYER_STATE_PAUSED"); isEnded = false; //updateState(State.Pause); break; case MediaStatus.PLAYER_STATE_PLAYING: //LOGD(TAG, "onStatusUpdated playerStatus = PLAYER_STATE_PLAYING"); isEnded = false; //updateState(State.Playing); break; case MediaStatus.PLAYER_STATE_BUFFERING: //LOGD(TAG, "onStatusUpdated playerStatus = PLAYER_STATE_BUFFERING"); isEnded = false; break; } } } @Override public void onMetadataUpdated() { //LOGD(TAG, "onStatusUpdated onMetadataUpdated"); } @Override public void onQueueStatusUpdated() { } @Override public void onPreloadStatusUpdated() { } @Override public void onSendingRemoteMediaRequest() { } }; } public RemoteMediaClient.Listener getRemoteMediaClientListener() { return mRemoteMediaClientListener; } public void setMediaInfoParams(final String[] mediaInfoParams) { mMediaInfoParams = mediaInfoParams; } public void load(final long fromPosition, String entryTitle, String entryDescription, String entryThumbnailUrl, String entryId) { if (mCastSession == null) { return; } //Init the tracks mTextTracks = new HashMap<>(); mVideoTracks = new ArrayList<>(); mEntryId = entryId; mEntryName = entryTitle; mEntryDescription = entryDescription; mEntryThumbnailUrl = entryThumbnailUrl; LOGD(TAG, "CC LOAD " + mEntryName); JSONObject descriptionJsonObj = null; try { descriptionJsonObj = new JSONObject(); descriptionJsonObj.put(KEY_DESCRIPTION, mEntryDescription); } catch (JSONException e) { sendError("CC Failed to add description to the json object", e); } MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); if (mCastSession != null && mCastSession.getCastDevice() != null && !"".equals(mCastSession.getCastDevice().getFriendlyName())) { mediaMetadata.putString(MediaMetadata.KEY_SUBTITLE, "Casting to " + mCastSession.getCastDevice().getFriendlyName()); } mediaMetadata.putString(MediaMetadata.KEY_TITLE, mEntryName); mediaMetadata.putString(KEY_ENTRY_ID, mEntryId); //small thumbnail ////mediaMetadata.addImage(new WebImage(Uri.parse(mEntryThumbnailUrl)));// + "/width/480/hight/270"))); if (mEntryThumbnailUrl != null && !mEntryThumbnailUrl.isEmpty()) { //big thumbnail if (mEntryId.contains("_")) { mediaMetadata.addImage(new WebImage(Uri.parse(mEntryThumbnailUrl + "/width/1200/hight/780")));//"/width/480/hight/270"))); } else { mediaMetadata.addImage(new WebImage(Uri.parse(mEntryThumbnailUrl))); } } MediaInfo mediaInfo = new MediaInfo.Builder( mMediaInfoParams[0]) .setContentType(mMediaInfoParams[1]) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setMetadata(mediaMetadata) .setCustomData(descriptionJsonObj) .build(); RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); remoteMediaClient.load(mediaInfo, false, fromPosition).setResultCallback(new ResultCallback<RemoteMediaClient.MediaChannelResult>() { @Override public void onResult(@NonNull RemoteMediaClient.MediaChannelResult mediaChannelResult) { if (!mediaChannelResult.getStatus().isSuccess()) { LOGE(TAG, "CC LOAD failed"); sendError("CC Load failed", null); } else { //stopTimer(); startTimer(); updateState(State.Loaded); } } }); } private void startTimer() { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper()); } mHandler.post(new Runnable() { @Override public void run() { try { if (mCastSession == null || mCastSession.getRemoteMediaClient() == null) { return; } long currentTime = mCastSession.getRemoteMediaClient().getApproximateStreamPosition(); if (currentTime != 0 && currentTime < mCastSession.getRemoteMediaClient().getStreamDuration()) { if (mCastSession.getRemoteMediaClient().isPlaying()) { //LOGD(TAG, "CC SEND TIME UPDATE " + currentTime); if(mListeners != null && mListeners.size() > 0) { for (KCastMediaRemoteControlListener listener : mListeners) { listener.onCastMediaProgressUpdate(currentTime); } } } } } catch (IllegalStateException e) { sendError("Failed to request status", e); } mHandler.postDelayed(this, PLAYHEAD_UPDATE_INTERVAL); } }); } private void stopTimer() { if (mHandler != null) { LOGD(TAG, "remove handler callbacks"); mHandler.removeMessages(0); } } public void play() { if (!hasMediaSession(true)) { return; } LOGD(TAG, "Start PLAY"); if (isEnded) { load(0, mEntryName, mEntryDescription , mEntryThumbnailUrl, mEntryId); stopTimer(); startTimer(); updateState(State.Playing); return; } mCastSession.getRemoteMediaClient().play().setResultCallback(new ResultCallback<RemoteMediaClient.MediaChannelResult>() { @Override public void onResult(@NonNull RemoteMediaClient.MediaChannelResult mediaChannelResult) { if (!mediaChannelResult.getStatus().isSuccess()) { LOGE(TAG, "CC Play failed"); sendError("CC Play failed", null); } else { startTimer(); updateState(State.Playing); } } }); } public void pause() { if (!hasMediaSession(true)) { return; } LOGD(TAG, "Start PAUSE"); mCastSession.getRemoteMediaClient().pause().setResultCallback(new ResultCallback<RemoteMediaClient.MediaChannelResult>() { @Override public void onResult(@NonNull RemoteMediaClient.MediaChannelResult mediaChannelResult) { if (!mediaChannelResult.getStatus().isSuccess()) { LOGE(TAG, "CC Pause failed"); sendError("CC Pause failed", null); } else { updateState(State.Pause); } } }); stopTimer(); } public void seek(long currentPosition) { if (!hasMediaSession(true)) { return; } LOGD(TAG, "CC seek to " + currentPosition); LOGD(TAG, "CC SEND SEEKING"); updateState(State.Seeking); mCastSession.getRemoteMediaClient().seek(currentPosition).setResultCallback(new ResultCallback<RemoteMediaClient.MediaChannelResult>() { @Override public void onResult(@NonNull RemoteMediaClient.MediaChannelResult mediaChannelResult) { if (!mediaChannelResult.getStatus().isSuccess()) { LOGE(TAG, "CC Seek to currentPosition failed"); sendError("CC Seek failed", null); } else { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { LOGD(TAG, "CC SEND SEEKED"); updateState(State.Seeked); } }, 2500); } } }); } @Override public boolean isPlaying() { if (mCastSession != null && mCastSession.getRemoteMediaClient() != null) { return mCastSession.getRemoteMediaClient().isPlaying(); } return false; } @Override public void addListener(KCastMediaRemoteControlListener listener) { if (mListeners != null) { if (mListeners.size() == 0 || mListeners.size() > 0 && !mListeners.contains(listener)) { mListeners.add(listener); } } } @Override public void removeListeners() { if (mListeners != null && mListeners.size() > 0) { mListeners.clear(); mListeners = null; } if (mRemoteMediaClientListener != null && mCastSession != null && mCastSession.getRemoteMediaClient() != null ) { mCastSession.getRemoteMediaClient().removeListener(mRemoteMediaClientListener); mRemoteMediaClientListener = null; } stopTimer(); // remove the timer that is responsible for time update //mHandler = null; } @Override public void setStreamVolume(double streamVolume) { if (!hasMediaSession(true)) { return; } LOGD(TAG, "CC setStreamVolume " + streamVolume); mCastSession.getRemoteMediaClient().setStreamVolume(streamVolume).setResultCallback(new ResultCallback<RemoteMediaClient.MediaChannelResult>() { @Override public void onResult(@NonNull RemoteMediaClient.MediaChannelResult mediaChannelResult) { if (!mediaChannelResult.getStatus().isSuccess()) { LOGE(TAG, "CC setStreamVolume failed"); sendError("CC setStreamVolume failed", null); } else { updateState(State.VolumeChanged); } } }); } @Override public double getCurrentVolume() { if (hasMediaSession(true)) { return mCastSession.getRemoteMediaClient().getMediaStatus().getStreamVolume(); } return 0; } @Override public boolean isMute() { if (hasMediaSession(true)) { return mCastSession.getRemoteMediaClient().getMediaStatus().isMute(); } return false; } @Override public void removeListener(KCastMediaRemoteControlListener listener) { if (mListeners != null && mListeners.size() > 0 && mListeners.contains(listener)) { mListeners.remove(listener); } } @Override public State getCastMediaRemoteControlState() { return mState; } @Override public long getCurrentPosition() { if (mCastSession != null && mCastSession.getRemoteMediaClient() != null) { return mCastSession.getRemoteMediaClient().getApproximateStreamPosition(); } return 0; } @Override public long getDuration() { if (mCastSession != null && mCastSession.getRemoteMediaClient() != null) { return mCastSession.getRemoteMediaClient().getStreamDuration(); } return 0; } @Override public boolean hasMediaSession(boolean validateCastConnectingState) { if (mCastSession == null) { return false; } boolean isCastSessionValid = mCastSession.isConnected(); if (validateCastConnectingState) { boolean isCastSessionInConnectingMode = mCastSession.isConnecting(); if (isCastSessionInConnectingMode) { return false; // no session to work with } } return isCastSessionValid; } @Override public void switchTextTrack(int index) { if (mListeners != null) { for (KCastMediaRemoteControlListener listener : mListeners) { currentSelectedTextTrack = index; listener.onTextTrackSwitch(index); } } } @Override public int getSelectedTextTrackIndex() { return currentSelectedTextTrack; } @Override public void setTextTracks(HashMap<String, Integer> textTrackHash) { mTextTracks = textTrackHash; updateState(State.TextTracksUpdated); } @Override public void setVideoTracks(List<Integer> videoTracksList) { mVideoTracks = videoTracksList; } @Override public HashMap<String, Integer> getTextTracks() { return mTextTracks; } @Override public List<Integer> getVideoTracks() { return mVideoTracks; } private void updateState(State state) { if (state != State.VolumeChanged && state != State.TextTracksUpdated) { mState = state; } if (mListeners != null) { for (KCastMediaRemoteControlListener listener : mListeners) { listener.onCastMediaStateChanged(state); } } } private void sendError(String errorMessage, Exception e) { if (mListeners != null) { for (KCastMediaRemoteControlListener listener : mListeners) { listener.onError(errorMessage,e); } } } }