package org.edx.mobile.player; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout.LayoutParams; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupWindow; import android.widget.PopupWindow.OnDismissListener; import android.widget.RelativeLayout; import android.widget.TextView; import com.facebook.widget.FacebookDialog; import com.google.inject.Inject; import org.edx.mobile.R; import org.edx.mobile.base.BaseFragment; import org.edx.mobile.core.IEdxEnvironment; import org.edx.mobile.interfaces.NetworkObserver; import org.edx.mobile.logger.Logger; import org.edx.mobile.model.VideoModel; import org.edx.mobile.model.api.TranscriptModel; import org.edx.mobile.model.db.DownloadEntry; import org.edx.mobile.module.facebook.IUiLifecycleHelper; import org.edx.mobile.module.prefs.LoginPrefs; import org.edx.mobile.util.AppConstants; import org.edx.mobile.util.BrowserUtil; import org.edx.mobile.util.DeviceSettingUtil; import org.edx.mobile.util.ListUtil; import org.edx.mobile.util.NetworkUtil; import org.edx.mobile.util.OrientationDetector; import org.edx.mobile.util.UiUtil; import org.edx.mobile.view.adapters.ClosedCaptionAdapter; import org.edx.mobile.view.dialog.CCLanguageDialogFragment; import org.edx.mobile.view.dialog.IListDialogCallback; import java.io.InputStream; import java.io.Serializable; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashMap; import subtitleFile.Caption; import subtitleFile.FormatSRT; import subtitleFile.TimedTextObject; @SuppressLint("WrongViewCast") @SuppressWarnings("serial") public class PlayerFragment extends BaseFragment implements IPlayerListener, Serializable, AudioManager.OnAudioFocusChangeListener, NetworkObserver { private enum VideoNotPlayMessageType { IS_CLEAR, IS_VIDEO_MESSAGE_DISPLAYED, IS_VIDEO_ONLY_ON_WEB, IS_NETWORK_MESSAGE_DISPLAYED, IS_SHOWN_WIFI_SETTINGS_MESSAGE } private static final String KEY_PLAYER = "player"; private static final String KEY_VIDEO = "video"; private static final String KEY_PREPARED = "isPrepared"; private static final String KEY_AUTOPLAY_DONE = "isAutoPlayDone"; private static final String KEY_MESSAGE_DISPLAYED = "isMessageDisplayed"; private static final String KEY_TRANSCRIPT = "transcript"; private static final int MSG_TYPE_TICK = 2014; private static final int DELAY_TIME_MS = 1000; private static final int UNFREEZE_DELAY_MS = 300; @Inject IEdxEnvironment environment; protected IPlayer player; private boolean isPrepared = false; private boolean isAutoPlayDone = false; private boolean stateSaved = false; private boolean orientationLocked = false; private transient OrientationDetector orientationDetector; private transient IPlayerEventCallback callback; private View.OnClickListener nextListner; private View.OnClickListener prevListner; private AudioManager audioManager; private boolean playOnFocusGain = false; private Handler subtitleDisplayHandler = new Handler(); private Handler subtitleFetchHandler = new Handler(); private CCLanguageDialogFragment ccFragment; private PopupWindow settingPopup; private PopupWindow cc_popup; private LinkedHashMap<String, TimedTextObject> srtList; private LinkedHashMap<String, String> langList; private TimedTextObject srt; private LayoutInflater layoutInflater; @Inject private TranscriptManager transcriptManager; private TranscriptModel transcript; private DownloadEntry videoEntry; private Object touchExplorationStateChangeListener; private EnumSet<VideoNotPlayMessageType> curMessageTypes = EnumSet.noneOf(VideoNotPlayMessageType.class); private boolean isManualFullscreen = false; private int currentPosition = 0; private final Logger logger = new Logger(getClass().getName()); private IUiLifecycleHelper uiHelper; private boolean pauseDueToDialog; private final transient Handler handler = new Handler() { private int lastSavedPosition; @Override public void handleMessage(android.os.Message msg) { if (msg.what == MSG_TYPE_TICK) { if (callback != null) { if(player!=null && player.isPlaying()) { // mark last current position int pos = player.getCurrentPosition(); if (pos > 0 && pos != lastSavedPosition) { lastSavedPosition = pos; callback.saveCurrentPlaybackPosition(pos); logger.debug("Current position saved: " + pos); } } } // repeat this message after every second sendEmptyMessageDelayed(MSG_TYPE_TICK, DELAY_TIME_MS); } } }; public PlayerFragment() { curMessageTypes.clear(); } public void setCallback(IPlayerEventCallback callback) { this.callback = callback; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.panel_player, null); this.layoutInflater = inflater; uiHelper = IUiLifecycleHelper.Factory.getInstance(getActivity(), null); uiHelper.onCreate(savedInstanceState); return view; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); uiHelper.onActivityResult(requestCode, resultCode, data, new FacebookDialog.Callback() { @Override public void onError(FacebookDialog.PendingCall pendingCall, Exception error, Bundle data) { } @Override public void onComplete(FacebookDialog.PendingCall pendingCall, Bundle data) { } }); } /** * Restores the saved instance of the player. */ private void restore(Bundle savedInstanceState) { if (savedInstanceState != null) { player = (IPlayer) savedInstanceState.get(KEY_PLAYER); videoEntry = (DownloadEntry) savedInstanceState.get(KEY_VIDEO); isPrepared = savedInstanceState.getBoolean(KEY_PREPARED); isAutoPlayDone = savedInstanceState.getBoolean(KEY_AUTOPLAY_DONE); transcript = (TranscriptModel) savedInstanceState.get(KEY_TRANSCRIPT); if (savedInstanceState.getBoolean(KEY_MESSAGE_DISPLAYED)) { showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); } } else { if (player == null) player = new Player(); } } private void reAttachPlayEventListener() { // set the fullscreen flag to correct value if (player != null) { boolean isLandscape = isScreenLandscape(); player.setFullScreen(isLandscape); player.setPlayerListener(this); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); try { restore(savedInstanceState); audioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); orientationDetector = new OrientationDetector(getActivity()) { private boolean isLastRotationOn = false; @Override protected void onChanged() { if (isResumed()) { allowSensorOrientationIfApplicable(); } } @Override protected void onUpdate() { super.onUpdate(); boolean isRotationOn = DeviceSettingUtil.isDeviceRotationON(getActivity()); if ( !isRotationOn && isLastRotationOn) { // rotation just got turned OFF, so exit fullscreen exitFullScreen(); } isLastRotationOn = isRotationOn; } }; getView().findViewById(R.id.panel_video_only_on_web).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final StringBuffer urlStringBuffer = new StringBuffer(); if (! videoEntry.url.startsWith("http://") && ! videoEntry.url.startsWith("https://")) { urlStringBuffer.append("http://"); urlStringBuffer.append( videoEntry.url); } else { urlStringBuffer.append( videoEntry.url); } BrowserUtil.open(getActivity(), urlStringBuffer.toString()); } }); } catch(Exception ex) { logger.error(ex); } } private void allowSensorOrientationIfApplicable() { try{ boolean isRotationOn = DeviceSettingUtil.isDeviceRotationON(getActivity()); if (isRotationOn) { // do UI operations only if the fragment is resumed if (orientationDetector.isLandscape()) { if (isScreenLandscape()) { logger.debug("Allowing sensor from landscape rotation"); isManualFullscreen = false; allowSensorOrientation(); } } else if (orientationDetector.isPortrait()) { if ( !isScreenLandscape()) { logger.debug("Allowing sensor from portrait rotation"); isManualFullscreen = false; allowSensorOrientation(); } } } else { logger.debug("Locking to portrait as Device Screen Rotation is OFF"); // lock to portrait if ( !isManualFullscreen) { exitFullScreen(); } else { logger.debug("You are in manual fullscreen mode"); } } } catch(Exception e) { logger.error(e); } } @Override public void onStart() { super.onStart(); logger.debug("Player fragment start"); stateSaved = false; try{ Preview preview = (Preview) getView().findViewById(R.id.preview); if(player!=null){ player.setPreview(preview); // setup the flat if player is fullscreen player.setFullScreen(isScreenLandscape()); } if(curMessageTypes.contains(VideoNotPlayMessageType.IS_VIDEO_ONLY_ON_WEB)) { showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_ONLY_ON_WEB); } if(curMessageTypes.contains(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED)){ showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); }else if(curMessageTypes.contains(VideoNotPlayMessageType.IS_NETWORK_MESSAGE_DISPLAYED)){ showNetworkError(); } else if(curMessageTypes.contains(VideoNotPlayMessageType.IS_SHOWN_WIFI_SETTINGS_MESSAGE)){ showWifiSettingsMessage(); } }catch(Exception e){ logger.error(e); } } @Override public void onResume() { super.onResume(); if (getUserVisibleHint()) { handleOnResume(); } } @Override public void onPause() { super.onPause(); if (getUserVisibleHint()) { handleOnPause(); } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isResumed()) { if (isVisibleToUser) { handleOnResume(); } else { handleOnPause(); } } } public void handleOnResume() { uiHelper.onResume(); setupController(); if (curMessageTypes.isEmpty()) { // display progress until playback actually starts showProgress(); } configureAutoHideControls(); // start playback after 300 milli seconds, so that it works on HTC One, Nexus5, S4, S5 // some devices take little time to be ready if (isPrepared) { handler.postDelayed(unfreezeCallback, UNFREEZE_DELAY_MS); } } @TargetApi(Build.VERSION_CODES.KITKAT) private void configureAutoHideControls() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return; } player.setAutoHideControls(!getTouchExploreEnabled()); setTouchExploreChangeListener(new AccessibilityManager.TouchExplorationStateChangeListener() { @Override public void onTouchExplorationStateChanged(boolean enabled) { player.setAutoHideControls(!enabled); } }); } public void handleOnPause(){ uiHelper.onPause(); try{ orientationDetector.stop(); handler.removeCallbacks(unfreezeCallback); handler.removeCallbacks(requestAccessibilityFocusCallback); freezePlayer(); setTouchExploreChangeListener(null); }catch(Exception e){ logger.error(e); } } @Override public void onStop() { super.onStop(); setTouchExploreChangeListener(null); if(audioManager!=null) { audioManager.abandonAudioFocus(this); } if(player!=null){ handler.removeMessages(MSG_TYPE_TICK); freezePlayer(); } } @Override public void onDestroy() { super.onDestroy(); uiHelper.onDestroy(); if (!stateSaved) { if (player!=null) { // reset player when user goes back, and there is no state saving happened player.reset(); removeSubtitleCallBack(); // release the player instance player.release(); player = null; logger.debug("player detached, reset and released"); } } } private void showProgress() { try { if(player!=null){ player.hideController(); } if( this.curMessageTypes.isEmpty() ){ getView().findViewById(R.id.loading_indicator).setVisibility(View.VISIBLE); } } catch(Exception ex) { logger.error(ex); } } private void hideProgress() { try { getView().findViewById(R.id.loading_indicator).setVisibility(View.GONE); } catch(Exception ex) { logger.error(ex); } } @Override public void onSaveInstanceState(Bundle outState) { logger.debug("Saving state ..."); stateSaved = true; if (player != null) { freezePlayer(); outState.putSerializable(KEY_PLAYER, player); } outState.putSerializable(KEY_VIDEO, videoEntry); outState.putBoolean(KEY_PREPARED, isPrepared); outState.putBoolean(KEY_AUTOPLAY_DONE, isAutoPlayDone); //FIXME: ensure that prepare is called on all activity restarts and then this can be removed outState.putSerializable(KEY_TRANSCRIPT, transcript); super.onSaveInstanceState(outState); uiHelper.onSaveInstanceState(outState); } public synchronized void prepare(String path, int seekTo, String title, TranscriptModel trModel, DownloadEntry video) { playOrPrepare(path, seekTo, title, trModel, video, true); } public synchronized void play(String path, int seekTo, String title, TranscriptModel trModel, DownloadEntry video) { playOrPrepare(path, seekTo, title, trModel, video, false); } /** * Starts playing given path. Path can be file path or http/https URL. * * @param path * @param seekTo * @param title * @param prepareOnly <code>true</code> player will be prepared but not start to play */ public synchronized void playOrPrepare(String path, int seekTo, String title, TranscriptModel trModel, DownloadEntry video, boolean prepareOnly) { isPrepared = false; // block to portrait while preparing if ( !isScreenLandscape()) { exitFullScreen(); } // reset the player, so that pending play requests will be cancelled try { player.reset(); } catch (Exception e) { logger.error(e); } if(video!=null){ this.videoEntry = video; } if (trModel != null) { this.transcript = trModel; transcriptManager.downloadTranscriptsForVideo(trModel); //initializeClosedCaptioning(); } // request focus on audio channel, as we are starting playback requestAudioFocus(); try { if ( video.isVideoForWebOnly ){ showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_ONLY_ON_WEB); path = ""; } else { // show loading indicator as player will prepare now showProgress(); if (path == null || path.trim().length() == 0) { showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); //return; } else { hideVideoNotPlayInfo(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); } } this.transcript = trModel; player.setLMSUrl(video.lmsUrl); player.setVideoTitle(title); logger.debug("playing [seek=" + seekTo + "]: " + path); if( prepareOnly) player.setUri(path, seekTo); else player.setUriAndPlay(path, seekTo); } catch (Exception e) { logger.error(e); } } private void setupController() { if (null == player) { return; } try { View f = getView(); if (f == null) { return; } final ViewGroup container = (ViewGroup) f .findViewById(R.id.preview_container); final PlayerController controller = new PlayerController( getActivity()); controller.setAnchorView(container); // changed to true after Lou's comments to hide the controllers controller.setAutoHide(true); controller.setNextPreviousListeners(nextListner, prevListner); player.setController(controller); reAttachPlayEventListener(); } catch(Exception e) { logger.error(e); } } public void setNextPreviousListeners(View.OnClickListener next, View.OnClickListener prev) { this.prevListner = prev; this.nextListner = next; updateNextPreviousListeners(); } private void updateNextPreviousListeners() { if (player != null) { if (isScreenLandscape()) { player.setNextPreviousListeners(nextListner, prevListner); } else { player.setNextPreviousListeners(null, null); } } } @Override public void onError() { // display error panel showNetworkError(); if (callback != null) { callback.onError(); } setScreenOnWhilePlaying(false); } @Override public void onVideoLagging() { if ( !NetworkUtil.isConnected(getActivity())) { // no network and video lagging, might be network problem showNetworkError(); } } @Override public void onVideoNotSeekable() { } @Override public void onPreparing() { setScreenOnWhilePlaying(true); hideNetworkError(); showProgress(); } @Override public void onPlaybackPaused() { setScreenOnWhilePlaying(false); try{ if(player!=null){ double current_time = player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND ; environment.getSegment().trackVideoPause(videoEntry.videoId, current_time, videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } hideCCPopUp(); hideSettingsPopUp(); } private void hideNetworkError() { try { unlockOrientation(); View errorView = getView().findViewById(R.id.panel_network_error); errorView.setVisibility(View.GONE); curMessageTypes.remove(VideoNotPlayMessageType.IS_SHOWN_WIFI_SETTINGS_MESSAGE); curMessageTypes.remove(VideoNotPlayMessageType.IS_NETWORK_MESSAGE_DISPLAYED); } catch(Exception ex) { logger.error(ex); } } private void showNetworkError() { try { if(player!=null){ if (player.isPlayingLocally() || player.isPlaying() ) { hideNetworkError(); } else { if(!curMessageTypes.contains(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED)){ //This has been commented after Lou's suggestion unlockOrientation(); //lockOrientation(); hideCCPopUp(); hideSettingsPopUp(); player.hideController(); clearAllErrors(); // if network is available , this must be video-corrupt-error if (NetworkUtil.isConnected(getActivity())) { // video might be corrupt showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); } else { View errorView = getView().findViewById(R.id.panel_network_error); errorView.setVisibility(View.VISIBLE); } curMessageTypes.add(VideoNotPlayMessageType.IS_NETWORK_MESSAGE_DISPLAYED); resetClosedCaptioning(); } } }else{ if (NetworkUtil.isConnected(getActivity())) { // video might be corrupt showVideoNotAvailable(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); } else { View errorView = getView().findViewById(R.id.panel_network_error); errorView.setVisibility(View.VISIBLE); } } } catch(Exception ex) { logger.error(ex); } } private void showVideoNotAvailable( VideoNotPlayMessageType reason ){ try { if(player!=null){ hideCCPopUp(); hideSettingsPopUp(); player.hideController(); // player got error, // mark player as prepared, because it is not in preparing state anymore isPrepared = true; //This has been put after Lou's Suggestion unlockOrientation(); //lockOrientation(); hideProgress(); View errorView; if ( reason == VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED) errorView = getView().findViewById(R.id.panel_video_not_available); else errorView = getView().findViewById(R.id.panel_video_only_on_web); errorView.setVisibility(View.VISIBLE); curMessageTypes.add(reason); hideClosedCaptioning(); } } catch(Exception ex) { logger.error(ex); } } private void hideVideoNotPlayInfo(VideoNotPlayMessageType reason) { try { View errorView; if ( reason == VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED) errorView = getView().findViewById(R.id.panel_video_not_available); else errorView = getView().findViewById(R.id.panel_video_only_on_web); errorView.setVisibility(View.GONE); curMessageTypes.remove(reason); } catch(Exception ex) { logger.error(ex); } } private void clearAllErrors() { hideNetworkError(); hideVideoNotPlayInfo(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED); hideVideoNotPlayInfo(VideoNotPlayMessageType.IS_VIDEO_ONLY_ON_WEB); hideProgress(); } @Override public void onPrepared() { // mark prepared and allow orientation isPrepared = true; if (getActivity() == null) { return; } allowSensorOrientation(); if (!isResumed() || !getUserVisibleHint()) { freezePlayer(); return; } // clear errors clearAllErrors(); initializeClosedCaptioning(); handler.postDelayed(unfreezeCallback, UNFREEZE_DELAY_MS); environment.getSegment().trackVideoLoading(videoEntry.videoId, videoEntry.eid, videoEntry.lmsUrl); } @Override public void onPlaybackStarted() { // mark prepared as playback has started isPrepared = true; // request audio focus, as playback has started requestAudioFocus(); // keep screen ON setScreenOnWhilePlaying(true); if (callback != null) { callback.onPlaybackStarted(); updateController("playback started"); } clearAllErrors(); if(langList!=null){ displaySrtData(); }else{ initializeClosedCaptioning(); } try{ if(player!=null){ double current_time = player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND ; environment.getSegment().trackVideoPlaying(videoEntry.videoId, current_time , videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } } private void setScreenOnWhilePlaying(boolean screenOn) { try { if (screenOn) { getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); logger.debug("KEEP SCREEN ON is set while playing, flag added"); } else { getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); logger.debug("KEEP SCREEN ON is unset, flag removed"); } } catch(Exception ex) { logger.error(ex, true); } } @Override public void onPlaybackComplete() { try{ if(player!=null){ double current_time = player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND ; environment.getSegment().trackVideoStop(videoEntry.videoId, current_time, videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } if (callback != null) { // mark offset as zero, so that playback will resume from start next time callback.saveCurrentPlaybackPosition(0); callback.onPlaybackComplete(); } hideCCPopUp(); hideSettingsPopUp(); try{ if(player!=null){ if(player.getController()!=null){ player.getController().showSpecial( (getTouchExploreEnabled() ? 0L : 5000L) ); } } }catch(Exception e){ logger.error(e); } } @Override public void callLMSServer(String url) { try{ if(url!=null){ BrowserUtil.open(getActivity(), url); } }catch(Exception e){ logger.error(e); } } @Override public void onFullScreen(boolean isFullScreen) { if (isPrepared) { isManualFullscreen = isFullScreen; if (isFullScreen) { enterFullScreen(); } else { exitFullScreen(); } } else { logger.debug("Player not prepared ?? full screen will NOT work!"); } } private void enterFullScreen() { try { if (getActivity() != null) { getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } if (isPrepared) { if (environment.getSegment() == null) { logger.warn("segment is NOT initialized, cannot capture event enterFullScreen"); return; } if (player == null) { logger.warn("player instance is null, cannot capture event enterFullScreen"); return; } if (videoEntry == null) { logger.warn("video model instance is null, cannot capture event enterFullScreen"); return; } environment.getSegment().trackVideoOrientation(videoEntry.videoId, player.getCurrentPosition() / AppConstants.MILLISECONDS_PER_SECOND, true, videoEntry.eid, videoEntry.lmsUrl); } } catch(Exception ex) { logger.error(ex); } } private void exitFullScreen() { try { if (getActivity() != null) { getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } if (isPrepared) { if (environment.getSegment() == null) { logger.warn("segment is NOT initialized, cannot capture event exitFullScreen"); return; } if (player == null) { logger.warn("player instance is null, cannot capture event exitFullScreen"); return; } if (videoEntry == null) { logger.warn("video model instance is null, cannot capture event exitFullScreen"); return; } environment.getSegment().trackVideoOrientation(videoEntry.videoId, player.getCurrentPosition() / AppConstants.MILLISECONDS_PER_SECOND, false, videoEntry.eid, videoEntry.lmsUrl); } } catch(Exception ex) { logger.error(ex); } } private void allowSensorOrientation() { if (isPrepared && !orientationLocked) { getActivity().setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_SENSOR); } } private boolean isScreenLandscape() { try { int orientation = getResources().getConfiguration().orientation; logger.debug("Current orientation = " + orientation); return (orientation == Configuration.ORIENTATION_LANDSCAPE); } catch(Exception ex) { logger.error(ex); } return false; } private Runnable unfreezeCallback = new Runnable() { @Override public void run() { if (isResumed() && !isRemoving()) { if (player != null) { player.unfreeze(); hideProgress(); if (player.isPlaying() || getTouchExploreEnabled()) { updateController("player unfreezed"); } if (isPrepared && !isAutoPlayDone) { isAutoPlayDone = true; player.start(); } if (pauseDueToDialog) { pauseDueToDialog = false; player.pause(); } } orientationDetector.start(); handler.sendEmptyMessage(MSG_TYPE_TICK); } // as before, request accessibility focus after 300 milli seconds, so that it works on HTC One, Nexus5, S4, S5 // some devices take little time to be ready handler.postDelayed(requestAccessibilityFocusCallback, UNFREEZE_DELAY_MS); } }; private Runnable requestAccessibilityFocusCallback = new Runnable() { @Override public void run() { if (player != null) { player.requestAccessibilityFocusPausePlay(); } } }; @Override public void onOnline() { //Nothing to do } @Override public void onOffline() { // nothing to do showNetworkError(); } public void onConnectedToMobile(){ boolean wifiPreference = environment.getUserPrefs().isDownloadOverWifiOnly(); if(!NetworkUtil.isOnZeroRatedNetwork(getActivity(), environment.getConfig()) && wifiPreference){ //If the user is connected to a non zero rated mobile data network and his wifi preference is on, //then prompt user to set change his wifi settings showWifiSettingsMessage(); }else{ handleNetworkChangeVideoPlayback(); } } public void onConnectedToWifi(){ //Start playing video is user is connected to wifi handleNetworkChangeVideoPlayback(); } /** * This method handles video playback on network change callbacks */ private void handleNetworkChangeVideoPlayback(){ hideNetworkError(); try { if(player!=null){ if(!curMessageTypes.contains(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED)){ if((!player.isPaused() && !player.isPlaying() && !player.isPlayingLocally()) || (player.isInError() || player.isReset())){ showProgress(); } } if (player.isInError() || player.isReset()) { //If player is either in error state or has been reset, restart the player with current position player.restart(currentPosition); } } } catch (Exception e) { logger.error(e); } } /** * LectureComplete dialog was creating problems if orientation is allowed when dialog is shown. * So, locked orientation while the LectureComplete dialog is showing. */ public void lockOrientation() { orientationLocked = true; if (isScreenLandscape()) { enterFullScreen(); } else { exitFullScreen(); } } public void unlockOrientation() { orientationLocked = false; allowSensorOrientationIfApplicable(); } @Override public void onAudioFocusChange(int focusChange) { try { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: // resume playback if (playOnFocusGain) { // before we start playing, request focus on audio channel requestAudioFocus(); if(player!=null){ player.start(); updateController("audio focus gained"); } } playOnFocusGain = false; break; case AudioManager.AUDIOFOCUS_LOSS: if(player!=null){ // resume playback if (player.isPlaying()) { player.pause(); updateController("audio focus lost"); playOnFocusGain = true; } else { playOnFocusGain = false; } } break; } } catch(Exception ex) { logger.error(ex); } } private void requestAudioFocus() { if(audioManager!=null) { audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } } /** * This runnable handles the displaying of * Subtitles on the screen per 100 mili seconds */ private Runnable subtitleProcessesor = new Runnable() { @Override public void run() { try{ //This has been reset so that previous cc will not be displayed resetClosedCaptioning(); if (player != null && (player.isPlaying() || player.isPaused())) { int currentPos = player.getCurrentPosition(); if(srt!=null){ Collection<Caption> subtitles = srt.captions.values(); for (Caption caption : subtitles) { int startMillis = caption.start.getMseconds(); int endMillis = caption.end.getMseconds(); if (currentPos >= startMillis && currentPos <= endMillis) { setClosedCaptionData(caption); break; } else if (currentPos > endMillis) { setClosedCaptionData(null); } } }else{ setClosedCaptionData(null); } } subtitleDisplayHandler.postDelayed(this, 100); }catch(Exception e){ logger.error(e); } } }; /** * This runnable is used the fetch the Subtitle in TimedTextObject */ private Runnable subtitleFetchProcessesor = new Runnable() { public void run() { if(srtList!=null){ srtList = null; } srtList = new LinkedHashMap<>(); try { LinkedHashMap<String, InputStream> localHashMap = transcriptManager .fetchTranscriptsForVideo(transcript,getActivity()); if (localHashMap != null){ for(String thisKey : localHashMap.keySet()){ InputStream localInputStream = localHashMap.get(thisKey); if (localInputStream != null) { TimedTextObject localTimedTextObject = new FormatSRT().parseFile("temp.srt", localInputStream); srtList.put(thisKey, localTimedTextObject); localInputStream.close(); } } if ((srtList == null) || (srtList.size() == 0)) { subtitleFetchHandler.postDelayed(subtitleFetchProcessesor, 100); }else{ displaySrtData(); } }else{ subtitleFetchHandler.postDelayed(subtitleFetchProcessesor, DELAY_TIME_MS); } }catch (Exception localException) { logger.error(localException); } } }; /** * Handler initialized for fetching Subtitles */ private void fetchSubtitlesTask(){ try { if (this.subtitleFetchHandler != null) { subtitleFetchHandler.removeCallbacks(this.subtitleFetchProcessesor); subtitleFetchHandler = null; } LinkedHashMap<String, String> languageList = getLanguageList(); if(languageList!=null && languageList.size()>0){ subtitleFetchHandler = new Handler(); if (subtitleFetchProcessesor != null) subtitleFetchHandler.post(this.subtitleFetchProcessesor); } } catch (Exception localException) { logger.error(localException); } } /** * This function sets the closed caption data on the TextView */ private void setClosedCaptionData(Caption text){ try{ RelativeLayout subTitlesLayout = (RelativeLayout) getActivity().findViewById(R.id.txtSubtitles); TextView subTitlesTv = (TextView) getActivity().findViewById(R.id.txtSubtitles_tv); if(subTitlesTv!=null ){ if(text!=null){ int margin_twenty_dp = (int) UiUtil.getParamsInDP(getResources(),20); int margin_ten_dp = (int) UiUtil.getParamsInDP(getResources(),10); if(player!=null){ LayoutParams lp = (LayoutParams) subTitlesLayout.getLayoutParams(); if (player.getController()!=null && player.getController().isShown()){ if(player.isFullScreen()){ lp.setMargins(margin_twenty_dp, 0, margin_twenty_dp, (int)UiUtil.getParamsInDP(getResources(),50)); }else{ lp.setMargins(margin_twenty_dp, 0, margin_twenty_dp,(int)UiUtil.getParamsInDP(getResources(),42)); } subTitlesLayout.setLayoutParams(lp); }else{ if(player.isFullScreen()){ lp.setMargins(margin_twenty_dp, 0, margin_twenty_dp, margin_ten_dp); }else{ lp.setMargins(margin_twenty_dp, 0, margin_twenty_dp,(int)UiUtil.getParamsInDP(getResources(),5)); } subTitlesLayout.setLayoutParams(lp); } } subTitlesTv.setPadding(margin_ten_dp, (int)UiUtil.getParamsInDP(getResources(),2), margin_ten_dp,(int)UiUtil.getParamsInDP(getResources(),2) ); subTitlesTv.setText(""); //This has been done because text.content contains <br /> //in the end of each message String temp = text.content; if(temp.endsWith("<br />")){ temp = temp.substring(0, temp.length()-6); } if(temp.length()==0){ subTitlesTv.setVisibility(View.GONE); }else{ subTitlesTv.setText(temp); subTitlesTv.setVisibility(View.VISIBLE); } }else{ subTitlesTv.setVisibility(View.GONE); } } }catch(Exception e){ logger.error(e); } } /** * Hide the Closed Captioning TextView */ private void hideClosedCaptioning(){ try{ TextView subTitlesTv = (TextView) getActivity().findViewById(R.id.txtSubtitles_tv); if(subTitlesTv!=null){ subTitlesTv.setVisibility(View.GONE); } }catch(Exception e){ logger.error(e); } } /** * This resets the Closed Captioning to blank/empty */ private void resetClosedCaptioning(){ try{ TextView subTitlesTv = (TextView) getActivity().findViewById(R.id.txtSubtitles_tv); if(subTitlesTv!=null){ subTitlesTv.setText(""); subTitlesTv.setVisibility(View.INVISIBLE); } }catch(Exception e){ logger.error(e); } } /** * Initialiaze and reset Closed Captioning handlers */ private void initializeClosedCaptioning(){ try{ removeSubtitleCallBack(); hideClosedCaptioning(); fetchSubtitlesTask(); }catch(Exception e){ logger.error(e); } } /** * This removes the callbacks and resets the handlers */ private void removeSubtitleCallBack() { if (subtitleDisplayHandler != null) { subtitleDisplayHandler.removeCallbacks(subtitleProcessesor); subtitleDisplayHandler = null; hideClosedCaptioning(); srt = null; srtList = null; } if (subtitleFetchHandler != null) { subtitleFetchHandler.removeCallbacks(subtitleFetchProcessesor); subtitleFetchHandler = null; } } /** * This removes the subtitle display callback */ private void removeSubtitleDisplayCallBack() { if (subtitleDisplayHandler != null) { subtitleDisplayHandler.removeCallbacks(subtitleProcessesor); subtitleDisplayHandler = null; hideClosedCaptioning(); } } @Override public void callSettings(Point p) { try{ ImageView iv = (ImageView) getActivity().findViewById(R.id.iv_transparent_bg); iv.setVisibility(View.VISIBLE); }catch(Exception e){ logger.error(e); } showSettingsPopup(p); } public void hideTransparentImage() { try{ ImageView iv = (ImageView) getActivity().findViewById(R.id.iv_transparent_bg); iv.setVisibility(View.GONE); }catch(Exception e){ logger.error(e); } } @Nullable private AccessibilityManager getAccessibilityManager() { return (AccessibilityManager)getActivity().getSystemService(Context.ACCESSIBILITY_SERVICE); } /** * @return True if talkback mode is on */ private boolean getTouchExploreEnabled() { boolean ret = false; if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { AccessibilityManager am = getAccessibilityManager(); if (am != null && am.isTouchExplorationEnabled()) { ret = true; } } return ret; } /** * Sets the current touch explore state change listener and removes the previous one if necessary * @param listener Null value unregisters the current listener, non-null unregisters previous one and registers new one * If the current listener is the same as the previous one, no operation is performed. */ @TargetApi(Build.VERSION_CODES.KITKAT) protected void setTouchExploreChangeListener(@Nullable AccessibilityManager.TouchExplorationStateChangeListener listener) { // if current touchExplorerStateChangeListener is identical to previous one, no operation is necessary if (listener != touchExplorationStateChangeListener) { AccessibilityManager am = getAccessibilityManager(); if (am != null) { /* touch explorer state listeners are additive (i.e. adding one doesn't remove the previous one), so we need to be careful * and only register one at a time. so, if the one we currently have is valid (non-null), unregister it. * If the new one is valid (non-null) register it. */ if (touchExplorationStateChangeListener != null) { am.removeTouchExplorationStateChangeListener((AccessibilityManager.TouchExplorationStateChangeListener) touchExplorationStateChangeListener); } if (listener != null) { am.addTouchExplorationStateChangeListener(listener); } touchExplorationStateChangeListener = listener; } } } //The method that displays the popup. private void showSettingsPopup(final Point p) { try{ if(player!=null){ player.getController().setAutoHide(!getTouchExploreEnabled()); Activity context = getActivity(); Resources r = getResources(); float popupHeight = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 40 , r.getDisplayMetrics()); float popupWidth = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 150 , r.getDisplayMetrics()); // Inflate the popup_layout.xml LinearLayout viewGroup = (LinearLayout) context.findViewById(R.id.setting_popup); LayoutInflater layoutInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = layoutInflater.inflate(R.layout.panel_settings_popup, viewGroup); // Creating the PopupWindow settingPopup = new PopupWindow(context); settingPopup.setContentView(layout); settingPopup.setWidth((int)popupWidth); settingPopup.setHeight((int)popupHeight); settingPopup.setFocusable(true); settingPopup.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss() { hideTransparentImage(); if(player!=null){ player.getController().setSettingsBtnDrawable(false); player.getController().setAutoHide(!getTouchExploreEnabled()); } } }); // Clear the default translucent background settingPopup.setBackgroundDrawable(new BitmapDrawable()); // Displaying the popup at the specified location, + offsets. settingPopup.showAtLocation(layout, Gravity.NO_GRAVITY, p.x-(int)popupWidth, p.y-(int)popupHeight); TextView tv_closedCaption = (TextView) layout.findViewById(R.id.tv_closedcaption); if ((langList != null) && (langList.size() > 0)) { tv_closedCaption.setBackgroundResource(R.drawable.white_rounded_selector); tv_closedCaption.setOnClickListener(new View.OnClickListener(){ public void onClick(View paramAnonymousView) { if(player.isFullScreen()) { showClosedCaptionLandscapePopup(p); }else{ showCCFragmentPopup(); } } }); }else{ tv_closedCaption.setBackgroundResource(R.drawable.grey_roundedbg); tv_closedCaption.setOnClickListener(null); } } }catch(Exception e){ logger.error(e); } } /** * This function is used to show popup in landscape mode and * the Point defines the current position of Settings button * @param p {@link android.graphics.Point} */ private void showClosedCaptionLandscapePopup(Point p){ try{ LinkedHashMap<String, String> languageList = getLanguageList(); float popupHeight = UiUtil.getParamsInDP(getResources(),220); float popupWidth = UiUtil.getParamsInDP(getResources(),250); // Inflate the popup_layout.xml LinearLayout viewGroup = (LinearLayout) getActivity() .findViewById(R.id.cc_layout_popup); View layout = layoutInflater.inflate(R.layout.panel_cc_popup, viewGroup); // Creating the PopupWindow for CC cc_popup = new PopupWindow(getActivity()); cc_popup.setContentView(layout); cc_popup.setWidth((int)popupWidth); cc_popup.setHeight((int)popupHeight); cc_popup.setFocusable(true); cc_popup.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss() { hideSettingsPopUp(); } }); ListView lv_ccLang = (ListView) layout.findViewById(R.id.cc_list); ClosedCaptionAdapter ccAdaptor = new ClosedCaptionAdapter(getActivity(), environment) { @Override public void onItemClicked(HashMap<String, String> lang) { try{ final String languageSubtitle = lang.keySet().toArray()[0].toString(); setSubtitleLanguage(languageSubtitle); try{ if(player!=null){ environment.getSegment().trackTranscriptLanguage(videoEntry.videoId, player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND, languageSubtitle , videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } displaySrtData(); cc_popup.dismiss(); if(player!=null){ player.getController().setSettingsBtnDrawable(false); player.getController().setAutoHide(true); } }catch(Exception e){ logger.error(e); } } }; lv_ccLang.setAdapter(ccAdaptor); lv_ccLang.setOnItemClickListener(ccAdaptor); if(languageList!=null && languageList.size()>0){ HashMap<String, String> lang; for(int i=0; i<languageList.size();i++){ lang = new HashMap<>(); lang.put(languageList.keySet().toArray()[i].toString(), languageList.values().toArray()[i].toString()); ccAdaptor.add(lang); } } final String languageSubtitle = getSubtitleLanguage(); ccAdaptor.selectedLanguage = languageSubtitle; ccAdaptor.notifyDataSetChanged(); // for less number of list rows, update height to fit contents // also add height NONE option and TITLE of the popup int fullHeightInDp = ListUtil.getFullHeightofListView(lv_ccLang) + (ListUtil.getSingleRowHeight(lv_ccLang) * 2) + (lv_ccLang.getDividerHeight() * 4); if (fullHeightInDp < popupHeight) { popupHeight = fullHeightInDp; cc_popup.setHeight(fullHeightInDp); } // Clear the default translucent background cc_popup.setBackgroundDrawable(new BitmapDrawable()); // Displaying the popup at the specified location, + offsets. cc_popup.showAtLocation(layout, Gravity.NO_GRAVITY, p.x + 10 -(int)popupWidth, p.y + 10 - (int)popupHeight); TextView tv_none = (TextView) layout.findViewById(R.id.tv_cc_cancel); if (languageSubtitle != null) { tv_none.setBackgroundResource(R.drawable.white_bottom_rounded_selector); } else { tv_none.setBackgroundResource(R.color.cyan_text_navigation_20); } tv_none.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try{ removeSubtitleDisplayCallBack(); hideCCPopUp(); setSubtitleLanguage(null); try{ if(player!=null){ environment.getSegment().trackHideTranscript(videoEntry.videoId, player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND, videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } if(player!=null){ player.getController().setSettingsBtnDrawable(false); player.getController().setAutoHide(true); } }catch(Exception e){ logger.error(e); } } }); }catch(Exception e){ logger.error(e); } } //Default Language List private LinkedHashMap<String, String> getLanguageList(){ if(transcript!=null){ langList = transcript.getLanguageList(getActivity()); return langList; } return null; } // /** *This function is used to show Dialog fragment of *language list in potrait mode */ protected void showCCFragmentPopup() { try{ hideSettingsPopUp(); ccFragment = CCLanguageDialogFragment.getInstance(getLanguageList(),new IListDialogCallback() { @Override public void onItemClicked(HashMap<String, String> lang) { try{ final String languageSubtitle = lang.keySet().toArray()[0].toString(); try{ setSubtitleLanguage(languageSubtitle); if(player!=null){ environment.getSegment().trackShowTranscript(videoEntry.videoId, player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND, videoEntry.eid, videoEntry.lmsUrl); environment.getSegment().trackTranscriptLanguage(videoEntry.videoId, player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND, languageSubtitle , videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } displaySrtData(); if(player!=null){ player.getController().setSettingsBtnDrawable(false); player.getController().setAutoHide(true); } }catch(Exception e){ logger.error(e); } } @Override public void onCancelClicked() { try{ removeSubtitleDisplayCallBack(); try{ setSubtitleLanguage(null); if(player!=null){ environment.getSegment().trackHideTranscript(videoEntry.videoId, player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND, videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } if(player!=null){ player.getController().setAutoHide(true); player.getController().setSettingsBtnDrawable(false); } }catch(Exception e){ logger.error(e); } } }, getSubtitleLanguage()); ccFragment.setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Holo_Dialog); ccFragment.show(getFragmentManager(), "dialog"); ccFragment.setCancelable(true); }catch(Exception e){ logger.error(e); } } /** * This function hides the Settings popup and overlay */ private void hideSettingsPopUp(){ try{ hideTransparentImage(); if(settingPopup!=null){ settingPopup.dismiss(); } }catch(Exception e){ logger.error(e); } } /** * This function hides the CC popup and overlay */ private void hideCCPopUp(){ try{ if(cc_popup!=null){ cc_popup.dismiss(); } if(ccFragment!=null && ccFragment.isVisible()){ ccFragment.dismiss(); } }catch(Exception e){ logger.error(e); } } /** * This function is used to display CC data when a transcript is selected */ private void displaySrtData(){ try{ if (subtitleDisplayHandler != null) { subtitleDisplayHandler.removeCallbacks(subtitleProcessesor); } resetClosedCaptioning(); if(srtList!=null && srtList.size()>0){ final String languageSubtitle = getSubtitleLanguage(); if(languageSubtitle!=null){ srt = srtList.get(languageSubtitle); if (srt != null) { try{ if(player!=null){ environment.getSegment().trackShowTranscript(videoEntry.videoId, player.getCurrentPosition()/AppConstants.MILLISECONDS_PER_SECOND, videoEntry.eid, videoEntry.lmsUrl); } }catch(Exception e){ logger.error(e); } if(subtitleDisplayHandler==null){ subtitleDisplayHandler = new Handler(); } subtitleDisplayHandler.post(subtitleProcessesor); } } } }catch(Exception e){ logger.error(e); } } @Inject LoginPrefs loginPrefs; @Nullable private String getSubtitleLanguage() { return loginPrefs.getSubtitleLanguage(); } private void setSubtitleLanguage(@Nullable String language) { loginPrefs.setSubtitleLanguage(language); } @Override public void callPlayerSeeked(long lastPostion, long newPosition, boolean isRewindClicked) { try{ if (callback != null) { // mark last seeked position callback.saveCurrentPlaybackPosition((int) newPosition); logger.debug("Current position saved: " + newPosition); } if(isRewindClicked){ resetClosedCaptioning(); } environment.getSegment().trackVideoSeek(videoEntry.videoId, lastPostion/AppConstants.MILLISECONDS_PER_SECOND, newPosition/AppConstants.MILLISECONDS_PER_SECOND, videoEntry.eid, videoEntry.lmsUrl, isRewindClicked); }catch(Exception e){ logger.error(e); } } /** * Displays controller * * @param source The source which called this function */ private void updateController(String source) { logger.debug("Updating controller from : " + source); if (player != null) { // controller should also refresh, so hide and show it player.hideController(); player.showController(); updateNextPreviousListeners(); } } /** * @return the video model that this fragment is supposed to play. */ public VideoModel getPlayingVideo() { return videoEntry; } /** * @return true if playback is ongoing, false otherwise. */ public boolean isPlaying() { return (player != null && player.isPlaying()); } /** * Returns true if video player is in frozen state * * @return <code>true</code> if the video player is frozen */ @SuppressWarnings("unused") public boolean isFrozen() { return (player != null && player.isFrozen()); } public void freezePlayer() { setScreenOnWhilePlaying(false); if (player!=null) { if (callback != null && player.isPlaying()) { int pos = player.getCurrentPosition(); if (pos > 0) { callback.saveCurrentPlaybackPosition(pos); } } player.freeze(); } } /** * This method is called when we need to notify the user during playback that he has * switched to mobile network and his current download settings is not allowed to download videos */ private void showWifiSettingsMessage() { try { if (player != null) { if (player.isPlayingLocally()) { hideNetworkError(); } else { if(!curMessageTypes.contains(VideoNotPlayMessageType.IS_VIDEO_MESSAGE_DISPLAYED) && !player.isInError()){ unlockOrientation(); hideCCPopUp(); hideSettingsPopUp(); if ( !player.isReset()) { if ( !player.isInError()) { currentPosition = player.getCurrentPosition(); } player.reset(); } player.hideController(); clearAllErrors(); View errorView = getView().findViewById(R.id.panel_network_error); errorView.setVisibility(View.VISIBLE); TextView errorHeaderTextView = (TextView) errorView.findViewById(R.id.error_header); errorHeaderTextView.setText(getString(R.string.wifi_off_message)); errorView.findViewById(R.id.error_message).setVisibility(View.GONE); curMessageTypes.add(VideoNotPlayMessageType.IS_SHOWN_WIFI_SETTINGS_MESSAGE); } resetClosedCaptioning(); } } } catch (Exception ex) { logger.error(ex); } } /** * @return true if message is displayed on player to change wifi settings. */ public boolean isShownWifiSettingsMessage(){ return curMessageTypes.contains(VideoNotPlayMessageType.IS_SHOWN_WIFI_SETTINGS_MESSAGE); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; player.setFullScreen(isLandscape); updateController("orientation change"); player.requestAccessibilityFocusPausePlay(); } }