/***************************************************************************** * VideoPlayerActivity.java ***************************************************************************** * Copyright © 2011-2014 VLC authors and VideoLAN * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ package org.videolan.vlc.gui.video; import android.annotation.TargetApi; import android.app.KeyguardManager; import android.app.Presentation; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Color; import android.graphics.PixelFormat; import android.media.AudioManager; import android.media.MediaRouter; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.support.v4.app.FragmentManager; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.PopupMenu; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.GestureDetector; import android.view.InputDevice; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLayoutChangeListener; import android.view.View.OnSystemUiVisibilityChangeListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.RotateAnimation; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import android.widget.Toast; import org.videolan.libvlc.IVLCVout; import org.videolan.libvlc.LibVLC; import org.videolan.libvlc.Media; import org.videolan.libvlc.MediaPlayer; import org.videolan.libvlc.util.AndroidUtil; import org.videolan.libvlc.util.HWDecoderUtil; import org.videolan.vlc.BuildConfig; import org.videolan.vlc.MediaDatabase; import org.videolan.vlc.MediaWrapper; import org.videolan.vlc.PlaybackService; import org.videolan.vlc.R; import org.videolan.vlc.VLCApplication; import org.videolan.vlc.gui.browser.FilePickerActivity; import org.videolan.vlc.gui.PlaybackServiceActivity; import org.videolan.vlc.gui.MainActivity; import org.videolan.vlc.gui.preferences.PreferencesActivity; import org.videolan.vlc.gui.dialogs.AdvOptionsDialog; import org.videolan.vlc.interfaces.IDelayController; import org.videolan.vlc.util.AndroidDevices; import org.videolan.vlc.util.Strings; import org.videolan.vlc.util.Util; import org.videolan.vlc.util.VLCInstance; import org.videolan.vlc.widget.OnRepeatListener; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.StreamCorruptedException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; public class VideoPlayerActivity extends AppCompatActivity implements IVLCVout.Callback, GestureDetector.OnDoubleTapListener, IDelayController, LibVLC.HardwareAccelerationError, PlaybackService.Client.Callback, PlaybackService.Callback { public final static String TAG = "VLC/VideoPlayerActivity"; // Internal intent identifier to distinguish between internal launch and // external intent. public final static String PLAY_FROM_VIDEOGRID = "org.videolan.vlc.gui.video.PLAY_FROM_VIDEOGRID"; public final static String PLAY_EXTRA_ITEM_LOCATION = "item_location"; public final static String PLAY_EXTRA_SUBTITLES_LOCATION = "subtitles_location"; public final static String PLAY_EXTRA_ITEM_TITLE = "title"; public final static String PLAY_EXTRA_FROM_START = "from_start"; public final static String PLAY_EXTRA_OPENED_POSITION = "opened_position"; public final static String ACTION_RESULT = "org.videolan.vlc.player.result"; public final static String EXTRA_POSITION = "extra_position"; public final static String EXTRA_DURATION = "extra_duration"; public final static int RESULT_CONNECTION_FAILED = RESULT_FIRST_USER + 1; public final static int RESULT_PLAYBACK_ERROR = RESULT_FIRST_USER + 2; public final static int RESULT_HARDWARE_ACCELERATION_ERROR = RESULT_FIRST_USER + 3; public final static int RESULT_VIDEO_TRACK_LOST = RESULT_FIRST_USER + 4; private final PlaybackServiceActivity.Helper mHelper = new PlaybackServiceActivity.Helper(this, this); private PlaybackService mService; private SurfaceView mSurfaceView = null; private SurfaceView mSubtitlesSurfaceView = null; private FrameLayout mSurfaceFrame; private MediaRouter mMediaRouter; private MediaRouter.SimpleCallback mMediaRouterCallback; private SecondaryDisplay mPresentation; private int mPresentationDisplayId = -1; private Uri mUri; private boolean mAskResume = true; private GestureDetectorCompat mDetector; private static final int SURFACE_BEST_FIT = 0; private static final int SURFACE_FIT_HORIZONTAL = 1; private static final int SURFACE_FIT_VERTICAL = 2; private static final int SURFACE_FILL = 3; private static final int SURFACE_16_9 = 4; private static final int SURFACE_4_3 = 5; private static final int SURFACE_ORIGINAL = 6; private int mCurrentSize = SURFACE_BEST_FIT; private SharedPreferences mSettings; /** Overlay */ private ActionBar mActionBar; private ViewGroup mActionBarView; private View mOverlayProgress; private View mOverlayBackground; private View mOverlayButtons; private static final int OVERLAY_TIMEOUT = 4000; private static final int OVERLAY_INFINITE = -1; private static final int FADE_OUT = 1; private static final int SHOW_PROGRESS = 2; private static final int FADE_OUT_INFO = 3; private static final int START_PLAYBACK = 4; private static final int AUDIO_SERVICE_CONNECTION_FAILED = 5; private static final int RESET_BACK_LOCK = 6; private static final int CHECK_VIDEO_TRACKS = 7; private static final int HW_ERROR = 1000; // TODO REMOVE private boolean mDragging; private boolean mShowing; private DelayState mDelay = DelayState.OFF; private int mUiVisibility = -1; private SeekBar mSeekbar; private TextView mTitle; private TextView mSysTime; private TextView mBattery; private TextView mTime; private TextView mLength; private TextView mInfo; private View mVerticalBar; private View mVerticalBarProgress; private boolean mIsLoading; private ImageView mLoading; private ImageView mTipsBackground; private ImageView mPlayPause; private ImageView mTracks; private ImageView mRewind; private ImageView mForward; private ImageView mAdvOptions; private ImageView mDelayPlus; private ImageView mDelayMinus; private boolean mEnableBrightnessGesture; private boolean mEnableCloneMode; private boolean mDisplayRemainingTime = false; private int mScreenOrientation; private int mScreenOrientationLock; private ImageView mLock; private ImageView mSize; private boolean mIsLocked = false; /* -1 is a valid track (Disable) */ private int mLastAudioTrack = -2; private int mLastSpuTrack = -2; private int mOverlayTimeout = 0; private boolean mLockBackButton = false; /** * For uninterrupted switching between audio and video mode */ private boolean mSwitchingView; private boolean mHardwareAccelerationError; private boolean mEndReached; private boolean mHasSubItems = false; // Playlist private int savedIndexPosition = -1; // size of the video private int mVideoHeight; private int mVideoWidth; private int mVideoVisibleHeight; private int mVideoVisibleWidth; private int mSarNum; private int mSarDen; //Volume private AudioManager mAudioManager; private int mAudioMax; private boolean mMute = false; private int mVolSave; private float mVol; //Touch Events private static final int TOUCH_NONE = 0; private static final int TOUCH_VOLUME = 1; private static final int TOUCH_BRIGHTNESS = 2; private static final int TOUCH_SEEK = 3; private int mTouchAction = TOUCH_NONE; private int mSurfaceYDisplayRange; private float mInitTouchY, mTouchY =-1f, mTouchX=-1f; //stick event private static final int JOYSTICK_INPUT_DELAY = 300; private long mLastMove; // Brightness private boolean mIsFirstBrightnessGesture = true; private float mRestoreAutoBrightness = -1f; // Tracks & Subtitles private MediaPlayer.TrackDescription[] mAudioTracksList; private MediaPlayer.TrackDescription[] mSubtitleTracksList; /** * Used to store a selected subtitle; see onActivityResult. * It is possible to have multiple custom subs in one session * (just like desktop VLC allows you as well.) */ private final ArrayList<String> mSubtitleSelectedFiles = new ArrayList<String>(); /** * Flag to indicate whether the media should be paused once loaded * (e.g. lock screen, or to restore the pause state) */ private boolean mPlaybackStarted = false; private boolean mSurfacesAttached = false; // Tips private View mOverlayTips; private static final String PREF_TIPS_SHOWN = "video_player_tips_shown"; // Navigation handling (DVD, Blu-Ray...) private int mMenuIdx = -1; private boolean mIsNavMenu = false; /* for getTime and seek */ private long mForcedTime = -1; private long mLastTime = -1; private OnLayoutChangeListener mOnLayoutChangeListener; private AlertDialog mAlertDialog; private static LibVLC LibVLC() { return VLCInstance.get(); } @Override @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!VLCInstance.testCompatibleCPU(this)) { exit(RESULT_CANCELED); return; } if (AndroidUtil.isJellyBeanMR1OrLater()) { // Get the media router service (Miracast) mMediaRouter = (MediaRouter) VLCApplication.getAppContext().getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouterCallback = new MediaRouter.SimpleCallback() { @Override public void onRoutePresentationDisplayChanged( MediaRouter router, MediaRouter.RouteInfo info) { Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); final Display presentationDisplay = info.getPresentationDisplay(); final int newDisplayId = presentationDisplay != null ? presentationDisplay.getDisplayId() : -1; if (newDisplayId != mPresentationDisplayId) removePresentation(); } }; Log.d(TAG, "MediaRouter information : " + mMediaRouter .toString()); } mSettings = PreferenceManager.getDefaultSharedPreferences(this); /* Services and miscellaneous */ mAudioManager = (AudioManager) VLCApplication.getAppContext().getSystemService(AUDIO_SERVICE); mAudioMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); mEnableCloneMode = mSettings.getBoolean("enable_clone_mode", false); createPresentation(); setContentView(mPresentation == null ? R.layout.player : R.layout.player_remote_control); /** initialize Views an their Events */ mActionBar = getSupportActionBar(); mActionBar.setDisplayShowHomeEnabled(false); mActionBar.setDisplayShowTitleEnabled(false); mActionBar.setBackgroundDrawable(null); mActionBar.setDisplayShowCustomEnabled(true); mActionBar.setCustomView(R.layout.player_action_bar); mActionBarView = (ViewGroup) mActionBar.getCustomView(); mTitle = (TextView) mActionBarView.findViewById(R.id.player_overlay_title); mSysTime = (TextView) findViewById(R.id.player_overlay_systime); mBattery = (TextView) findViewById(R.id.player_overlay_battery); mOverlayProgress = findViewById(R.id.progress_overlay); RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)mOverlayProgress.getLayoutParams(); if (AndroidDevices.isPhone() || !AndroidDevices.hasNavBar()) { layoutParams.width = LayoutParams.MATCH_PARENT; } else { layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE); } mOverlayProgress.setLayoutParams(layoutParams); mOverlayBackground = findViewById(R.id.player_overlay_background); mOverlayButtons = findViewById(R.id.player_overlay_buttons); // Position and remaining time mTime = (TextView) findViewById(R.id.player_overlay_time); mLength = (TextView) findViewById(R.id.player_overlay_length); // the info textView is not on the overlay mInfo = (TextView) findViewById(R.id.player_overlay_textinfo); mVerticalBar = findViewById(R.id.verticalbar); mVerticalBarProgress = findViewById(R.id.verticalbar_progress); mEnableBrightnessGesture = mSettings.getBoolean("enable_brightness_gesture", true); mScreenOrientation = Integer.valueOf( mSettings.getString("screen_orientation_value", "4" /*SCREEN_ORIENTATION_SENSOR*/)); mPlayPause = (ImageView) findViewById(R.id.player_overlay_play); mTracks = (ImageView) findViewById(R.id.player_overlay_tracks); mAdvOptions = (ImageView) findViewById(R.id.player_overlay_adv_function); mLock = (ImageView) findViewById(R.id.lock_overlay_button); mSize = (ImageView) findViewById(R.id.player_overlay_size); if (mSettings.getBoolean("enable_seek_buttons", false)) initSeekButton(); mDelayPlus = (ImageView) findViewById(R.id.player_delay_plus); mDelayMinus = (ImageView) findViewById(R.id.player_delay_minus); mSurfaceView = (SurfaceView) findViewById(R.id.player_surface); mSubtitlesSurfaceView = (SurfaceView) findViewById(R.id.subtitles_surface); if (HWDecoderUtil.HAS_SUBTITLES_SURFACE) { mSubtitlesSurfaceView.setZOrderMediaOverlay(true); mSubtitlesSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); } else mSubtitlesSurfaceView.setVisibility(View.GONE); mSurfaceFrame = (FrameLayout) findViewById(R.id.player_surface_frame); mSeekbar = (SeekBar) findViewById(R.id.player_overlay_seekbar); /* Loading view */ mLoading = (ImageView) findViewById(R.id.player_overlay_loading); if (mPresentation != null) mTipsBackground = (ImageView) findViewById(R.id.player_remote_tips_background); dimStatusBar(false); startLoading(); mSwitchingView = false; mHardwareAccelerationError = false; mEndReached = false; mAskResume = mSettings.getBoolean("dialog_confirm_resume", false); // Clear the resume time, since it is only used for resumes in external // videos. SharedPreferences.Editor editor = mSettings.edit(); editor.putLong(PreferencesActivity.VIDEO_RESUME_TIME, -1); // Also clear the subs list, because it is supposed to be per session // only (like desktop VLC). We don't want the custom subtitle files // to persist forever with this video. editor.putString(PreferencesActivity.VIDEO_SUBTITLE_FILES, null); // Paused flag - per session too, like the subs list. editor.remove(PreferencesActivity.VIDEO_PAUSED); Util.commitPreferences(editor); IntentFilter filter = new IntentFilter(); if (mBattery != null) filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(VLCApplication.SLEEP_INTENT); registerReceiver(mReceiver, filter); this.setVolumeControlStream(AudioManager.STREAM_MUSIC); // Extra initialization when no secondary display is detected if (mPresentation == null) { // Orientation // 100 is the value for screen_orientation_start_lock setRequestedOrientation(mScreenOrientation != 100 ? mScreenOrientation : getScreenOrientation()); // Tips mOverlayTips = findViewById(R.id.player_overlay_tips); if(BuildConfig.tv || mSettings.getBoolean(PREF_TIPS_SHOWN, false)) mOverlayTips.setVisibility(View.GONE); else { mOverlayTips.bringToFront(); mOverlayTips.invalidate(); } } else setRequestedOrientation(getScreenOrientation()); resetHudLayout(); mDetector = new GestureDetectorCompat(this, mGestureListener); } public boolean onCreateOptionsMenu(Menu menu){ getMenuInflater().inflate(R.menu.video_player, menu); return super.onCreateOptionsMenu(menu); } public boolean onPrepareOptionsMenu(Menu menu){ MenuItem item = menu.findItem(R.id.pl_menu_nav); if (item != null) { item.setVisible(mMenuIdx >= 0 && !mIsNavMenu); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); } return super.onPrepareOptionsMenu(menu); } public boolean onOptionsItemSelected(MenuItem item){ switch (item.getItemId()) { case R.id.pl_menu_nav: showNavMenu(); return true; default: return super.onOptionsItemSelected(item); } } @Override protected void onResume() { super.onResume(); mSwitchingView = false; /* * Set listeners here to avoid NPE when activity is closing */ mSeekbar.setOnSeekBarChangeListener(mSeekListener); mLock.setOnClickListener(mLockListener); mPlayPause.setOnClickListener(mPlayPauseListener); mPlayPause.setOnLongClickListener(mPlayPauseLongListener); mLength.setOnClickListener(mRemainingTimeListener); mTime.setOnClickListener(mRemainingTimeListener); mSize.setOnClickListener(mSizeListener); if (mIsLocked && mScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) setRequestedOrientation(mScreenOrientationLock); } @Override protected void onNewIntent(Intent intent) { setIntent(intent); if (mPlaybackStarted) { Uri uri = intent.hasExtra(PLAY_EXTRA_ITEM_LOCATION) ? (Uri) intent.getExtras().getParcelable(PLAY_EXTRA_ITEM_LOCATION) : intent.getData(); if (uri == null || uri.equals(mUri)) return; stopPlayback(); startPlayback(); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override protected void onPause() { super.onPause(); mSeekbar.setOnSeekBarChangeListener(null); mLock.setOnClickListener(null); mPlayPause.setOnClickListener(null); mPlayPause.setOnLongClickListener(null); mLength.setOnClickListener(null); mTime.setOnClickListener(null); mSize.setOnClickListener(null); /* Stop the earliest possible to avoid vout error */ if (isFinishing()) stopPlayback(); else if (BuildConfig.tv && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !requestVisibleBehind(true)) stopPlayback(); } @Override public void onVisibleBehindCanceled() { super.onVisibleBehindCanceled(); stopPlayback(); exitOK(); } @Override public void onConfigurationChanged(Configuration newConfig) { if (!AndroidUtil.isHoneycombOrLater()) changeSurfaceLayout(); super.onConfigurationChanged(newConfig); resetHudLayout(); } public void resetHudLayout() { RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)mOverlayButtons.getLayoutParams(); if (getScreenOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { layoutParams.addRule(RelativeLayout.BELOW, R.id.player_overlay_length); layoutParams.addRule(RelativeLayout.RIGHT_OF, 0); layoutParams.addRule(RelativeLayout.LEFT_OF, 0); } else { layoutParams.addRule(RelativeLayout.BELOW, R.id.player_overlay_seekbar); layoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.player_overlay_time); layoutParams.addRule(RelativeLayout.LEFT_OF, R.id.player_overlay_length); } mOverlayButtons.setLayoutParams(layoutParams); } @Override protected void onStart() { super.onStart(); mHelper.onStart(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override protected void onStop() { super.onStop(); if (mAlertDialog != null && mAlertDialog.isShowing()) mAlertDialog.dismiss(); if (!isFinishing() && mSettings.getBoolean(PreferencesActivity.VIDEO_BACKGROUND, false)) { Util.commitPreferences(mSettings.edit().putBoolean(PreferencesActivity.VIDEO_RESTORE, true)); switchToAudioMode(false); } stopPlayback(); restoreBrightness(); if (mService != null) mService.removeCallback(this); mHelper.onStop(); } @TargetApi(android.os.Build.VERSION_CODES.FROYO) private void restoreBrightness() { if (mRestoreAutoBrightness != -1f) { int brightness = (int) (mRestoreAutoBrightness*255f); Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, brightness); Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); } } @Override protected void onDestroy() { super.onDestroy(); if (mReceiver != null) unregisterReceiver(mReceiver); // Dismiss the presentation when the activity is not visible. if (mPresentation != null) { Log.i(TAG, "Dismissing presentation because the activity is no longer visible."); mPresentation.dismiss(); mPresentation = null; } mAudioManager = null; } /** * Add or remove MediaRouter callbacks. This is provided for version targeting. * * @param add true to add, false to remove */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void mediaRouterAddCallback(boolean add) { if(!AndroidUtil.isJellyBeanMR1OrLater() || mMediaRouter == null) return; if(add) mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback); else mMediaRouter.removeCallback(mMediaRouterCallback); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void startPlayback() { /* start playback only when audio service and both surfaces are ready */ if (mPlaybackStarted || mService == null) return; mPlaybackStarted = true; /* Dispatch ActionBar touch events to the Activity */ mActionBarView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { onTouchEvent(event); return true; } }); if (AndroidUtil.isICSOrLater()) getWindow().getDecorView().setOnSystemUiVisibilityChangeListener( new OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { if (visibility == mUiVisibility) return; if (visibility == View.SYSTEM_UI_FLAG_VISIBLE && !mShowing && !isFinishing()) { showOverlay(); } mUiVisibility = visibility; } } ); if (AndroidUtil.isHoneycombOrLater()) { if (mOnLayoutChangeListener == null) { mOnLayoutChangeListener = new View.OnLayoutChangeListener() { private final Runnable mRunnable = new Runnable() { @Override public void run() { changeSurfaceLayout(); } }; @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { /* changeSurfaceLayout need to be called after the layout changed */ mHandler.removeCallbacks(mRunnable); mHandler.post(mRunnable); } } }; } mSurfaceFrame.addOnLayoutChangeListener(mOnLayoutChangeListener); } changeSurfaceLayout(); /* Listen for changes to media routes. */ if (mMediaRouter != null) mediaRouterAddCallback(true); LibVLC().setOnHardwareAccelerationError(this); final IVLCVout vlcVout = mService.getVLCVout(); vlcVout.detachViews(); if (mPresentation == null) { vlcVout.setVideoView(mSurfaceView); if (mSubtitlesSurfaceView.getVisibility() != View.GONE) vlcVout.setSubtitlesView(mSubtitlesSurfaceView); } else { vlcVout.setVideoView(mPresentation.mSurfaceView); if (mSubtitlesSurfaceView.getVisibility() != View.GONE) vlcVout.setSubtitlesView(mPresentation.mSubtitlesSurfaceView); } mSurfacesAttached = true; vlcVout.addCallback(this); vlcVout.attachViews(); mSurfaceView.setKeepScreenOn(true); loadMedia(); // Add any selected subtitle file from the file picker if(mSubtitleSelectedFiles.size() > 0) { for(String file : mSubtitleSelectedFiles) { Log.i(TAG, "Adding user-selected subtitle " + file); mService.addSubtitleTrack(file); } } // Set user playback speed mService.setRate(mSettings.getFloat(PreferencesActivity.VIDEO_SPEED, 1)); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void stopPlayback() { if (!mPlaybackStarted) return; if (mMute) mute(false); LibVLC().setOnHardwareAccelerationError(null); mPlaybackStarted = false; mService.removeCallback(this); final IVLCVout vlcVout = mService.getVLCVout(); vlcVout.removeCallback(this); if (mSurfacesAttached) vlcVout.detachViews(); mSurfaceView.setKeepScreenOn(false); mHandler.removeCallbacksAndMessages(null); mDetector.setOnDoubleTapListener(null); /* Stop listening for changes to media routes. */ if (mMediaRouter != null) mediaRouterAddCallback(false); if (AndroidUtil.isHoneycombOrLater() && mOnLayoutChangeListener != null) mSurfaceFrame.removeOnLayoutChangeListener(mOnLayoutChangeListener); if (AndroidUtil.isICSOrLater()) getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(null); mActionBarView.setOnTouchListener(null); if(mSwitchingView && mService != null) { Log.d(TAG, "mLocation = \"" + mUri + "\""); mService.showWithoutParse(savedIndexPosition); return; } final boolean isPaused = !mService.isPlaying(); long time = getTime(); long length = mService.getLength(); //remove saved position if in the last 5 seconds if (length - time < 5000) time = 0; else time -= 2000; // go back 2 seconds, to compensate loading time mService.stop(); SharedPreferences.Editor editor = mSettings.edit(); // Save position if (mService.isSeekable()) { if(MediaDatabase.getInstance().mediaItemExists(mUri)) { MediaDatabase.getInstance().updateMedia( mUri, MediaDatabase.INDEX_MEDIA_TIME, time); } else { // Video file not in media library, store time just for onResume() editor.putLong(PreferencesActivity.VIDEO_RESUME_TIME, time); } } if(isPaused) Log.d(TAG, "Video paused - saving flag"); editor.putBoolean(PreferencesActivity.VIDEO_PAUSED, isPaused); // Save selected subtitles String subtitleList_serialized = null; if(mSubtitleSelectedFiles.size() > 0) { Log.d(TAG, "Saving selected subtitle files"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(mSubtitleSelectedFiles); subtitleList_serialized = bos.toString(); } catch(IOException e) {} } editor.putString(PreferencesActivity.VIDEO_SUBTITLE_FILES, subtitleList_serialized); if (mUri != null) editor.putString(PreferencesActivity.VIDEO_LAST, mUri.toString()); // Save user playback speed and restore normal speed editor.putFloat(PreferencesActivity.VIDEO_SPEED, mService.getRate()); mService.setRate(1.0f); Util.commitPreferences(editor); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(data == null) return; if(data.getData() == null) Log.d(TAG, "Subtitle selection dialog was cancelled"); String subtitlesPath = data.getData().getPath(); mSubtitleSelectedFiles.add(subtitlesPath); mService.addSubtitleTrack(subtitlesPath); } public static void start(Context context, Uri uri) { start(context, uri, null, false, -1); } public static void start(Context context, Uri uri, boolean fromStart) { start(context, uri, null, fromStart, -1); } public static void start(Context context, Uri uri, String title) { start(context, uri, title, false, -1); } public static void startOpened(Context context, int openedPosition) { start(context, null, null, false, openedPosition); } private static void start(Context context, Uri uri, String title, boolean fromStart, int openedPosition) { Intent intent = new Intent(context, VideoPlayerActivity.class); intent.setAction(PLAY_FROM_VIDEOGRID); intent.putExtra(PLAY_EXTRA_ITEM_LOCATION, uri); intent.putExtra(PLAY_EXTRA_ITEM_TITLE, title); intent.putExtra(PLAY_EXTRA_FROM_START, fromStart); intent.putExtra(PLAY_EXTRA_OPENED_POSITION, openedPosition); if (openedPosition != -1) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); context.startActivity(intent); } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equalsIgnoreCase(Intent.ACTION_BATTERY_CHANGED)) { if (mBattery == null) return; int batteryLevel = intent.getIntExtra("level", 0); if (batteryLevel >= 50) mBattery.setTextColor(Color.GREEN); else if (batteryLevel >= 30) mBattery.setTextColor(Color.YELLOW); else mBattery.setTextColor(Color.RED); mBattery.setText(String.format("%d%%", batteryLevel)); } else if (action.equalsIgnoreCase(VLCApplication.SLEEP_INTENT)) { exitOK(); } } }; private void exit(int resultCode){ if (isFinishing()) return; Intent resultIntent = new Intent(ACTION_RESULT); if (mUri != null && mService != null) { resultIntent.setData(mUri); resultIntent.putExtra(EXTRA_POSITION, mService.getTime()); resultIntent.putExtra(EXTRA_DURATION, mService.getLength()); } setResult(resultCode, resultIntent); finish(); } private void exitOK() { exit(RESULT_OK); } @Override public boolean onTrackballEvent(MotionEvent event) { if (mIsLoading) return false; showOverlay(); return true; } @TargetApi(12) //only active for Android 3.1+ public boolean dispatchGenericMotionEvent(MotionEvent event){ if (mIsLoading) return false; //Check for a joystick event if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK || event.getAction() != MotionEvent.ACTION_MOVE) return false; InputDevice mInputDevice = event.getDevice(); float dpadx = event.getAxisValue(MotionEvent.AXIS_HAT_X); float dpady = event.getAxisValue(MotionEvent.AXIS_HAT_Y); if (mInputDevice == null || Math.abs(dpadx) == 1.0f || Math.abs(dpady) == 1.0f) return false; float x = AndroidDevices.getCenteredAxis(event, mInputDevice, MotionEvent.AXIS_X); float y = AndroidDevices.getCenteredAxis(event, mInputDevice, MotionEvent.AXIS_Y); float rz = AndroidDevices.getCenteredAxis(event, mInputDevice, MotionEvent.AXIS_RZ); if (System.currentTimeMillis() - mLastMove > JOYSTICK_INPUT_DELAY){ if (Math.abs(x) > 0.3){ if (BuildConfig.tv) { navigateDvdMenu(x > 0.0f ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT); } else seekDelta(x > 0.0f ? 10000 : -10000); } else if (Math.abs(y) > 0.3){ if (BuildConfig.tv) navigateDvdMenu(x > 0.0f ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN); else { if (mIsFirstBrightnessGesture) initBrightnessTouch(); changeBrightness(-y / 10f); } } else if (Math.abs(rz) > 0.3){ mVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); int delta = -(int) ((rz / 7) * mAudioMax); int vol = (int) Math.min(Math.max(mVol + delta, 0), mAudioMax); setAudioVolume(vol); } mLastMove = System.currentTimeMillis(); } return true; } @Override public void onBackPressed() { if (mLockBackButton) { mLockBackButton = false; mHandler.sendEmptyMessageDelayed(RESET_BACK_LOCK, 2000); Toast.makeText(this, getString(R.string.back_quit_lock), Toast.LENGTH_SHORT).show(); } else if (mDelay != DelayState.OFF){ endDelaySetting(); } else if (BuildConfig.tv && mShowing && !mIsLocked) { hideOverlay(true); } else exitOK(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B) return super.onKeyDown(keyCode, event); if (mIsLoading) { switch (keyCode) { case KeyEvent.KEYCODE_S: case KeyEvent.KEYCODE_MEDIA_STOP: exitOK(); return true; } return false; } showOverlayTimeout(OVERLAY_TIMEOUT); switch (keyCode) { case KeyEvent.KEYCODE_F: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: case KeyEvent.KEYCODE_MEDIA_NEXT: seekDelta(10000); return true; case KeyEvent.KEYCODE_R: case KeyEvent.KEYCODE_MEDIA_REWIND: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: seekDelta(-10000); return true; case KeyEvent.KEYCODE_BUTTON_R1: seekDelta(60000); return true; case KeyEvent.KEYCODE_BUTTON_L1: seekDelta(-60000); return true; case KeyEvent.KEYCODE_BUTTON_A: if (mOverlayProgress.getVisibility() == View.VISIBLE) return false; case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_SPACE: if (mIsNavMenu) return navigateDvdMenu(keyCode); else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) //prevent conflict with remote control return super.onKeyDown(keyCode, event); else doPlayPause(); return true; case KeyEvent.KEYCODE_O: case KeyEvent.KEYCODE_BUTTON_Y: case KeyEvent.KEYCODE_MENU: showAdvancedOptions(mAdvOptions); return true; case KeyEvent.KEYCODE_V: case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: case KeyEvent.KEYCODE_BUTTON_X: onAudioSubClick(mTracks); return true; case KeyEvent.KEYCODE_N: showNavMenu(); return true; case KeyEvent.KEYCODE_A: resizeVideo(); return true; case KeyEvent.KEYCODE_M: case KeyEvent.KEYCODE_VOLUME_MUTE: updateMute(); return true; case KeyEvent.KEYCODE_S: case KeyEvent.KEYCODE_MEDIA_STOP: exitOK(); return true; case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: if (mIsNavMenu) return navigateDvdMenu(keyCode); else return super.onKeyDown(keyCode, event); case KeyEvent.KEYCODE_J: delayAudio(-50000l); return true; case KeyEvent.KEYCODE_K: delayAudio(50000l); return true; case KeyEvent.KEYCODE_G: delaySubs(-50000l); return true; case KeyEvent.KEYCODE_H: delaySubs(50000l); return true; case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: if (mMute) { updateMute(); return true; } else return false; } return super.onKeyDown(keyCode, event); } private boolean navigateDvdMenu(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: mService.navigate(MediaPlayer.Navigate.Up); return true; case KeyEvent.KEYCODE_DPAD_DOWN: mService.navigate(MediaPlayer.Navigate.Down); return true; case KeyEvent.KEYCODE_DPAD_LEFT: mService.navigate(MediaPlayer.Navigate.Left); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: mService.navigate(MediaPlayer.Navigate.Right); return true; case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_BUTTON_X: case KeyEvent.KEYCODE_BUTTON_A: mService.navigate(MediaPlayer.Navigate.Activate); return true; default: return false; } } @Override public void showAudioDelaySetting() { mDelay = DelayState.AUDIO; showDelayControls(); } @Override public void showSubsDelaySetting() { mDelay = DelayState.SUBS; showDelayControls(); } public void showDelayControls(){ mTouchAction = TOUCH_NONE; showOverlayTimeout(OVERLAY_INFINITE); mDelayMinus.setOnClickListener(mAudioDelayListener); mDelayPlus.setOnClickListener(mAudioDelayListener); mDelayMinus.setOnTouchListener(new OnRepeatListener(mAudioDelayListener)); mDelayPlus.setOnTouchListener(new OnRepeatListener(mAudioDelayListener)); mDelayMinus.setVisibility(View.VISIBLE); mDelayPlus.setVisibility(View.VISIBLE); mDelayPlus.requestFocus(); initDelayInfo(); } private void initDelayInfo() { if (mPresentation == null) mVerticalBar.setVisibility(View.GONE); mInfo.setVisibility(View.VISIBLE); String text = ""; if (mDelay == DelayState.AUDIO) { text += getString(R.string.audio_delay)+"\n"; text += mService.getAudioDelay() / 1000l; } else if (mDelay == DelayState.SUBS) { text += getString(R.string.spu_delay)+"\n"; text += mService.getSpuDelay() / 1000l; } else text += "0"; text += " ms"; mInfo.setText(text); } @Override public void endDelaySetting() { mTouchAction = TOUCH_NONE; mDelay = DelayState.OFF; mDelayMinus.setOnClickListener(null); mDelayPlus.setOnClickListener(null); mDelayMinus.setVisibility(View.INVISIBLE); mDelayPlus.setVisibility(View.INVISIBLE); mInfo.setVisibility(View.INVISIBLE); mInfo.setText(""); mPlayPause.requestFocus(); } private OnClickListener mAudioDelayListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()){ case R.id.player_delay_minus: if (mDelay == DelayState.AUDIO) delayAudio(-50000); else if (mDelay == DelayState.SUBS) delaySubs(-50000); break; case R.id.player_delay_plus: if (mDelay == DelayState.AUDIO) delayAudio(50000); else if (mDelay == DelayState.SUBS) delaySubs(50000); break; } } }; public void delayAudio(long delta){ long delay = mService.getAudioDelay()+delta; mService.setAudioDelay(delay); mInfo.setText(getString(R.string.audio_delay)+"\n"+(delay/1000l)+" ms"); if (mDelay == DelayState.OFF) { mDelay = DelayState.AUDIO; initDelayInfo(); } } public void delaySubs(long delta){ Log.d(TAG, "delaySubs " + delta); long delay = mService.getSpuDelay()+delta; mService.setSpuDelay(delay); mInfo.setText(getString(R.string.spu_delay) + "\n" + (delay / 1000l) + " ms"); if (mDelay == DelayState.OFF) { mDelay = DelayState.SUBS; initDelayInfo(); } } /** * Lock screen rotation */ private void lockScreen() { if(mScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) setRequestedOrientation(14 /* SCREEN_ORIENTATION_LOCKED */); else setRequestedOrientation(getScreenOrientation()); mScreenOrientationLock = getScreenOrientation(); } showInfo(R.string.locked, 1000); mLock.setImageResource(R.drawable.ic_locked_circle); mTime.setEnabled(false); mSeekbar.setEnabled(false); mLength.setEnabled(false); mSize.setEnabled(false); hideOverlay(true); mLockBackButton = true; } /** * Remove screen lock */ private void unlockScreen() { if(mScreenOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); showInfo(R.string.unlocked, 1000); mLock.setImageResource(R.drawable.ic_lock_circle); mTime.setEnabled(true); mSeekbar.setEnabled(mService == null || mService.isSeekable()); mLength.setEnabled(true); mSize.setEnabled(true); mShowing = false; showOverlay(); mLockBackButton = false; } /** * Show text in the info view and vertical progress bar for "duration" milliseconds * @param text * @param duration * @param barNewValue new volume/brightness value (range: 0 - 15) */ private void showInfoWithVerticalBar(String text, int duration, int barNewValue) { showInfo(text, duration); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mVerticalBarProgress.getLayoutParams(); layoutParams.weight = barNewValue; mVerticalBarProgress.setLayoutParams(layoutParams); mVerticalBar.setVisibility(View.VISIBLE); } /** * Show text in the info view for "duration" milliseconds * @param text * @param duration */ private void showInfo(String text, int duration) { if (mPresentation == null) mVerticalBar.setVisibility(View.GONE); mInfo.setVisibility(View.VISIBLE); mInfo.setText(text); mHandler.removeMessages(FADE_OUT_INFO); mHandler.sendEmptyMessageDelayed(FADE_OUT_INFO, duration); } private void showInfo(int textid, int duration) { if (mPresentation == null) mVerticalBar.setVisibility(View.GONE); mInfo.setVisibility(View.VISIBLE); mInfo.setText(textid); mHandler.removeMessages(FADE_OUT_INFO); mHandler.sendEmptyMessageDelayed(FADE_OUT_INFO, duration); } /** * Show text in the info view * @param text */ private void showInfo(String text) { if (mPresentation == null) mVerticalBar.setVisibility(View.GONE); mHandler.removeMessages(FADE_OUT_INFO); mInfo.setVisibility(View.VISIBLE); mInfo.setText(text); hideInfo(); } /** * hide the info view with "delay" milliseconds delay * @param delay */ private void hideInfo(int delay) { mHandler.sendEmptyMessageDelayed(FADE_OUT_INFO, delay); } /** * hide the info view */ private void hideInfo() { hideInfo(0); } private void fadeOutInfo() { if (mInfo.getVisibility() == View.VISIBLE) mInfo.startAnimation(AnimationUtils.loadAnimation( VideoPlayerActivity.this, android.R.anim.fade_out)); mInfo.setVisibility(View.INVISIBLE); if (mPresentation == null) { if (mVerticalBar.getVisibility() == View.VISIBLE) { mVerticalBar.startAnimation(AnimationUtils.loadAnimation( VideoPlayerActivity.this, android.R.anim.fade_out)); mVerticalBar.setVisibility(View.INVISIBLE); } } } @Override public boolean onSingleTapConfirmed(MotionEvent e) { return false; } @Override public boolean onDoubleTap(MotionEvent e) { if (mService == null) return false; if (!mIsLocked) { doPlayPause(); return true; } return false; } @Override public boolean onDoubleTapEvent(MotionEvent e) { return false; } /* PlaybackService.Callback */ @Override public void update() { } @Override public void updateProgress() { } @Override public void onMediaEvent(Media.Event event) { switch (event.type) { case Media.Event.ParsedChanged: updateNavStatus(); break; case Media.Event.MetaChanged: break; case Media.Event.SubItemTreeAdded: mHasSubItems = true; break; } } @Override public void onMediaPlayerEvent(MediaPlayer.Event event) { switch (event.type) { case MediaPlayer.Event.Opening: mHasSubItems = false; break; case MediaPlayer.Event.Playing: onPlaying(); break; case MediaPlayer.Event.Paused: updateOverlayPausePlay(); break; case MediaPlayer.Event.Stopped: exitOK(); break; case MediaPlayer.Event.EndReached: /* Don't end the activity if the media has subitems since the next child will be * loaded by the PlaybackService */ if (!mHasSubItems) endReached(); break; case MediaPlayer.Event.EncounteredError: encounteredError(); break; case MediaPlayer.Event.TimeChanged: break; case MediaPlayer.Event.Vout: updateNavStatus(); if (mMenuIdx == -1) handleVout(event.getVoutCount()); break; case MediaPlayer.Event.ESAdded: case MediaPlayer.Event.ESDeleted: if (mMenuIdx == -1 && event.getEsChangedType() == Media.Track.Type.Video) { mHandler.removeMessages(CHECK_VIDEO_TRACKS); mHandler.sendEmptyMessageDelayed(CHECK_VIDEO_TRACKS, 1000); } invalidateESTracks(event.getEsChangedType()); break; case MediaPlayer.Event.SeekableChanged: updateSeekable(event.getSeekable()); break; case MediaPlayer.Event.PausableChanged: updatePausable(event.getPausable()); break; } } /** * Handle resize of the surface and the overlay */ private final Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (mService == null) return true; switch (msg.what) { case FADE_OUT: hideOverlay(false); break; case SHOW_PROGRESS: int pos = setOverlayProgress(); if (canShowProgress()) { msg = mHandler.obtainMessage(SHOW_PROGRESS); mHandler.sendMessageDelayed(msg, 1000 - (pos % 1000)); } break; case FADE_OUT_INFO: fadeOutInfo(); break; case START_PLAYBACK: startPlayback(); break; case AUDIO_SERVICE_CONNECTION_FAILED: exit(RESULT_CONNECTION_FAILED); break; case RESET_BACK_LOCK: mLockBackButton = true; break; case CHECK_VIDEO_TRACKS: if (mService.getVideoTracksCount() < 1 && mService.getAudioTracksCount() > 0) { Log.i(TAG, "No video track, open in audio mode"); switchToAudioMode(true); } break; case HW_ERROR: handleHardwareAccelerationError(); break; } return true; } }); private boolean canShowProgress() { return !mDragging && mShowing && mService != null && mService.isPlaying(); } private void onPlaying() { stopLoading(); showOverlay(true); setESTracks(); updateNavStatus(); } private void endReached() { if (mService == null) return; if (mService.getRepeatType() == PlaybackService.REPEAT_ONE){ seek(0); return; } if(mService.expand() == 0) { startLoading(); Log.d(TAG, "Found a video playlist, expanding it"); mHandler.post(new Runnable() { @Override public void run() { loadMedia(); } }); } else { /* Exit player when reaching the end */ mEndReached = true; exitOK(); } } private void encounteredError() { if (isFinishing()) return; /* Encountered Error, exit player with a message */ mAlertDialog = new AlertDialog.Builder(VideoPlayerActivity.this) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { exit(RESULT_PLAYBACK_ERROR); } }) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { exit(RESULT_PLAYBACK_ERROR); } }) .setTitle(R.string.encountered_error_title) .setMessage(R.string.encountered_error_message) .create(); mAlertDialog.show(); } @Override public void eventHardwareAccelerationError() { mHandler.sendEmptyMessage(HW_ERROR); } private void handleHardwareAccelerationError() { mHardwareAccelerationError = true; if (mSwitchingView) return; mService.removeCallback(this); mService.stop(); mAlertDialog = new AlertDialog.Builder(VideoPlayerActivity.this) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { loadMedia(); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { exit(RESULT_HARDWARE_ACCELERATION_ERROR); } }) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { exit(RESULT_HARDWARE_ACCELERATION_ERROR); } }) .setTitle(R.string.hardware_acceleration_error_title) .setMessage(R.string.hardware_acceleration_error_message) .create(); if(!isFinishing()) mAlertDialog.show(); } private void handleVout(int voutCount) { final IVLCVout vlcVout = mService.getVLCVout(); if (vlcVout.areViewsAttached() && voutCount == 0 && !mEndReached) { /* Video track lost, open in audio mode */ Log.i(TAG, "Video track lost, switching to audio"); mSwitchingView = true; exit(RESULT_VIDEO_TRACK_LOST); } } public void switchToAudioMode(boolean showUI) { if (mHardwareAccelerationError || mService == null) return; mSwitchingView = true; // Show the MainActivity if it is not in background. if (showUI && getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_VIEW)) { Intent i = new Intent(this, MainActivity.class); if (!Util.isCallable(i)){ try { i = new Intent(this, Class.forName("org.videolan.vlc.gui.tv.audioplayer.AudioPlayerActivity")); } catch (ClassNotFoundException e) { return; } } startActivity(i); } exitOK(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void changeSurfaceLayout() { int sw; int sh; // get screen size if (mPresentation == null) { sw = getWindow().getDecorView().getWidth(); sh = getWindow().getDecorView().getHeight(); } else { sw = mPresentation.getWindow().getDecorView().getWidth(); sh = mPresentation.getWindow().getDecorView().getHeight(); } if (mService != null) { final IVLCVout vlcVout = mService.getVLCVout(); vlcVout.setWindowSize(sw, sh); } double dw = sw, dh = sh; boolean isPortrait; if (mPresentation == null) { // getWindow().getDecorView() doesn't always take orientation into account, we have to correct the values isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; } else { isPortrait = false; } if (sw > sh && isPortrait || sw < sh && !isPortrait) { dw = sh; dh = sw; } // sanity check if (dw * dh == 0 || mVideoWidth * mVideoHeight == 0) { Log.e(TAG, "Invalid surface size"); return; } // compute the aspect ratio double ar, vw; if (mSarDen == mSarNum) { /* No indication about the density, assuming 1:1 */ vw = mVideoVisibleWidth; ar = (double)mVideoVisibleWidth / (double)mVideoVisibleHeight; } else { /* Use the specified aspect ratio */ vw = mVideoVisibleWidth * (double)mSarNum / mSarDen; ar = vw / mVideoVisibleHeight; } // compute the display aspect ratio double dar = dw / dh; switch (mCurrentSize) { case SURFACE_BEST_FIT: if (dar < ar) dh = dw / ar; else dw = dh * ar; break; case SURFACE_FIT_HORIZONTAL: dh = dw / ar; break; case SURFACE_FIT_VERTICAL: dw = dh * ar; break; case SURFACE_FILL: break; case SURFACE_16_9: ar = 16.0 / 9.0; if (dar < ar) dh = dw / ar; else dw = dh * ar; break; case SURFACE_4_3: ar = 4.0 / 3.0; if (dar < ar) dh = dw / ar; else dw = dh * ar; break; case SURFACE_ORIGINAL: dh = mVideoVisibleHeight; dw = vw; break; } SurfaceView surface; SurfaceView subtitlesSurface; FrameLayout surfaceFrame; if (mPresentation == null) { surface = mSurfaceView; subtitlesSurface = mSubtitlesSurfaceView; surfaceFrame = mSurfaceFrame; } else { surface = mPresentation.mSurfaceView; subtitlesSurface = mPresentation.mSubtitlesSurfaceView; surfaceFrame = mPresentation.mSurfaceFrame; } // set display size LayoutParams lp = surface.getLayoutParams(); lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth); lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight); surface.setLayoutParams(lp); if (subtitlesSurface != null) subtitlesSurface.setLayoutParams(lp); // set frame size (crop if necessary) lp = surfaceFrame.getLayoutParams(); lp.width = (int) Math.floor(dw); lp.height = (int) Math.floor(dh); surfaceFrame.setLayoutParams(lp); surface.invalidate(); if (subtitlesSurface != null) subtitlesSurface.invalidate(); } private void sendMouseEvent(int action, int button, int x, int y) { if (mService == null) return; final IVLCVout vlcVout = mService.getVLCVout(); vlcVout.sendMouseEvent(action, button, x, y); } /** * show/hide the overlay */ @Override public boolean onTouchEvent(MotionEvent event) { if (mService == null || mIsLoading) return false; if (mDelay != DelayState.OFF){ endDelaySetting(); return true; } if (mDetector.onTouchEvent(event)) return true; if (mIsLocked) { // locked, only handle show/hide & ignore all actions if (event.getAction() == MotionEvent.ACTION_UP) { if (!mShowing) { showOverlay(); } else { hideOverlay(true); } } return false; } DisplayMetrics screen = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(screen); if (mSurfaceYDisplayRange == 0) mSurfaceYDisplayRange = Math.min(screen.widthPixels, screen.heightPixels); float x_changed, y_changed; if (mTouchX != -1f && mTouchY != -1f) { y_changed = event.getRawY() - mTouchY; x_changed = event.getRawX() - mTouchX; } else { x_changed = 0f; y_changed = 0f; } // coef is the gradient's move to determine a neutral zone float coef = Math.abs (y_changed / x_changed); float xgesturesize = ((x_changed / screen.xdpi) * 2.54f); float delta_y = Math.max(1f,((mInitTouchY - event.getRawY()) / screen.xdpi + 0.5f)*2f); /* Offset for Mouse Events */ int[] offset = new int[2]; mSurfaceView.getLocationOnScreen(offset); int xTouch = Math.round((event.getRawX() - offset[0]) * mVideoWidth / mSurfaceView.getWidth()); int yTouch = Math.round((event.getRawY() - offset[1]) * mVideoHeight / mSurfaceView.getHeight()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // Audio mTouchY = mInitTouchY = event.getRawY(); mVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); mTouchAction = TOUCH_NONE; // Seek mTouchX = event.getRawX(); // Mouse events for the core sendMouseEvent(MotionEvent.ACTION_DOWN, 0, xTouch, yTouch); break; case MotionEvent.ACTION_MOVE: // Mouse events for the core sendMouseEvent(MotionEvent.ACTION_MOVE, 0, xTouch, yTouch); // No volume/brightness action if coef < 2 or a secondary display is connected //TODO : Volume action when a secondary display is connected if (mTouchAction != TOUCH_SEEK && coef > 2 && mPresentation == null) { if (Math.abs(y_changed/mSurfaceYDisplayRange) < 0.05) return false; mTouchY = event.getRawY(); mTouchX = event.getRawX(); // Volume (Up or Down - Right side) if (!mEnableBrightnessGesture || (int)mTouchX > (3 * screen.widthPixels / 5)){ doVolumeTouch(y_changed); hideOverlay(true); } // Brightness (Up or Down - Left side) if (mEnableBrightnessGesture && (int)mTouchX < (2 * screen.widthPixels / 5)){ doBrightnessTouch(y_changed); hideOverlay(true); } } else { // Seek (Right or Left move) doSeekTouch(Math.round(delta_y), xgesturesize, false); } break; case MotionEvent.ACTION_UP: // Mouse events for the core sendMouseEvent(MotionEvent.ACTION_UP, 0, xTouch, yTouch); if (mTouchAction == TOUCH_NONE) { if (!mShowing) { showOverlay(); } else { hideOverlay(true); } } // Seek if (mTouchAction == TOUCH_SEEK) doSeekTouch(Math.round(delta_y), xgesturesize, true); mTouchX = -1f; mTouchY = -1f; break; } return mTouchAction != TOUCH_NONE; } private void doSeekTouch(int coef, float gesturesize, boolean seek) { if (coef == 0) coef = 1; // No seek action if coef > 0.5 and gesturesize < 1cm if (Math.abs(gesturesize) < 1 || !mService.isSeekable()) return; if (mTouchAction != TOUCH_NONE && mTouchAction != TOUCH_SEEK) return; mTouchAction = TOUCH_SEEK; long length = mService.getLength(); long time = getTime(); // Size of the jump, 10 minutes max (600000), with a bi-cubic progression, for a 8cm gesture int jump = (int) ((Math.signum(gesturesize) * ((600000 * Math.pow((gesturesize / 8), 4)) + 3000)) / coef); // Adjust the jump if ((jump > 0) && ((time + jump) > length)) jump = (int) (length - time); if ((jump < 0) && ((time + jump) < 0)) jump = (int) -time; //Jump ! if (seek && length > 0) seek(time + jump, length); if (length > 0) //Show the jump's size showInfo(String.format("%s%s (%s) x%d", jump >= 0 ? "+" : "", Strings.millisToString(jump), Strings.millisToString(time + jump), coef), 1000); else showInfo(R.string.unseekable_stream, 1000); } private void doVolumeTouch(float y_changed) { if (mTouchAction != TOUCH_NONE && mTouchAction != TOUCH_VOLUME) return; float delta = - ((y_changed / mSurfaceYDisplayRange) * mAudioMax); mVol += delta; int vol = (int) Math.min(Math.max(mVol, 0), mAudioMax); if (delta != 0f) { setAudioVolume(vol); } } private void setAudioVolume(int vol) { mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, 0); /* Since android 4.3, the safe volume warning dialog is displayed only with the FLAG_SHOW_UI flag. * We don't want to always show the default UI volume, so show it only when volume is not set. */ int newVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (vol != newVol) mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, AudioManager.FLAG_SHOW_UI); mTouchAction = TOUCH_VOLUME; vol = vol * 100 / mAudioMax; showInfoWithVerticalBar(getString(R.string.volume) + "\n" + Integer.toString(vol) + '%', 1000, vol); } private void mute(boolean mute) { mMute = mute; if (mMute) mVolSave = mService.getVolume(); mService.setVolume(mMute ? 0 : mVolSave); } private void updateMute () { mute(!mMute); showInfo(mMute ? R.string.sound_off : R.string.sound_on,1000); } @TargetApi(android.os.Build.VERSION_CODES.FROYO) private void initBrightnessTouch() { float brightnesstemp = 0.6f; // Initialize the layoutParams screen brightness try { if (AndroidUtil.isFroyoOrLater() && Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); mRestoreAutoBrightness = android.provider.Settings.System.getInt(getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS) / 255.0f; } else { brightnesstemp = android.provider.Settings.System.getInt(getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS) / 255.0f; } } catch (SettingNotFoundException e) { e.printStackTrace(); } WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.screenBrightness = brightnesstemp; getWindow().setAttributes(lp); mIsFirstBrightnessGesture = false; } private void doBrightnessTouch(float y_changed) { if (mTouchAction != TOUCH_NONE && mTouchAction != TOUCH_BRIGHTNESS) return; if (mIsFirstBrightnessGesture) initBrightnessTouch(); mTouchAction = TOUCH_BRIGHTNESS; // Set delta : 2f is arbitrary for now, it possibly will change in the future float delta = - y_changed / mSurfaceYDisplayRange; changeBrightness(delta); } private void changeBrightness(float delta) { // Estimate and adjust Brightness WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.screenBrightness = Math.min(Math.max(lp.screenBrightness + delta, 0.01f), 1); // Set Brightness getWindow().setAttributes(lp); int brightness = Math.round(lp.screenBrightness * 100); showInfoWithVerticalBar(getString(R.string.brightness) + "\n" + brightness + '%', 1000, brightness); } /** * handle changes of the seekbar (slicer) */ private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { @Override public void onStartTrackingTouch(SeekBar seekBar) { mDragging = true; showOverlayTimeout(OVERLAY_INFINITE); } @Override public void onStopTrackingTouch(SeekBar seekBar) { mDragging = false; showOverlay(true); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser && mService.isSeekable()) { seek(progress); setOverlayProgress(); mTime.setText(Strings.millisToString(progress)); showInfo(Strings.millisToString(progress)); } } }; public void onAudioSubClick(View anchor){ final AppCompatActivity context = this; PopupMenu popupMenu = new PopupMenu(this, anchor); popupMenu.getMenuInflater().inflate(R.menu.audiosub_tracks, popupMenu.getMenu()); popupMenu.getMenu().findItem(R.id.video_menu_audio_track).setEnabled(mService.getAudioTracksCount() > 0); popupMenu.getMenu().findItem(R.id.video_menu_subtitles).setEnabled(mService.getSpuTracksCount() > 0); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.video_menu_audio_track) { selectAudioTrack(); return true; } else if (item.getItemId() == R.id.video_menu_subtitles) { selectSubtitles(); return true; } else if (item.getItemId() == R.id.video_menu_subtitles_picker) { if (mUri == null) return false; Intent filePickerIntent = new Intent(context, FilePickerActivity.class); if (TextUtils.equals(mUri.getScheme(), "file")) filePickerIntent.setData(Uri.parse(Strings.getParent(mUri.toString()))); context.startActivityForResult(filePickerIntent, 0); return true; } return false; } }); popupMenu.show(); } private interface TrackSelectedListener { boolean onTrackSelected(int trackID); } private void selectTrack(final MediaPlayer.TrackDescription[] tracks, int currentTrack, int titleId, final TrackSelectedListener listener) { if (listener == null) throw new IllegalArgumentException("listener must not be null"); if (tracks == null) return; final String[] nameList = new String[tracks.length]; final int[] idList = new int[tracks.length]; int i = 0; int listPosition = 0; for (MediaPlayer.TrackDescription track : tracks) { idList[i] = track.id; nameList[i] = track.name; // map the track position to the list position if (track.id == currentTrack) listPosition = i; i++; } mAlertDialog = new AlertDialog.Builder(VideoPlayerActivity.this) .setTitle(titleId) .setSingleChoiceItems(nameList, listPosition, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int listPosition) { int trackID = -1; // Reverse map search... for (MediaPlayer.TrackDescription track : tracks) { if (idList[listPosition] == track.id) { trackID = track.id; break; } } listener.onTrackSelected(trackID); dialog.dismiss(); } }) .create(); mAlertDialog.setCanceledOnTouchOutside(true); mAlertDialog.setOwnerActivity(VideoPlayerActivity.this); mAlertDialog.show(); } private void selectAudioTrack() { setESTrackLists(); selectTrack(mAudioTracksList, mService.getAudioTrack(), R.string.track_audio, new TrackSelectedListener() { @Override public boolean onTrackSelected(int trackID) { if (trackID < -1 || mService == null) return false; MediaDatabase.getInstance().updateMedia( mUri, MediaDatabase.INDEX_MEDIA_AUDIOTRACK, trackID); mService.setAudioTrack(trackID); return true; } }); } private void selectSubtitles() { setESTrackLists(); selectTrack(mSubtitleTracksList, mService.getSpuTrack(), R.string.track_text, new TrackSelectedListener() { @Override public boolean onTrackSelected(int trackID) { if (trackID < -1 || mService == null) return false; MediaDatabase.getInstance().updateMedia( mUri, MediaDatabase.INDEX_MEDIA_SPUTRACK, trackID); mService.setSpuTrack(trackID); return true; } }); } private void showNavMenu() { if (mMenuIdx >= 0) mService.setTitleIdx(mMenuIdx); } /** * */ private final OnClickListener mPlayPauseListener = new OnClickListener() { @Override public void onClick(View v) { doPlayPause(); } }; private final View.OnLongClickListener mPlayPauseLongListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mService == null) return false; if (mService.getRepeatType() == PlaybackService.REPEAT_ONE) { showInfo(getString(R.string.repeat)); mService.setRepeatType(PlaybackService.REPEAT_NONE); } else { mService.setRepeatType(PlaybackService.REPEAT_ONE); showInfo(getString(R.string.repeat_single)); } return true; } }; private void updateSeekable(boolean seekable) { if (mRewind != null) { mRewind.setEnabled(seekable); mRewind.setImageResource(seekable ? R.drawable.ic_rewind_circle : R.drawable.ic_rewind_circle_disable_o); } if (mForward != null){ mForward.setEnabled(seekable); mForward.setImageResource(seekable ? R.drawable.ic_forward_circle : R.drawable.ic_forward_circle_disable_o); } mSeekbar.setEnabled(seekable); } private void updatePausable(boolean pausable) { mPlayPause.setEnabled(pausable); if (!pausable) mPlayPause.setImageResource(R.drawable.ic_play_circle_disable_o); mDetector.setOnDoubleTapListener(pausable ? this : null); } private void doPlayPause() { if (!mService.isPausable()) return; if (mService.isPlaying()) { pause(); showOverlayTimeout(OVERLAY_INFINITE); } else { play(); showOverlayTimeout(OVERLAY_TIMEOUT); } } private long getTime() { long time = mService.getTime(); if (mForcedTime != -1 && mLastTime != -1) { /* XXX: After a seek, mService.getTime can return the position before or after * the seek position. Therefore we return mForcedTime in order to avoid the seekBar * to move between seek position and the actual position. * We have to wait for a valid position (that is after the seek position). * to re-init mLastTime and mForcedTime to -1 and return the actual position. */ if (mLastTime > mForcedTime) { if (time <= mLastTime && time > mForcedTime || time > mLastTime) mLastTime = mForcedTime = -1; } else { if (time > mForcedTime) mLastTime = mForcedTime = -1; } } return mForcedTime == -1 ? time : mForcedTime; } private void seek(long position) { seek(position, mService.getLength()); } private void seek(long position, float length) { mForcedTime = position; mLastTime = mService.getTime(); if (length == 0f) mService.setTime(position); else mService.setPosition(position / length); } private void seekDelta(int delta) { // unseekable stream if(mService.getLength() <= 0 || !mService.isSeekable()) return; long position = getTime() + delta; if (position < 0) position = 0; seek(position); showOverlay(); } private void initSeekButton() { mRewind = (ImageView) findViewById(R.id.player_overlay_rewind); mForward = (ImageView) findViewById(R.id.player_overlay_forward); mRewind.setVisibility(View.VISIBLE); mForward.setVisibility(View.VISIBLE); mRewind.setOnClickListener(mRewindListener); mForward.setOnClickListener(mForwardListener); mRewind.setOnTouchListener(new OnRepeatListener(mRewindListener)); mForward.setOnTouchListener(new OnRepeatListener(mForwardListener)); } private final OnClickListener mRewindListener = new OnClickListener() { @Override public void onClick(View v) { seekDelta(-30000); } }; private final OnClickListener mForwardListener = new OnClickListener() { @Override public void onClick(View v) { seekDelta(30000); } }; /** * */ private final OnClickListener mLockListener = new OnClickListener() { @Override public void onClick(View v) { if (mIsLocked) { mIsLocked = false; unlockScreen(); } else { mIsLocked = true; lockScreen(); } } }; /** * */ private final OnClickListener mSizeListener = new OnClickListener() { @Override public void onClick(View v) { resizeVideo(); } }; private void resizeVideo() { if (mCurrentSize < SURFACE_ORIGINAL) { mCurrentSize++; } else { mCurrentSize = 0; } changeSurfaceLayout(); switch (mCurrentSize) { case SURFACE_BEST_FIT: showInfo(R.string.surface_best_fit, 1000); break; case SURFACE_FIT_HORIZONTAL: showInfo(R.string.surface_fit_horizontal, 1000); break; case SURFACE_FIT_VERTICAL: showInfo(R.string.surface_fit_vertical, 1000); break; case SURFACE_FILL: showInfo(R.string.surface_fill, 1000); break; case SURFACE_16_9: showInfo("16:9", 1000); break; case SURFACE_4_3: showInfo("4:3", 1000); break; case SURFACE_ORIGINAL: showInfo(R.string.surface_original, 1000); break; } showOverlay(); } private final OnClickListener mRemainingTimeListener = new OnClickListener() { @Override public void onClick(View v) { mDisplayRemainingTime = !mDisplayRemainingTime; showOverlay(); } }; /** * show overlay * @param forceCheck: adjust the timeout in function of playing state */ private void showOverlay(boolean forceCheck) { if (forceCheck) mOverlayTimeout = 0; showOverlayTimeout(0); } /** * show overlay with the previous timeout value */ private void showOverlay() { showOverlay(false); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void setActionBarVisibility(boolean show) { if (show) mActionBar.show(); else mActionBar.hide(); } /** * show overlay */ private void showOverlayTimeout(int timeout) { if (mService == null) return; if (timeout != 0) mOverlayTimeout = timeout; if (mOverlayTimeout == 0) mOverlayTimeout = mService.isPlaying() ? OVERLAY_TIMEOUT : OVERLAY_INFINITE; if (mIsNavMenu){ mShowing = true; return; } mHandler.sendEmptyMessage(SHOW_PROGRESS); if (!mShowing) { mShowing = true; if (!mIsLocked) { setActionBarVisibility(true); mPlayPause.setVisibility(View.VISIBLE); if (mTracks != null) mTracks.setVisibility(View.VISIBLE); if (mAdvOptions !=null) mAdvOptions.setVisibility(View.VISIBLE); mSize.setVisibility(View.VISIBLE); if (mRewind != null) mRewind.setVisibility(View.VISIBLE); if (mForward != null) mForward.setVisibility(View.VISIBLE); } dimStatusBar(false); mOverlayProgress.setVisibility(View.VISIBLE); if (mPresentation != null) mOverlayBackground.setVisibility(View.VISIBLE); } mHandler.removeMessages(FADE_OUT); if (mOverlayTimeout != OVERLAY_INFINITE) mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT), mOverlayTimeout); updateOverlayPausePlay(); } /** * hider overlay */ private void hideOverlay(boolean fromUser) { if (mShowing) { mHandler.removeMessages(FADE_OUT); mHandler.removeMessages(SHOW_PROGRESS); Log.i(TAG, "remove View!"); if (mOverlayTips != null) mOverlayTips.setVisibility(View.INVISIBLE); if (!fromUser && !mIsLocked) { mOverlayProgress.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); mPlayPause.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); if (mTracks != null) mTracks.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); if (mAdvOptions !=null) mAdvOptions.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); if (mRewind != null) mRewind.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); if (mForward != null) mForward.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); } else mSize.setVisibility(View.INVISIBLE); if (mPresentation != null) { mOverlayBackground.startAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); mOverlayBackground.setVisibility(View.INVISIBLE); } setActionBarVisibility(false); mOverlayProgress.setVisibility(View.INVISIBLE); mPlayPause.setVisibility(View.INVISIBLE); if (mTracks != null) mTracks.setVisibility(View.INVISIBLE); if (mAdvOptions !=null) mAdvOptions.setVisibility(View.INVISIBLE); if (mRewind != null) mRewind.setVisibility(View.INVISIBLE); if (mForward != null) mForward.setVisibility(View.INVISIBLE); mShowing = false; dimStatusBar(true); } else if (!fromUser) { /* * Try to hide the Nav Bar again. * It seems that you can't hide the Nav Bar if you previously * showed it in the last 1-2 seconds. */ dimStatusBar(true); } } /** * Dim the status bar and/or navigation icons when needed on Android 3.x. * Hide it on Android 4.0 and later */ @TargetApi(Build.VERSION_CODES.KITKAT) private void dimStatusBar(boolean dim) { if (!AndroidUtil.isHoneycombOrLater() || mIsNavMenu) return; int visibility = 0; int navbar = 0; if (!AndroidDevices.hasCombBar() && AndroidUtil.isJellyBeanOrLater()) { visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; navbar = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } visibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; if (dim || mIsLocked) { navbar |= View.SYSTEM_UI_FLAG_LOW_PROFILE; if (!AndroidDevices.hasCombBar()) { navbar |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; if (AndroidUtil.isKitKatOrLater()) visibility |= View.SYSTEM_UI_FLAG_IMMERSIVE; visibility |= View.SYSTEM_UI_FLAG_FULLSCREEN; } } if (!dim) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); visibility |= View.SYSTEM_UI_FLAG_VISIBLE; } if (AndroidDevices.hasNavBar()) visibility |= navbar; getWindow().getDecorView().setSystemUiVisibility(visibility); } private void updateOverlayPausePlay() { if (mService == null) return; if (mService.isPausable()) mPlayPause.setImageResource(mService.isPlaying() ? R.drawable.ic_pause_circle : R.drawable.ic_play_circle); } /** * update the overlay */ private int setOverlayProgress() { if (mService == null) { return 0; } int time = (int) getTime(); int length = (int) mService.getLength(); if (length == 0) { MediaWrapper media = MediaDatabase.getInstance().getMedia(mUri); if (media != null) length = (int) media.getLength(); } // Update all view elements mSeekbar.setMax(length); mSeekbar.setProgress(time); if (mSysTime != null) mSysTime.setText(DateFormat.getTimeFormat(this).format(new Date(System.currentTimeMillis()))); if (time >= 0) mTime.setText(Strings.millisToString(time)); if (length >= 0) mLength.setText(mDisplayRemainingTime && length > 0 ? "-" + '\u00A0' + Strings.millisToString(length - time) : Strings.millisToString(length)); return time; } private void invalidateESTracks(int type) { switch (type) { case Media.Track.Type.Audio: mAudioTracksList = null; break; case Media.Track.Type.Text: mSubtitleTracksList = null; break; } } private void setESTracks() { if (mLastAudioTrack >= -1) { mService.setAudioTrack(mLastAudioTrack); mLastAudioTrack = -2; } if (mLastSpuTrack >= -1) { mService.setSpuTrack(mLastSpuTrack); mLastSpuTrack = -2; } } private void setESTrackLists() { if (mAudioTracksList == null && mService.getAudioTracksCount() > 0) mAudioTracksList = mService.getAudioTracks(); if (mSubtitleTracksList == null && mService.getSpuTracksCount() > 0) mSubtitleTracksList = mService.getSpuTracks(); } /** * */ private void play() { mService.play(); mSurfaceView.setKeepScreenOn(true); } /** * */ private void pause() { mService.pause(); mSurfaceView.setKeepScreenOn(false); } /* * Additionnal method to prevent alert dialog to pop up */ @SuppressWarnings({ "unchecked" }) private void loadMedia(boolean fromStart) { mAskResume = false; getIntent().putExtra(PLAY_EXTRA_FROM_START, fromStart); loadMedia(); } /** * External extras: * - position (long) - position of the video to start with (in ms) */ @TargetApi(12) @SuppressWarnings({ "unchecked" }) private void loadMedia() { if (mService == null) return; mUri = null; String title = getResources().getString(R.string.title); boolean fromStart = false; int openedPosition = -1; Uri data; String itemTitle = null; long intentPosition = -1; // position passed in by intent (ms) long mediaLength = 0l; Intent intent = getIntent(); String action = intent.getAction(); Bundle extras = getIntent().getExtras(); boolean wasPaused; /* * If the activity has been paused by pressing the power button, then * pressing it again will show the lock screen. * But onResume will also be called, even if vlc-android is still in * the background. * To workaround this, pause playback if the lockscreen is displayed. */ final KeyguardManager km = (KeyguardManager) VLCApplication.getAppContext().getSystemService(KEYGUARD_SERVICE); if (km.inKeyguardRestrictedInputMode()) wasPaused = true; else wasPaused = mSettings.getBoolean(PreferencesActivity.VIDEO_PAUSED, false); if (wasPaused) Log.d(TAG, "Video was previously paused, resuming in paused mode"); if (TextUtils.equals(action, Intent.ACTION_VIEW)) { /* Started from external application 'content' */ data = intent.getData(); if (data != null && TextUtils.equals(data.getScheme(), "content")) { // Mail-based apps - download the stream to a temporary file and play it if(data.getHost().equals("com.fsck.k9.attachmentprovider") || data.getHost().equals("gmail-ls")) { InputStream is = null; OutputStream os = null; try { Cursor cursor = getContentResolver().query(data, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); if (cursor != null) { cursor.moveToFirst(); String filename = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)); cursor.close(); Log.i(TAG, "Getting file " + filename + " from content:// URI"); is = getContentResolver().openInputStream(data); os = new FileOutputStream(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY + "/Download/" + filename); byte[] buffer = new byte[1024]; int bytesRead = 0; while((bytesRead = is.read(buffer)) >= 0) { os.write(buffer, 0, bytesRead); } mUri = AndroidUtil.PathToUri(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY + "/Download/" + filename); } } catch (Exception e) { Log.e(TAG, "Couldn't download file from mail URI"); encounteredError(); return; } finally { Util.close(is); Util.close(os); } } // Media or MMS URI else if (TextUtils.equals(data.getAuthority(), "media")){ try { Cursor cursor = getContentResolver().query(data, new String[]{ MediaStore.Video.Media.DATA }, null, null, null); if (cursor != null) { int column_index = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA); if (cursor.moveToFirst()) mUri = AndroidUtil.PathToUri(cursor.getString(column_index)); cursor.close(); } // other content-based URI (probably file pickers) else { mUri = data; } } catch (Exception e) { mUri = data; if (mUri.getScheme() == null) mUri = AndroidUtil.PathToUri(mUri.getPath()); Log.e(TAG, "Couldn't read the file from media or MMS"); } } else { ParcelFileDescriptor inputPFD = null; try { inputPFD = getContentResolver().openFileDescriptor(data, "r"); if (AndroidUtil.isHoneycombMr1OrLater()) mUri = AndroidUtil.LocationToUri("fd://" + inputPFD.getFd()); else { String fdString = inputPFD.getFileDescriptor().toString(); mUri = AndroidUtil.LocationToUri("fd://" + fdString.substring(15, fdString.length() - 1)); } Cursor returnCursor = getContentResolver().query(data, null, null, null, null); if (returnCursor != null) { if (returnCursor.getCount() > 0) { int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (nameIndex > -1) { returnCursor.moveToFirst(); title = returnCursor.getString(nameIndex); } } returnCursor.close(); } } catch (FileNotFoundException e) { Log.e(TAG, "Couldn't understand the intent"); encounteredError(); return; } } } /* External application */ else if (intent.getDataString() != null) { // Plain URI final String location = intent.getDataString(); // Remove VLC prefix if needed if (location.startsWith("vlc://")) { mUri = AndroidUtil.LocationToUri(location.substring(6)); } else { mUri = intent.getData(); if (mUri.getScheme() == null) mUri = AndroidUtil.PathToUri(mUri.getPath()); } } else { Log.e(TAG, "Couldn't understand the intent"); encounteredError(); return; } // Try to get the position if(extras != null) intentPosition = extras.getLong("position", -1); } /* ACTION_VIEW */ /* Started from VideoListActivity */ else if(TextUtils.equals(action, PLAY_FROM_VIDEOGRID) && extras != null) { mUri = extras.getParcelable(PLAY_EXTRA_ITEM_LOCATION); fromStart = extras.getBoolean(PLAY_EXTRA_FROM_START); mAskResume &= !fromStart; openedPosition = extras.getInt(PLAY_EXTRA_OPENED_POSITION, -1); } if (intent.hasExtra(PLAY_EXTRA_SUBTITLES_LOCATION)) mSubtitleSelectedFiles.add(extras.getString(PLAY_EXTRA_SUBTITLES_LOCATION)); if (intent.hasExtra(PLAY_EXTRA_ITEM_TITLE)) itemTitle = extras.getString(PLAY_EXTRA_ITEM_TITLE); if (openedPosition != -1) { // Provided externally from AudioService Log.d(TAG, "Continuing playback from AudioService at index " + openedPosition); MediaWrapper openedMedia = mService.getCurrentMediaWrapper(); if (openedMedia == null) { encounteredError(); return; } mUri = openedMedia.getUri(); itemTitle = openedMedia.getTitle(); savedIndexPosition = openedPosition; updateSeekable(mService.isSeekable()); updatePausable(mService.isPausable()); } if (mUri != null) { // restore last position MediaWrapper media = MediaDatabase.getInstance().getMedia(mUri); if(media != null) { // in media library if(media.getTime() > 0 && !fromStart && openedPosition == -1) { if (mAskResume) { showConfirmResumeDialog(); return; } else { intentPosition = media.getTime(); mediaLength = media.getLength(); } } // Consume fromStart option after first use to prevent // restarting again when playback is paused. intent.putExtra(PLAY_EXTRA_FROM_START, false); mLastAudioTrack = media.getAudioTrack(); mLastSpuTrack = media.getSpuTrack(); } else if (openedPosition == -1) { // not in media library if (intentPosition > 0 && mAskResume) { showConfirmResumeDialog(); return; } else { long rTime = mSettings.getLong(PreferencesActivity.VIDEO_RESUME_TIME, -1); if (rTime > 0 && !fromStart) { if (mAskResume) { showConfirmResumeDialog(); return; } else { Editor editor = mSettings.edit(); editor.putLong(PreferencesActivity.VIDEO_RESUME_TIME, -1); Util.commitPreferences(editor); intentPosition = rTime; } } } } // Start playback & seek if (openedPosition == -1) { /* prepare playback */ mService.stop(); // final MediaWrapper mw = new MediaWrapper(Uri.parse("http://forum.ea3w.com/coll_ea3w/attach/2008_10/12237832415.3gp")); final MediaWrapper mw = new MediaWrapper(Uri.parse("http://img1.peiyinxiu.com/2014121211339c64b7fb09742e2c.mp4")); if (wasPaused) mw.addFlags(MediaWrapper.MEDIA_PAUSED); if (mHardwareAccelerationError) mw.addFlags(MediaWrapper.MEDIA_NO_HWACCEL); mw.removeFlags(MediaWrapper.MEDIA_FORCE_AUDIO); mw.addFlags(MediaWrapper.MEDIA_VIDEO); mService.addCallback(this); mService.load(mw); savedIndexPosition = mService.getCurrentMediaPosition(); if (intentPosition > 0 && mediaLength >= 0l) seek(intentPosition, mediaLength); } else { mService.addCallback(this); // AudioService-transitioned playback for item after sleep and resume if(!mService.isPlaying()) mService.playIndex(savedIndexPosition); else onPlaying(); } // Get possible subtitles String subtitleList_serialized = mSettings.getString(PreferencesActivity.VIDEO_SUBTITLE_FILES, null); ArrayList<String> prefsList = new ArrayList<String>(); if(subtitleList_serialized != null) { ByteArrayInputStream bis = new ByteArrayInputStream(subtitleList_serialized.getBytes()); try { ObjectInputStream ois = new ObjectInputStream(bis); prefsList = (ArrayList<String>)ois.readObject(); } catch(ClassNotFoundException e) {} catch (StreamCorruptedException e) {} catch (IOException e) {} } for(String x : prefsList){ if(!mSubtitleSelectedFiles.contains(x)) mSubtitleSelectedFiles.add(x); } // Get the title if (itemTitle == null) title = mUri.getLastPathSegment(); } if (itemTitle != null) title = itemTitle; mTitle.setText(title); } @SuppressWarnings("deprecation") private int getScreenRotation(){ WindowManager wm = (WindowManager) VLCApplication.getAppContext().getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO /* Android 2.2 has getRotation */) { try { Method m = display.getClass().getDeclaredMethod("getRotation"); return (Integer) m.invoke(display); } catch (Exception e) { return Surface.ROTATION_0; } } else { return display.getOrientation(); } } @TargetApi(Build.VERSION_CODES.GINGERBREAD) private int getScreenOrientation(){ WindowManager wm = (WindowManager) VLCApplication.getAppContext().getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); int rot = getScreenRotation(); /* * Since getRotation() returns the screen's "natural" orientation, * which is not guaranteed to be SCREEN_ORIENTATION_PORTRAIT, * we have to invert the SCREEN_ORIENTATION value if it is "naturally" * landscape. */ @SuppressWarnings("deprecation") boolean defaultWide = display.getWidth() > display.getHeight(); if(rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) defaultWide = !defaultWide; if(defaultWide) { switch (rot) { case Surface.ROTATION_0: return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; case Surface.ROTATION_90: return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; case Surface.ROTATION_180: // SCREEN_ORIENTATION_REVERSE_PORTRAIT only available since API // Level 9+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); case Surface.ROTATION_270: // SCREEN_ORIENTATION_REVERSE_LANDSCAPE only available since API // Level 9+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); default: return 0; } } else { switch (rot) { case Surface.ROTATION_0: return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; case Surface.ROTATION_90: return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; case Surface.ROTATION_180: // SCREEN_ORIENTATION_REVERSE_PORTRAIT only available since API // Level 9+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); case Surface.ROTATION_270: // SCREEN_ORIENTATION_REVERSE_LANDSCAPE only available since API // Level 9+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); default: return 0; } } } public void showConfirmResumeDialog() { if (isFinishing()) return; pause(); /* Encountered Error, exit player with a message */ mAlertDialog = new AlertDialog.Builder(VideoPlayerActivity.this) .setMessage(R.string.confirm_resume) .setPositiveButton(R.string.resume_from_position, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { loadMedia(false); } }) .setNegativeButton(R.string.play_from_start, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { loadMedia(true); } }) .create(); mAlertDialog.setCancelable(false); mAlertDialog.show(); } public void showAdvancedOptions(View v) { FragmentManager fm = getSupportFragmentManager(); AdvOptionsDialog advOptionsDialog = new AdvOptionsDialog(); advOptionsDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { dimStatusBar(true); } }); advOptionsDialog.show(fm, "fragment_adv_options"); hideOverlay(false); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void createPresentation() { if (mMediaRouter == null || mEnableCloneMode) return; // Get the current route and its presentation display. MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute( MediaRouter.ROUTE_TYPE_LIVE_VIDEO); Display presentationDisplay = route != null ? route.getPresentationDisplay() : null; if (presentationDisplay != null) { // Show a new presentation if possible. Log.i(TAG, "Showing presentation on display: " + presentationDisplay); mPresentation = new SecondaryDisplay(this, LibVLC(), presentationDisplay); mPresentation.setOnDismissListener(mOnDismissListener); try { mPresentation.show(); mPresentationDisplayId = presentationDisplay.getDisplayId(); } catch (WindowManager.InvalidDisplayException ex) { Log.w(TAG, "Couldn't show presentation! Display was removed in " + "the meantime.", ex); mPresentation = null; } } else Log.i(TAG, "No secondary display detected"); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void removePresentation() { if (mMediaRouter == null) return; // Dismiss the current presentation if the display has changed. Log.i(TAG, "Dismissing presentation because the current route no longer " + "has a presentation display."); if (mPresentation != null) mPresentation.dismiss(); mPresentation = null; mPresentationDisplayId = -1; stopPlayback(); recreate(); } /** * Listens for when presentations are dismissed. */ private final DialogInterface.OnDismissListener mOnDismissListener = new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { if (dialog == mPresentation) { Log.i(TAG, "Presentation was dismissed."); mPresentation = null; } } }; @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private static final class SecondaryDisplay extends Presentation { public final static String TAG = "VLC/SecondaryDisplay"; private SurfaceView mSurfaceView; private SurfaceView mSubtitlesSurfaceView; private FrameLayout mSurfaceFrame; public SecondaryDisplay(Context context, LibVLC libVLC, Display display) { super(context, display); if (context instanceof AppCompatActivity) { setOwnerActivity((AppCompatActivity) context); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.player_remote); mSurfaceView = (SurfaceView) findViewById(R.id.remote_player_surface); mSubtitlesSurfaceView = (SurfaceView) findViewById(R.id.remote_subtitles_surface); mSurfaceFrame = (FrameLayout) findViewById(R.id.remote_player_surface_frame); if (HWDecoderUtil.HAS_SUBTITLES_SURFACE) { mSubtitlesSurfaceView.setZOrderMediaOverlay(true); mSubtitlesSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); } else mSubtitlesSurfaceView.setVisibility(View.GONE); VideoPlayerActivity activity = (VideoPlayerActivity)getOwnerActivity(); if (activity == null) { Log.e(TAG, "Failed to get the VideoPlayerActivity instance, secondary display won't work"); return; } Log.i(TAG, "Secondary display created"); } } /** * Start the video loading animation. */ private void startLoading() { mIsLoading = true; mOverlayProgress.setVisibility(View.INVISIBLE); AnimationSet anim = new AnimationSet(true); RotateAnimation rotate = new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotate.setDuration(800); rotate.setInterpolator(new DecelerateInterpolator()); rotate.setRepeatCount(RotateAnimation.INFINITE); anim.addAnimation(rotate); mLoading.startAnimation(anim); } /** * Stop the video loading animation. */ private void stopLoading() { mIsLoading = false; mOverlayProgress.setVisibility(View.VISIBLE); mLoading.setVisibility(View.INVISIBLE); mLoading.clearAnimation(); if (mPresentation != null) { mTipsBackground.setVisibility(View.VISIBLE); } } public void onClickOverlayTips(View v) { mOverlayTips.setVisibility(View.GONE); } public void onClickDismissTips(View v) { mOverlayTips.setVisibility(View.GONE); Editor editor = mSettings.edit(); editor.putBoolean(PREF_TIPS_SHOWN, true); Util.commitPreferences(editor); } private void updateNavStatus() { mIsNavMenu = false; mMenuIdx = -1; final MediaPlayer.Title[] titles = mService.getTitles(); if (titles != null) { final int currentIdx = mService.getTitleIdx(); for (int i = 0; i < titles.length; ++i) { final MediaPlayer.Title title = titles[i]; if (title.menu) { mMenuIdx = i; break; } } mIsNavMenu = mMenuIdx == currentIdx; } if (mIsNavMenu) { /* * Keep the overlay hidden in order to have touch events directly * transmitted to navigation handling. */ hideOverlay(false); } else if (mMenuIdx != -1) setESTracks(); supportInvalidateOptionsMenu(); } private GestureDetector.OnGestureListener mGestureListener = new GestureDetector.OnGestureListener() { @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) {} @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) {} @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } }; public PlaybackServiceActivity.Helper getHelper() { return mHelper; } @Override public void onConnected(PlaybackService service) { mService = service; mHandler.sendEmptyMessage(START_PLAYBACK); } @Override public void onDisconnected() { mService = null; mHandler.sendEmptyMessage(AUDIO_SERVICE_CONNECTION_FAILED); } @Override public void onNewLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) { if (width * height == 0) return; // store video size mVideoWidth = width; mVideoHeight = height; mVideoVisibleWidth = visibleWidth; mVideoVisibleHeight = visibleHeight; mSarNum = sarNum; mSarDen = sarDen; changeSurfaceLayout(); } @Override public void onSurfacesCreated(IVLCVout vlcVout) { } @Override public void onSurfacesDestroyed(IVLCVout vlcVout) { mSurfacesAttached = false; } }