package com.distantfuture.videos.youtube; import android.app.Activity; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import com.distantfuture.videos.misc.AppUtils; import com.distantfuture.videos.misc.Auth; import com.distantfuture.videos.misc.BusEvents; import com.distantfuture.videos.misc.DUtils; import com.distantfuture.videos.misc.SoundManager; import com.distantfuture.videos.misc.Utils; import com.google.android.youtube.player.YouTubeInitializationResult; import com.google.android.youtube.player.YouTubePlayer; import com.google.android.youtube.player.YouTubePlayerFragment; import java.util.Timer; import java.util.TimerTask; import de.greenrobot.event.EventBus; public final class VideoPlayerFragment extends YouTubePlayerFragment { private YouTubePlayer mPlayer; private VideoPlayer.PlayerParams mPlayerParams; private boolean mMutedForAd = false; private Timer mTimer; private TimeRemainingListener mTimeRemainingListener; private boolean mFullscreen = false; private VideoFragmentListener mFragmentListener; private boolean mInitializingPlayer = false; private SoundManager mSoundManager; // added for debugging, remove this shit once we know it's solid private String mLastTimeString; public static VideoPlayerFragment newInstance() { return new VideoPlayerFragment(); } public void setVideoFragmentListener(VideoFragmentListener l) { mFragmentListener = l; } public void closingPlayer() { // pause immediately on click for better UX pause(); // action bar gets hidden by the fullscreen mode, need to show it again when closing player Activity activity = getActivity(); if (activity != null) { activity.getActionBar().show(); activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); } destroyPlayer(); } public VideoPlayer.PlayerParams playerParams() { return mPlayerParams; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mSoundManager = new SoundManager(getActivity()); } @Override public void onPause() { super.onPause(); mSoundManager.restoreMuteIfNeeded(); } @Override public void onDestroy() { destroyPlayer(); mSoundManager.restoreMuteIfNeeded(); super.onDestroy(); } private void destroyPlayer() { stopElapsedTimer(); // fixes case where you start a video, stop it, then replay the same video. if not reset, it would think the video was already running and ignore it. mPlayerParams = null; if (mPlayer != null) { mPlayer.release(); mPlayer = null; } } public String getTitle() { // saw it crash here once, not sure how, could not reproduce, added null check if (mPlayerParams == null) return ""; return mPlayerParams.title; } public String getVideoId() { return mPlayerParams.videoId; } public void setVideo(VideoPlayer.PlayerParams playerParams) { if (playerParams != null && !playerParams.equals(mPlayerParams)) { mPlayerParams = playerParams; if (mPlayer != null) mPlayer.loadVideo(mPlayerParams.videoId); else initializePlayer(); } } public void pause() { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.pause(); } public void mute(boolean muteState) { mSoundManager.mute(muteState); } public boolean isMute() { return mSoundManager.isMute(); } public void setFullscreen(boolean state) { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.setFullscreen(state); } public void seekRelativeSeconds(int seconds) { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.seekRelativeMillis(seconds * 1000); } public void setTimeRemainingListener(TimeRemainingListener listener) { mTimeRemainingListener = listener; } private void setupFullscreenListener() { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.setOnFullscreenListener(new YouTubePlayer.OnFullscreenListener() { public void onFullscreen(boolean isFullscreen) { DUtils.log("setOnFullscreenListener: " + (isFullscreen ? "yes" : "no")); VideoPlayerFragment.this.mFullscreen = isFullscreen; mFragmentListener.onFullScreen(isFullscreen); } }); } public int getCurrentTimeMillis() { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return 0; } return mPlayer.getCurrentTimeMillis(); } public int getDurationMillis() { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return 0; } return mPlayer.getDurationMillis(); } public void seekToMillis(int i) { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.seekToMillis(i); } private void setupStateChangeListener() { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.setPlayerStateChangeListener(new YouTubePlayer.PlayerStateChangeListener() { @Override public void onLoading() { } @Override public void onLoaded(String s) { } @Override public void onAdStarted() { if (!isMute()) { boolean muteAds = AppUtils.instance(VideoPlayerFragment.this.getActivity()).muteAds(); if (muteAds) { mMutedForAd = true; mute(true); } } } @Override public void onVideoStarted() { if (mMutedForAd) { mMutedForAd = false; mute(false); } } @Override public void onVideoEnded() { // playnext has precedence over repeat, both could be on if user set them if (AppUtils.instance(getActivity()).playNext()) { if (mPlayer != null) EventBus.getDefault().post(new BusEvents.PlayNextEvent(mPlayerParams)); else DUtils.log("playnext canceled, player is null"); } else if (AppUtils.instance(getActivity()).repeatVideo()) { if (mPlayer != null) mPlayer.play(); // back to the start else DUtils.log("repeat canceled, player is null"); } } @Override public void onError(YouTubePlayer.ErrorReason errorReason) { } }); } private void setupPlaybackEventListener() { if (mPlayer == null) { DUtils.log("mPlayer is null inside: " + DUtils.currentMethod()); return; } mPlayer.setPlaybackEventListener(new YouTubePlayer.PlaybackEventListener() { @Override public void onPlaying() { startElapsedTimer(); } @Override public void onPaused() { stopElapsedTimer(); } @Override public void onStopped() { stopElapsedTimer(); } @Override public void onBuffering(boolean b) { // Utils.log("buffering: " + ((b) ? "yes" : "no")); } @Override public void onSeekTo(int newPositionMillis) { // Utils.log("seeking: " + newPositionMillis / 1000 + " seconds"); final String seekString = Utils.millisecondsToDuration(newPositionMillis); AppUtils.instance(VideoPlayerFragment.this.getActivity()).runOnMainThread(new Runnable() { @Override public void run() { // we're on the main thread... mTimeRemainingListener.setSeekFlashText(seekString); } }); } }); } private void stopElapsedTimer() { if (mTimer != null) { mTimer.cancel(); mTimer = null; } } private void startElapsedTimer() { if (mTimer == null) { mTimer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { if (mTimeRemainingListener != null) { long millis = getCurrentTimeMillis(); final String timeString = Utils.millisecondsToDuration(millis); // added for debugging, remove this shit once we know it's solid mLastTimeString = (mLastTimeString == null) ? "" : mLastTimeString; if (timeString.equals(mLastTimeString)) DUtils.log("equal to last"); else { mLastTimeString = timeString; } // activity can go null on configuration change Activity activity = VideoPlayerFragment.this.getActivity(); if (activity != null) { AppUtils.instance(activity).runOnMainThread(new Runnable() { @Override public void run() { // we're on the main thread... mTimeRemainingListener.setTimeRemainingText(timeString); } }); } } } }; mTimer.schedule(timerTask, 0, 1000); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { // if (mPlayer != null) // mPlayer.setFullscreen(true); // } // if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { // if (mPlayer != null) // mPlayer.setFullscreen(false); // } } private void initializePlayer() { if (!mInitializingPlayer) { mInitializingPlayer = true; // creates a player async, not sure why initialize(Auth.devKey(), new YouTubePlayer.OnInitializedListener() { @Override public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer player, boolean restored) { mPlayer = player; player.setPlayerStyle(YouTubePlayer.PlayerStyle.DEFAULT); // this handles landscape perfectly, nothing more to do mPlayer.addFullscreenControlFlag(YouTubePlayer.FULLSCREEN_FLAG_ALWAYS_FULLSCREEN_IN_LANDSCAPE | YouTubePlayer.FULLSCREEN_FLAG_CUSTOM_LAYOUT); setupFullscreenListener(); setupStateChangeListener(); setupPlaybackEventListener(); if (!restored && mPlayerParams != null) { player.loadVideo(mPlayerParams.videoId); } mFragmentListener.playerInitialized(); mInitializingPlayer = false; } @Override public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult result) { mPlayer = null; mInitializingPlayer = false; DUtils.log("initializePlayer: failed" + result); } }); } } public interface TimeRemainingListener { // call this on the main thread public void setTimeRemainingText(final String timeRemaining); public void setSeekFlashText(final String seekFlash); } public interface VideoFragmentListener { public void onFullScreen(boolean fullscreen); public void playerInitialized(); } }