From 6926d4fa93b3f7ca43cf57cc3c700230db9eb95a Mon Sep 17 00:00:00 2001 From: Chirag Maheshwari Date: Tue, 20 Jun 2017 04:31:27 +0530 Subject: [PATCH] Feature swipe gestures (#225) * Adds swipe gestures * adds GestureDetector * adds functions to update volume, seek, brightness * minor fixes for smooth gestures * size of the progress bar and minor looks changed * resolves minor issues - resolves video controls opening at the end of swipe gestures - move seek text to the bottom of the video --- build.gradle | 2 +- src/main/AndroidManifest.xml | 71 +- .../activity/ServerFileVideoActivity.java | 1102 +++++++++-------- .../amahi/anywhere/service/VideoService.java | 3 +- .../anywhere/util/VideoSwipeGestures.java | 245 ++++ .../amahi/anywhere/view/PercentageView.java | 106 ++ .../org/amahi/anywhere/view/SeekView.java | 44 + src/main/res/drawable/ic_brightness.xml | 14 + src/main/res/drawable/ic_volume_up.xml | 15 + src/main/res/drawable/percentage_bar.xml | 19 + .../res/layout/activity_server_file_video.xml | 8 + src/main/res/layout/percentage_view.xml | 47 + src/main/res/layout/seek_view.xml | 18 + 13 files changed, 1116 insertions(+), 578 deletions(-) create mode 100644 src/main/java/org/amahi/anywhere/util/VideoSwipeGestures.java create mode 100644 src/main/java/org/amahi/anywhere/view/PercentageView.java create mode 100644 src/main/java/org/amahi/anywhere/view/SeekView.java create mode 100644 src/main/res/drawable/ic_brightness.xml create mode 100644 src/main/res/drawable/ic_volume_up.xml create mode 100644 src/main/res/drawable/percentage_bar.xml create mode 100644 src/main/res/layout/percentage_view.xml create mode 100644 src/main/res/layout/seek_view.xml diff --git a/build.gradle b/build.gradle index b353261f1..07f444d92 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' classpath 'io.fabric.tools:gradle:1.22.1' } } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index eb2b62d11..cd0d533da 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -17,20 +17,22 @@ ~ along with Amahi. If not, see . --> + package="org.amahi.anywhere" + android:installLocation="auto"> - - - - - - - - - - - + + + + + + + + + + + + + - + - + - + + android:theme="@style/Theme.Amahi.Fullscreen"/> + android:theme="@style/Theme.Amahi.Fullscreen"/> - + android:theme="@style/Theme.Amahi.Fullscreen"/> + + android:label="@string/title_settings"/> + android:windowSoftInputMode="adjustResize"/> - + + android:resource="@xml/authenticator"/> - - + - + - - + + - - + + - + + android:value="d7b65346d3cf0028328f006bff447501d70f8996"/> + android:label="@string/title_version_settings"/> \ No newline at end of file diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFileVideoActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFileVideoActivity.java index ee6adb142..32c30ce19 100644 --- a/src/main/java/org/amahi/anywhere/activity/ServerFileVideoActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/ServerFileVideoActivity.java @@ -50,562 +50,580 @@ import org.amahi.anywhere.service.VideoService; import org.amahi.anywhere.util.FullScreenHelper; import org.amahi.anywhere.util.Intents; +import org.amahi.anywhere.util.VideoSwipeGestures; import org.amahi.anywhere.view.MediaControls; import org.videolan.libvlc.IVLCVout; import org.videolan.libvlc.Media; import org.videolan.libvlc.MediaPlayer; +import java.util.Locale; + /** * Video activity. Shows videos, supports basic operations such as pausing, resuming, scrolling. * The playback itself is done via {@link org.amahi.anywhere.service.VideoService}. * Backed up by {@link android.view.SurfaceView} and {@link org.videolan.libvlc.LibVLC}. */ public class ServerFileVideoActivity extends AppCompatActivity implements - ServiceConnection, - MediaController.MediaPlayerControl, - IVLCVout.OnNewVideoLayoutListener, - MediaPlayer.EventListener, - View.OnLayoutChangeListener { - - private static final boolean ENABLE_SUBTITLES = false; - // TODO: Make UI changes to enable/disable subtitles - - private VideoService videoService; - private MediaControls videoControls; - private FullScreenHelper fullScreen; - private Handler layoutChangeHandler; - - private int mVideoHeight = 0; - private int mVideoWidth = 0; - private int mVideoVisibleHeight = 0; - private int mVideoVisibleWidth = 0; - private int mVideoSarNum = 0; - private int mVideoSarDen = 0; - - private SurfaceView mSubtitlesSurface = null; - private float bufferPercent = 0.0f; - - private enum SurfaceSizes { - SURFACE_BEST_FIT, - SURFACE_FIT_SCREEN, - SURFACE_FILL, - SURFACE_16_9, - SURFACE_4_3, - SURFACE_ORIGINAL; - } - - private static SurfaceSizes CURRENT_SIZE = SurfaceSizes.SURFACE_BEST_FIT; - - //TODO Add feature for changing the screen size - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_server_file_video); - - setUpInjections(); - - setUpHomeNavigation(); - - setUpViews(); - - setUpVideo(); - - setUpFullScreen(); - - setUpVideoService(); - } - - private void setUpInjections() { - AmahiApplication.from(this).inject(this); - } - - private void setUpHomeNavigation() { - getSupportActionBar().setHomeButtonEnabled(true); - getSupportActionBar().setIcon(R.drawable.ic_launcher); - } - - private void setUpViews() { - if (ENABLE_SUBTITLES) { - final ViewStub stub = (ViewStub) findViewById(R.id.subtitles_stub); - mSubtitlesSurface = (SurfaceView) stub.inflate(); - mSubtitlesSurface.setZOrderMediaOverlay(true); - mSubtitlesSurface.getHolder().setFormat(PixelFormat.TRANSLUCENT); - } - } - - private void setUpVideo() { - setUpVideoTitle(); - } - - private void setUpVideoTitle() { - getSupportActionBar().setTitle(getVideoFile().getName()); - } - - private ServerFile getVideoFile() { - return getIntent().getParcelableExtra(Intents.Extras.SERVER_FILE); - } - - private void setUpFullScreen() { - fullScreen = new FullScreenHelper(getSupportActionBar(), getVideoMainFrame()); - fullScreen.enableOnClickToggle(false); - getVideoMainFrame().setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - fullScreen.toggle(); - videoControls.toggle(); - } - }); - fullScreen.init(); - } - - private MediaPlayer getMediaPlayer() { - assert videoService != null; - return videoService.getMediaPlayer(); - } - - @Override - protected void onStart() { - super.onStart(); - setUpVideoServiceBind(); - } - - private void setUpVideoService() { - Intent intent = new Intent(this, VideoService.class); - startService(intent); - } - - private void setUpVideoServiceBind() { - Intent intent = new Intent(this, VideoService.class); - bindService(intent, this, Context.BIND_AUTO_CREATE); - } - - @Override - public void onServiceDisconnected(ComponentName serviceName) { - } - - @Override - public void onServiceConnected(ComponentName serviceName, IBinder serviceBinder) { - setUpVideoServiceBind(serviceBinder); - - setUpVideoView(); - setUpVideoControls(); - setUpHandlers(); - setUpVideoPlayback(); - } - - private void setUpVideoServiceBind(IBinder serviceBinder) { - VideoService.VideoServiceBinder videoServiceBinder = (VideoService.VideoServiceBinder) serviceBinder; - videoService = videoServiceBinder.getVideoService(); - } - - private void setUpVideoView() { - SurfaceHolder surfaceHolder = getSurface().getHolder(); - - surfaceHolder.setFormat(PixelFormat.RGBX_8888); - surfaceHolder.setKeepScreenOn(true); - final IVLCVout vlcVout = getMediaPlayer().getVLCVout(); - vlcVout.setVideoView(getSurface()); - if (mSubtitlesSurface != null) - vlcVout.setSubtitlesView(mSubtitlesSurface); - vlcVout.attachViews(this); - getMediaPlayer().setEventListener(this); - } - - private SurfaceView getSurface() { - return (SurfaceView) findViewById(R.id.surface); - } - - private FrameLayout getSurfaceFrame() { - return (FrameLayout) findViewById(R.id.video_surface_frame); - } - - private FrameLayout getVideoMainFrame() { - return (FrameLayout) findViewById(R.id.video_main_frame); - } - - private void setUpVideoControls() { - if (!areVideoControlsAvailable()) { - videoControls = new MediaControls(this); - videoControls.setMediaPlayer(this); - videoControls.setAnchorView(getControlsContainer()); - } - } - - private boolean areVideoControlsAvailable() { - return videoControls != null; - } - - private void setUpHandlers() { - if (!layoutChangeHandlerAvailable()) { - layoutChangeHandler = new Handler(); - } - } - - private boolean layoutChangeHandlerAvailable() { - return layoutChangeHandler != null; - } - - private View getControlsContainer() { - return findViewById(R.id.container_controls); - } - - private void setUpVideoPlayback() { - if (videoService.isVideoStarted()) { - showThenAutoHideControls(); - } else { - videoService.startVideo(getVideoShare(), getVideoFile(), ENABLE_SUBTITLES); - addLayoutChangeListener(); - } - } - - private void showThenAutoHideControls() { - if (!isFinishing()) { - fullScreen.show(); - fullScreen.delayedHide(); - videoControls.show(); - } - } - - private ProgressBar getProgressBar() { - return (ProgressBar) findViewById(android.R.id.progress); - } - - private ServerShare getVideoShare() { - return getIntent().getParcelableExtra(Intents.Extras.SERVER_SHARE); - } - - private void addLayoutChangeListener() { - getSurfaceFrame().addOnLayoutChangeListener(this); - } - - @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) { - layoutChangeHandler.removeCallbacks(mRunnable); - layoutChangeHandler.post(mRunnable); - } - } - - private final Runnable mRunnable = new Runnable() { - @Override - public void run() { - updateVideoSurfaces(); - } - }; - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @Override - public void onNewVideoLayout(IVLCVout vout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) { - mVideoWidth = width; - mVideoHeight = height; - mVideoVisibleWidth = visibleWidth; - mVideoVisibleHeight = visibleHeight; - mVideoSarNum = sarNum; - mVideoSarDen = sarDen; - updateVideoSurfaces(); - } - - private void updateVideoSurfaces() { - int screenWidth = getWindow().getDecorView().getWidth(); - int screenHeight = getWindow().getDecorView().getHeight(); - - // sanity check - if (screenWidth * screenHeight == 0) { - Log.e("Error", "Invalid surface size"); - return; - } - - getMediaPlayer().getVLCVout().setWindowSize(screenWidth, screenHeight); - ViewGroup.LayoutParams lp = getSurface().getLayoutParams(); - if (mVideoWidth * mVideoHeight == 0) { + ServiceConnection, + MediaController.MediaPlayerControl, + IVLCVout.OnNewVideoLayoutListener, + MediaPlayer.EventListener, + View.OnLayoutChangeListener, + VideoSwipeGestures.SeekControl { + + private static final boolean ENABLE_SUBTITLES = false; + + private VideoService videoService; + private MediaControls videoControls; + private FullScreenHelper fullScreen; + private Handler layoutChangeHandler; + + private int mVideoHeight = 0; + private int mVideoWidth = 0; + private int mVideoVisibleHeight = 0; + private int mVideoVisibleWidth = 0; + private int mVideoSarNum = 0; + private int mVideoSarDen = 0; + + private SurfaceView mSubtitlesSurface = null; + private float bufferPercent = 0.0f; + + private enum SurfaceSizes { + SURFACE_BEST_FIT, + SURFACE_FIT_SCREEN, + SURFACE_FILL, + SURFACE_16_9, + SURFACE_4_3, + SURFACE_ORIGINAL; + } + + private static SurfaceSizes CURRENT_SIZE = SurfaceSizes.SURFACE_BEST_FIT; + + //TODO Add feature for changing the screen size + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_server_file_video); + + setUpInjections(); + + setUpHomeNavigation(); + + setUpViews(); + + setUpVideo(); + + setUpFullScreen(); + + setUpGestureListener(); + + setUpVideoService(); + } + + private void setUpInjections() { + AmahiApplication.from(this).inject(this); + } + + private void setUpHomeNavigation() { + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setIcon(R.drawable.ic_launcher); + } + + private void setUpViews() { + final ViewStub stub = (ViewStub) findViewById(R.id.subtitles_stub); + mSubtitlesSurface = (SurfaceView) stub.inflate(); + mSubtitlesSurface.setZOrderMediaOverlay(true); + mSubtitlesSurface.getHolder().setFormat(PixelFormat.TRANSLUCENT); + } + + private void setUpVideo() { + setUpVideoTitle(); + } + + private void setUpVideoTitle() { + getSupportActionBar().setTitle(getVideoFile().getName()); + } + + private ServerFile getVideoFile() { + return getIntent().getParcelableExtra(Intents.Extras.SERVER_FILE); + } + + private void setUpFullScreen() { + fullScreen = new FullScreenHelper(getSupportActionBar(), getVideoMainFrame()); + fullScreen.enableOnClickToggle(false); + getVideoMainFrame().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + fullScreen.toggle(); + videoControls.toggle(); + } + }); + fullScreen.init(); + } + + private FrameLayout getSwipeContainer() { + return (FrameLayout) findViewById(R.id.swipe_controls_frame); + } + + private void setUpGestureListener() { + getVideoMainFrame().setOnTouchListener(new VideoSwipeGestures(this, this, getSwipeContainer())); + } + + private MediaPlayer getMediaPlayer() { + assert videoService != null; + return videoService.getMediaPlayer(); + } + + @Override + protected void onStart() { + super.onStart(); + setUpVideoServiceBind(); + } + + private void setUpVideoService() { + Intent intent = new Intent(this, VideoService.class); + startService(intent); + } + + private void setUpVideoServiceBind() { + Intent intent = new Intent(this, VideoService.class); + bindService(intent, this, Context.BIND_AUTO_CREATE); + } + + @Override + public void onServiceDisconnected(ComponentName serviceName) { + } + + @Override + public void onServiceConnected(ComponentName serviceName, IBinder serviceBinder) { + setUpVideoServiceBind(serviceBinder); + + setUpVideoView(); + setUpVideoControls(); + setUpHandlers(); + setUpVideoPlayback(); + } + + private void setUpVideoServiceBind(IBinder serviceBinder) { + VideoService.VideoServiceBinder videoServiceBinder = (VideoService.VideoServiceBinder) serviceBinder; + videoService = videoServiceBinder.getVideoService(); + } + + private void setUpVideoView() { + SurfaceHolder surfaceHolder = getSurface().getHolder(); + + surfaceHolder.setFormat(PixelFormat.RGBX_8888); + surfaceHolder.setKeepScreenOn(true); + final IVLCVout vlcVout = getMediaPlayer().getVLCVout(); + vlcVout.setVideoView(getSurface()); + if (mSubtitlesSurface != null) + vlcVout.setSubtitlesView(mSubtitlesSurface); + vlcVout.attachViews(this); + getMediaPlayer().setEventListener(this); + } + + private SurfaceView getSurface() { + return (SurfaceView) findViewById(R.id.surface); + } + + private FrameLayout getSurfaceFrame() { + return (FrameLayout) findViewById(R.id.video_surface_frame); + } + + private FrameLayout getVideoMainFrame() { + return (FrameLayout) findViewById(R.id.video_main_frame); + } + + private void setUpVideoControls() { + if (!areVideoControlsAvailable()) { + videoControls = new MediaControls(this); + videoControls.setMediaPlayer(this); + videoControls.setAnchorView(getControlsContainer()); + } + + } + + private boolean areVideoControlsAvailable() { + return videoControls != null; + } + + private void setUpHandlers() { + if (!layoutChangeHandlerAvailable()) { + layoutChangeHandler = new Handler(); + } + } + + private boolean layoutChangeHandlerAvailable() { + return layoutChangeHandler != null; + } + + private View getControlsContainer() { + return findViewById(R.id.container_controls); + } + + private void setUpVideoPlayback() { + if (videoService.isVideoStarted()) { + showThenAutoHideControls(); + getVideoMainFrame().setVisibility(View.VISIBLE); + getProgressBar().setVisibility(View.INVISIBLE); + } else { + videoService.startVideo(getVideoShare(), getVideoFile(), ENABLE_SUBTITLES); + addLayoutChangeListener(); + } + } + + private void showThenAutoHideControls() { + if (!isFinishing()) { + fullScreen.show(); + fullScreen.delayedHide(); + videoControls.show(); + } + } + + private ProgressBar getProgressBar() { + return (ProgressBar) findViewById(android.R.id.progress); + } + + private ServerShare getVideoShare() { + return getIntent().getParcelableExtra(Intents.Extras.SERVER_SHARE); + } + + private void addLayoutChangeListener() { + getSurfaceFrame().addOnLayoutChangeListener(this); + } + + @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) { + layoutChangeHandler.removeCallbacks(mRunnable); + layoutChangeHandler.post(mRunnable); + } + } + + private final Runnable mRunnable = new Runnable() { + @Override + public void run() { + updateVideoSurfaces(); + } + }; + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + @Override + public void onNewVideoLayout(IVLCVout vout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) { + mVideoWidth = width; + mVideoHeight = height; + mVideoVisibleWidth = visibleWidth; + mVideoVisibleHeight = visibleHeight; + mVideoSarNum = sarNum; + mVideoSarDen = sarDen; + updateVideoSurfaces(); + } + + private void updateVideoSurfaces() { + int screenWidth = getWindow().getDecorView().getWidth(); + int screenHeight = getWindow().getDecorView().getHeight(); + + // sanity check + if (screenWidth * screenHeight == 0) { + Log.e("Error", "Invalid surface size"); + return; + } + + getMediaPlayer().getVLCVout().setWindowSize(screenWidth, screenHeight); + ViewGroup.LayoutParams lp = getSurface().getLayoutParams(); + if (mVideoWidth * mVideoHeight == 0) { /* Case of OpenGL vouts: handles the placement of the video using MediaPlayer API */ - lp.width = ViewGroup.LayoutParams.MATCH_PARENT; - lp.height = ViewGroup.LayoutParams.MATCH_PARENT; - getSurface().setLayoutParams(lp); - lp = getSurfaceFrame().getLayoutParams(); - lp.width = ViewGroup.LayoutParams.MATCH_PARENT; - lp.height = ViewGroup.LayoutParams.MATCH_PARENT; - getSurfaceFrame().setLayoutParams(lp); - changeMediaPlayerLayout(screenWidth, screenHeight); - return; - } - - if (lp.width == lp.height && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { + lp.width = ViewGroup.LayoutParams.MATCH_PARENT; + lp.height = ViewGroup.LayoutParams.MATCH_PARENT; + getSurface().setLayoutParams(lp); + lp = getSurfaceFrame().getLayoutParams(); + lp.width = ViewGroup.LayoutParams.MATCH_PARENT; + lp.height = ViewGroup.LayoutParams.MATCH_PARENT; + getSurfaceFrame().setLayoutParams(lp); + changeMediaPlayerLayout(screenWidth, screenHeight); + return; + } + + if (lp.width == lp.height && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { /* We handle the placement of the video using Android View LayoutParams */ - getMediaPlayer().setAspectRatio(null); - getMediaPlayer().setScale(0); - } - - double dw = screenWidth, dh = screenHeight; - final boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - if (screenWidth > screenHeight && isPortrait || screenWidth < screenHeight && !isPortrait) { - dw = screenHeight; - dh = screenWidth; - } - - // compute the aspect ratio - double ar, vw; - if (mVideoSarDen == mVideoSarNum) { + getMediaPlayer().setAspectRatio(null); + getMediaPlayer().setScale(0); + } + + double dw = screenWidth, dh = screenHeight; + final boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + if (screenWidth > screenHeight && isPortrait || screenWidth < screenHeight && !isPortrait) { + dw = screenHeight; + dh = screenWidth; + } + + // compute the aspect ratio + double ar, vw; + if (mVideoSarDen == mVideoSarNum) { /* No indication about the density, assuming 1:1 */ - vw = mVideoVisibleWidth; - ar = (double)mVideoVisibleWidth / (double)mVideoVisibleHeight; - } else { + vw = mVideoVisibleWidth; + ar = (double) mVideoVisibleWidth / (double) mVideoVisibleHeight; + } else { /* Use the specified aspect ratio */ - vw = mVideoVisibleWidth * (double)mVideoSarNum / mVideoSarDen; - ar = vw / mVideoVisibleHeight; - } - - // compute the display aspect ratio - double dar = dw / dh; - switch (CURRENT_SIZE) { - case SURFACE_BEST_FIT: - if (dar < ar) - dh = dw / ar; - else - dw = dh * ar; - break; - case SURFACE_FIT_SCREEN: - if (dar >= ar) - dh = dw / ar; /* horizontal */ - else - dw = dh * ar; /* vertical */ - 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; - } - // set display size - lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth); - lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight); - getSurface().setLayoutParams(lp); - if (mSubtitlesSurface != null) - mSubtitlesSurface.setLayoutParams(lp); - - // set frame size (crop if necessary) - lp = getSurfaceFrame().getLayoutParams(); - lp.width = (int) Math.floor(dw); - lp.height = (int) Math.floor(dh); - getSurfaceFrame().setLayoutParams(lp); - getSurface().invalidate(); - if (mSubtitlesSurface != null) - mSubtitlesSurface.invalidate(); - } - - private void changeMediaPlayerLayout(int displayW, int displayH) { + vw = mVideoVisibleWidth * (double) mVideoSarNum / mVideoSarDen; + ar = vw / mVideoVisibleHeight; + } + + // compute the display aspect ratio + double dar = dw / dh; + switch (CURRENT_SIZE) { + case SURFACE_BEST_FIT: + if (dar < ar) + dh = dw / ar; + else + dw = dh * ar; + break; + case SURFACE_FIT_SCREEN: + if (dar >= ar) + dh = dw / ar; /* horizontal */ + else + dw = dh * ar; /* vertical */ + 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; + } + // set display size + lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth); + lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight); + getSurface().setLayoutParams(lp); + if (mSubtitlesSurface != null) + mSubtitlesSurface.setLayoutParams(lp); + + // set frame size (crop if necessary) + lp = getSurfaceFrame().getLayoutParams(); + lp.width = (int) Math.floor(dw); + lp.height = (int) Math.floor(dh); + getSurfaceFrame().setLayoutParams(lp); + lp = getSwipeContainer().getLayoutParams(); + lp.width = (int) Math.floor(dw); + lp.height = (int) Math.floor(dh); + getSwipeContainer().setLayoutParams(lp); + getSurface().invalidate(); + if (mSubtitlesSurface != null) + mSubtitlesSurface.invalidate(); + } + + private void changeMediaPlayerLayout(int displayW, int displayH) { /* Change the video placement using the MediaPlayer API */ - switch (CURRENT_SIZE) { - case SURFACE_BEST_FIT: - getMediaPlayer().setAspectRatio(null); - getMediaPlayer().setScale(0); - break; - case SURFACE_FIT_SCREEN: - case SURFACE_FILL: { - Media.VideoTrack vtrack = getMediaPlayer().getCurrentVideoTrack(); - if (vtrack == null) - return; - final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom - || vtrack.orientation == Media.VideoTrack.Orientation.RightTop; - if (CURRENT_SIZE == SurfaceSizes.SURFACE_FIT_SCREEN) { - int videoW = vtrack.width; - int videoH = vtrack.height; - if (videoSwapped) { - int swap = videoW; - videoW = videoH; - videoH = swap; - } - if (vtrack.sarNum != vtrack.sarDen) - videoW = videoW * vtrack.sarNum / vtrack.sarDen; - float videoAspectRatio = videoW / (float) videoH; - float displayAspectRatio = displayW / (float) displayH; - float scale; - if (displayAspectRatio >= videoAspectRatio) - scale = displayW / (float) videoW; /* horizontal */ - else - scale = displayH / (float) videoH; /* vertical */ - getMediaPlayer().setScale(scale); - getMediaPlayer().setAspectRatio(null); - } else { - getMediaPlayer().setScale(0); - getMediaPlayer().setAspectRatio(!videoSwapped ? ""+displayW+":"+displayH - : ""+displayH+":"+displayW); - } - break; - } - case SURFACE_16_9: - getMediaPlayer().setAspectRatio("16:9"); - getMediaPlayer().setScale(0); - break; - case SURFACE_4_3: - getMediaPlayer().setAspectRatio("4:3"); - getMediaPlayer().setScale(0); - break; - case SURFACE_ORIGINAL: - getMediaPlayer().setAspectRatio(null); - getMediaPlayer().setScale(1); - break; - } - } - - @Override - public void start() { - videoService.playVideo(); - } - - @Override - public boolean canPause() { - return true; - } - - @Override - public void pause() { - videoService.pauseVideo(); - } - - @Override - public boolean canSeekBackward() { - return true; - } - - @Override - public boolean canSeekForward() { - return true; - } - - @Override - public void seekTo(int time) { - getMediaPlayer().setTime(time); - } - - @Override - public int getDuration() { - return (int) getMediaPlayer().getLength(); - } - - @Override - public int getCurrentPosition() { - return (int) getMediaPlayer().getTime(); - } - - @Override - public boolean isPlaying() { - return getMediaPlayer().isPlaying(); - } - - @Override - public int getBufferPercentage() { - return (int) bufferPercent; - } - - @Override - public int getAudioSessionId() { - return 0; - } - - @Override - public void onEvent(MediaPlayer.Event event) { - - switch(event.type) { - case MediaPlayer.Event.MediaChanged: - getVideoMainFrame().setVisibility(View.VISIBLE); - break; - case MediaPlayer.Event.Playing: - getProgressBar().setVisibility(View.INVISIBLE); - showThenAutoHideControls(); - break; - case MediaPlayer.Event.Paused: - showThenAutoHideControls(); - break; - case MediaPlayer.Event.EndReached: - finish(); - break; - case MediaPlayer.Event.Buffering: - bufferPercent = event.getBuffering(); - break; - case MediaPlayer.Event.EncounteredError: - Toast.makeText(this, R.string.message_error_video, Toast.LENGTH_SHORT).show(); - break; - } - - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - switch (menuItem.getItemId()) { - case android.R.id.home: - finish(); - return true; - - default: - return super.onOptionsItemSelected(menuItem); - } - } - - @Override - public void onPause() { - super.onPause(); - - videoControls.hide(); - - if (!isChangingConfigurations()) { - pause(); - } - - if (isFinishing()) { - tearDownVideoPlayback(); - } - } - - private void tearDownVideoPlayback() { - getMediaPlayer().stop(); - } - - @Override - protected void onStop() { - super.onStop(); - getSurfaceFrame().removeOnLayoutChangeListener(this); - getMediaPlayer().getVLCVout().detachViews(); - tearDownVideoServiceBind(); - } - - private void tearDownVideoServiceBind() { - unbindService(this); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (isFinishing()) { - tearDownVideoService(); - } - } - - private void tearDownVideoService() { - Intent intent = new Intent(this, VideoService.class); - stopService(intent); - } - - public static boolean supports(String mime_type) { - String type = mime_type.split("/")[0]; - - return "video".equals(type); - } + switch (CURRENT_SIZE) { + case SURFACE_BEST_FIT: + getMediaPlayer().setAspectRatio(null); + getMediaPlayer().setScale(0); + break; + case SURFACE_FIT_SCREEN: + case SURFACE_FILL: { + Media.VideoTrack vtrack = getMediaPlayer().getCurrentVideoTrack(); + if (vtrack == null) + return; + final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom + || vtrack.orientation == Media.VideoTrack.Orientation.RightTop; + if (CURRENT_SIZE == SurfaceSizes.SURFACE_FIT_SCREEN) { + int videoW = vtrack.width; + int videoH = vtrack.height; + if (videoSwapped) { + int swap = videoW; + videoW = videoH; + videoH = swap; + } + if (vtrack.sarNum != vtrack.sarDen) + videoW = videoW * vtrack.sarNum / vtrack.sarDen; + float videoAspectRatio = videoW / (float) videoH; + float displayAspectRatio = displayW / (float) displayH; + float scale; + if (displayAspectRatio >= videoAspectRatio) + scale = displayW / (float) videoW; /* horizontal */ + else + scale = displayH / (float) videoH; /* vertical */ + getMediaPlayer().setScale(scale); + getMediaPlayer().setAspectRatio(null); + } else { + getMediaPlayer().setScale(0); + getMediaPlayer().setAspectRatio(!videoSwapped ? "" + displayW + ":" + displayH + : "" + displayH + ":" + displayW); + } + break; + } + case SURFACE_16_9: + getMediaPlayer().setAspectRatio("16:9"); + getMediaPlayer().setScale(0); + break; + case SURFACE_4_3: + getMediaPlayer().setAspectRatio("4:3"); + getMediaPlayer().setScale(0); + break; + case SURFACE_ORIGINAL: + getMediaPlayer().setAspectRatio(null); + getMediaPlayer().setScale(1); + break; + } + } + + @Override + public void start() { + videoService.playVideo(); + } + + @Override + public boolean canPause() { + return true; + } + + @Override + public void pause() { + videoService.pauseVideo(); + } + + @Override + public boolean canSeekBackward() { + return true; + } + + @Override + public boolean canSeekForward() { + return true; + } + + @Override + public void seekTo(int time) { + getMediaPlayer().setTime(time); + } + + @Override + public int getDuration() { + return (int) getMediaPlayer().getLength(); + } + + @Override + public int getCurrentPosition() { + return (int) getMediaPlayer().getTime(); + } + + @Override + public boolean isPlaying() { + return getMediaPlayer().isPlaying(); + } + + @Override + public int getBufferPercentage() { + return (int) bufferPercent; + } + + @Override + public int getAudioSessionId() { + return 0; + } + + @Override + public void onEvent(MediaPlayer.Event event) { + + switch (event.type) { + case MediaPlayer.Event.MediaChanged: + getVideoMainFrame().setVisibility(View.VISIBLE); + break; + case MediaPlayer.Event.Playing: + getProgressBar().setVisibility(View.INVISIBLE); + showThenAutoHideControls(); + break; + case MediaPlayer.Event.Paused: + showThenAutoHideControls(); + break; + case MediaPlayer.Event.EndReached: + finish(); + break; + case MediaPlayer.Event.Buffering: + bufferPercent = event.getBuffering(); + break; + case MediaPlayer.Event.EncounteredError: + Toast.makeText(this, R.string.message_error_video, Toast.LENGTH_SHORT).show(); + break; + } + + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case android.R.id.home: + finish(); + return true; + + default: + return super.onOptionsItemSelected(menuItem); + } + } + + @Override + public void onPause() { + super.onPause(); + + videoControls.hide(); + + if (!isChangingConfigurations()) { + pause(); + } + + if (isFinishing()) { + tearDownVideoPlayback(); + } + } + + private void tearDownVideoPlayback() { + getMediaPlayer().stop(); + } + + @Override + protected void onStop() { + super.onStop(); + getSurfaceFrame().removeOnLayoutChangeListener(this); + getMediaPlayer().getVLCVout().detachViews(); + tearDownVideoServiceBind(); + } + + private void tearDownVideoServiceBind() { + unbindService(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isFinishing()) { + tearDownVideoService(); + } + } + + private void tearDownVideoService() { + Intent intent = new Intent(this, VideoService.class); + stopService(intent); + } + + public static boolean supports(String mime_type) { + String type = mime_type.split("/")[0]; + + return "video".equals(type); + } } diff --git a/src/main/java/org/amahi/anywhere/service/VideoService.java b/src/main/java/org/amahi/anywhere/service/VideoService.java index eff692b10..d3cdb0be4 100644 --- a/src/main/java/org/amahi/anywhere/service/VideoService.java +++ b/src/main/java/org/amahi/anywhere/service/VideoService.java @@ -24,6 +24,7 @@ import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.util.Log; import com.squareup.otto.Subscribe; @@ -98,10 +99,10 @@ public void startVideo(ServerShare videoShare, ServerFile videoFile, boolean isS private void setUpVideoPlayback(boolean isSubtitleEnabled) { Media media = new Media(mLibVLC, getVideoUri()); mMediaPlayer.setMedia(media); + media.release(); if (isSubtitleEnabled) { searchSubtitleFile(); } - media.release(); mMediaPlayer.play(); } diff --git a/src/main/java/org/amahi/anywhere/util/VideoSwipeGestures.java b/src/main/java/org/amahi/anywhere/util/VideoSwipeGestures.java new file mode 100644 index 000000000..1988e1c3b --- /dev/null +++ b/src/main/java/org/amahi/anywhere/util/VideoSwipeGestures.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi 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 3 of the License, or + * (at your option) any later version. + * + * Amahi 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 Amahi. If not, see . + */ +package org.amahi.anywhere.util; + +import android.app.Activity; +import android.content.Context; +import android.media.AudioManager; +import android.util.DisplayMetrics; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import org.amahi.anywhere.view.PercentageView; +import org.amahi.anywhere.view.SeekView; + +import java.util.Locale; + +/** + * SwipeGestures Helper class. + * Implements Gesture Detector to control the actions of Swipe Gestures. + */ + +public class VideoSwipeGestures implements View.OnTouchListener { + + private final AudioManager audio; + private float volumePer; + private final int maxVolume; + private Activity activity; + private PercentageView percentageView; + private SeekView seekView; + private float seekDistance = 0; + private SeekControl seekControl; + + + public interface SeekControl { + int getCurrentPosition(); + + void seekTo(int time); + + int getDuration(); + } + + private enum Direction { + LEFT, RIGHT, UP, DOWN, NONE + } + + private GestureDetector gestureDetector; + + public VideoSwipeGestures(Activity activity, SeekControl seekControl, FrameLayout container) { + this.activity = activity; + this.seekControl = seekControl; + this.gestureDetector = new GestureDetector(activity, new CustomGestureDetect()); + this.gestureDetector.setIsLongpressEnabled(false); + percentageView = new PercentageView(container); + container.addView(percentageView.getView()); + seekView = new SeekView(container); + container.addView(seekView.getView()); + audio = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); + int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC); + maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + volumePer = (float) currentVolume / (float) maxVolume; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (gestureDetector != null) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (percentageView.isShowing() || seekView.isShowing()) { + percentageView.hide(); + seekView.hide(); + return true; + } + return false; + } + return gestureDetector.onTouchEvent(event); + } + return false; + } + + private class CustomGestureDetect implements GestureDetector.OnGestureListener { + + private Direction direction = Direction.NONE; + private Direction xPosition; + + @Override + public boolean onDown(MotionEvent e) { + seekDistance = 0; + direction = Direction.NONE; + DisplayMetrics displayMetrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + int width = displayMetrics.widthPixels; + if (e.getX() >= width / 2) { + xPosition = Direction.RIGHT; + } else { + xPosition = Direction.LEFT; + } + 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) { + /* Check if this is the first ACTION_MOVE event in the current touch event + * If true then set the initial direction of the movement + * */ + try { + if (direction == Direction.NONE) { + float diffY = e2.getY() - e1.getY(); + float diffX = e2.getX() - e1.getX(); + if (Math.abs(diffX) > Math.abs(diffY)) { + if (diffX > 0) { + direction = Direction.RIGHT; + } else { + direction = Direction.LEFT; + } + seekView.show(); + } else { + if (xPosition == Direction.LEFT) { + percentageView.setType(PercentageView.VOLUME); + percentageView.setProgress((int) (volumePer * 100)); + } else if (xPosition == Direction.RIGHT) { + percentageView.setType(PercentageView.BRIGHTNESS); + WindowManager.LayoutParams layout = activity.getWindow().getAttributes(); + percentageView.setProgress((int) (layout.screenBrightness * 100)); + } + if (diffY > 0) { + direction = Direction.DOWN; + } else { + direction = Direction.UP; + } + percentageView.show(); + } + } + } catch (NullPointerException ignored) { + } + + switch (direction) { + case UP: + case DOWN: + switch (xPosition) { + case LEFT: + changeVolume(distanceY / 400); + break; + case RIGHT: + changeBrightness(distanceY / 400); + break; + } + break; + case LEFT: + case RIGHT: + seek(-distanceX * 200); + break; + case NONE: + break; + } + return true; + + } + + @Override + public void onLongPress(MotionEvent e) { + + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + return false; + } + + } + + + private void changeBrightness(float distance) { + WindowManager.LayoutParams layout = activity.getWindow().getAttributes(); + layout.screenBrightness += distance; + if (layout.screenBrightness > 1f) { + layout.screenBrightness = 1f; + } else if (layout.screenBrightness < 0f) { + layout.screenBrightness = 0f; + } + percentageView.setProgress((int) (layout.screenBrightness * 100)); + activity.getWindow().setAttributes(layout); + } + + private void changeVolume(float distance) { + float val = volumePer + distance; + if (val > 1f) { + val = 1f; + } else if (val < 0f) { + val = 0f; + } + percentageView.setProgress((int) (val * 100)); + audio.setStreamVolume(AudioManager.STREAM_MUSIC, Math.round(val * maxVolume), 0); + volumePer = val; + + } + + private void seek(float distance) { + seekDistance += distance; + if (seekControl != null && seekView != null) { + float seekValue = seekControl.getCurrentPosition() + distance; + if (seekValue > 0 && seekValue < seekControl.getDuration()) { + seekControl.seekTo((int) seekValue); + String displayText = String.format(Locale.getDefault(), + "%02d:%02d (%02d:%02d)", + (int) Math.abs(seekDistance / 60000), + (int) Math.abs((seekDistance % 60000) / 1000), + (int) (seekValue / 60000), + (int) ((seekValue % 60000) / 1000)); + if (seekDistance > 0) + seekView.setText("+" + displayText); + else + seekView.setText("-" + displayText); + } + } + } + +} diff --git a/src/main/java/org/amahi/anywhere/view/PercentageView.java b/src/main/java/org/amahi/anywhere/view/PercentageView.java new file mode 100644 index 000000000..be6d32c5f --- /dev/null +++ b/src/main/java/org/amahi/anywhere/view/PercentageView.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi 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 3 of the License, or + * (at your option) any later version. + * + * Amahi 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 Amahi. If not, see . + */ +package org.amahi.anywhere.view; + +/* + PercentageView. + Custom view class for displaying volume and brightness controls on screen while using Swipe Gestures. + */ + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.IntDef; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.amahi.anywhere.R; + +import java.util.Locale; + +public class PercentageView { + public static final int VOLUME = 1; + public static final int BRIGHTNESS = 2; + + @IntDef({VOLUME, BRIGHTNESS}) + @interface TYPE { + } + + private ViewHolder viewHolder; + private View view; + + public PercentageView(FrameLayout parentView) { + LayoutInflater inflater = (LayoutInflater) parentView.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.percentage_view, parentView, false); + view.requestLayout(); + viewHolder = new ViewHolder(view); + } + + public View getView() { + return view; + } + + private class ViewHolder { + private ProgressBar progressBar; + private TextView valuePercent; + private ImageView icon; + + ViewHolder(View itemView) { + progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar); + icon = (ImageView) itemView.findViewById(R.id.type_icon); + valuePercent = (TextView) itemView.findViewById(R.id.value_percent); + } + } + + public void setType(@TYPE int type) { + switch (type) { + case VOLUME: + viewHolder.icon.setImageResource(R.drawable.ic_volume_up); + viewHolder.progressBar.getProgressDrawable().setColorFilter( + Color.BLUE, android.graphics.PorterDuff.Mode.SRC_IN); + break; + case BRIGHTNESS: + viewHolder.icon.setImageResource(R.drawable.ic_brightness); + viewHolder.progressBar.getProgressDrawable().setColorFilter( + Color.YELLOW, android.graphics.PorterDuff.Mode.SRC_IN); + break; + } + } + + public void setProgress(int n) { + viewHolder.progressBar.setProgress(n); + viewHolder.valuePercent.setText(String.format(Locale.getDefault(), "%d %s", n, "%")); + } + + public void hide() { + view.setVisibility(View.GONE); + } + + public void show() { + view.setVisibility(View.VISIBLE); + } + + public boolean isShowing() { + return view.getVisibility() == View.VISIBLE; + } +} + diff --git a/src/main/java/org/amahi/anywhere/view/SeekView.java b/src/main/java/org/amahi/anywhere/view/SeekView.java new file mode 100644 index 000000000..3abb5fff7 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/view/SeekView.java @@ -0,0 +1,44 @@ +package org.amahi.anywhere.view; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.amahi.anywhere.R; + + +public class SeekView { + + + private View view; + private TextView textView; + + public SeekView(ViewGroup viewGroup) { + LayoutInflater inflater = (LayoutInflater) viewGroup.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.seek_view, viewGroup, false); + view.requestLayout(); + textView = (TextView) view.findViewById(R.id.seek_value); + } + + public View getView() { + return view; + } + + public void setText(String s) { + textView.setText(s); + } + + public void hide() { + view.setVisibility(View.GONE); + } + + public void show() { + view.setVisibility(View.VISIBLE); + } + + public boolean isShowing() { + return view.getVisibility() == View.VISIBLE; + } +} diff --git a/src/main/res/drawable/ic_brightness.xml b/src/main/res/drawable/ic_brightness.xml new file mode 100644 index 000000000..67118763f --- /dev/null +++ b/src/main/res/drawable/ic_brightness.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/ic_volume_up.xml b/src/main/res/drawable/ic_volume_up.xml new file mode 100644 index 000000000..2ddbcc801 --- /dev/null +++ b/src/main/res/drawable/ic_volume_up.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/percentage_bar.xml b/src/main/res/drawable/percentage_bar.xml new file mode 100644 index 000000000..b005cbb54 --- /dev/null +++ b/src/main/res/drawable/percentage_bar.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/activity_server_file_video.xml b/src/main/res/layout/activity_server_file_video.xml index 4eab7f86b..f6a3956aa 100644 --- a/src/main/res/layout/activity_server_file_video.xml +++ b/src/main/res/layout/activity_server_file_video.xml @@ -63,6 +63,14 @@ android:layout_height="match_parent"/> + + + + + + + + + + + + + + + diff --git a/src/main/res/layout/seek_view.xml b/src/main/res/layout/seek_view.xml new file mode 100644 index 000000000..fc21af5ca --- /dev/null +++ b/src/main/res/layout/seek_view.xml @@ -0,0 +1,18 @@ + + + + + +