diff options
29 files changed, 1565 insertions, 1 deletions
diff --git a/build.gradle b/build.gradle index 99dd1d732..908fa4b8b 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' - implementation 'com.github.rtoshiro.fullscreenvideoview:fullscreenvideoview:1.1.3' + implementation project(':libs:fullscreenvideoview') implementation 'pub.devrel:easypermissions:2.0.0' implementation 'com.wefika:flowlayout:0.4.1' implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5' diff --git a/libs/fullscreenvideoview/.gitignore b/libs/fullscreenvideoview/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/libs/fullscreenvideoview/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libs/fullscreenvideoview/build.gradle b/libs/fullscreenvideoview/build.gradle new file mode 100644 index 000000000..1201cea57 --- /dev/null +++ b/libs/fullscreenvideoview/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + lintOptions { + abortOnError false + } + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 28 + versionCode 11 + versionName "1.1.3" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +//apply from: '../maven_push.gradle'
\ No newline at end of file diff --git a/libs/fullscreenvideoview/gradle.properties b/libs/fullscreenvideoview/gradle.properties new file mode 100644 index 000000000..878a8d0ed --- /dev/null +++ b/libs/fullscreenvideoview/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=FullscreenVideoView Library +POM_ARTIFACT_ID=fullscreenvideoview +POM_PACKAGING=aar
\ No newline at end of file diff --git a/libs/fullscreenvideoview/proguard-rules.pro b/libs/fullscreenvideoview/proguard-rules.pro new file mode 100644 index 000000000..c7928bcb5 --- /dev/null +++ b/libs/fullscreenvideoview/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Applications/ADT/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/libs/fullscreenvideoview/src/main/AndroidManifest.xml b/libs/fullscreenvideoview/src/main/AndroidManifest.xml new file mode 100644 index 000000000..5b5b2ace5 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.github.rtoshiro.view.video"> + +</manifest> diff --git a/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java new file mode 100644 index 000000000..f4113ce48 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoLayout.java @@ -0,0 +1,336 @@ +/** + * Copyright (C) 2016 Toshiro Sugii + * <p/> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.rtoshiro.view.video; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.media.MediaPlayer; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageButton; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import java.util.Locale; + +public class FullscreenVideoLayout extends FullscreenVideoView implements View.OnClickListener, SeekBar.OnSeekBarChangeListener, MediaPlayer.OnPreparedListener { + + /** + * Log cat TAG name + */ + private final static String TAG = "FullscreenVideoLayout"; + + /** + * RelativeLayout that contains all control related views + */ + public View videoControlsView; + + /** + * SeekBar reference (from videoControlsView) + */ + protected SeekBar seekBar; + + /** + * Reference to ImageButton play + */ + protected ImageButton imgplay; + + /** + * Reference to ImageButton fullscreen + */ + protected ImageButton imgfullscreen; + + /** + * Reference to TextView for elapsed time and total time + */ + protected TextView textTotal, textElapsed; + + /** + * Handler and Runnable to keep tracking on elapsed time + */ + protected static final Handler TIME_THREAD = new Handler(); + protected Runnable updateTimeRunnable = new Runnable() { + public void run() { + updateCounter(); + + TIME_THREAD.postDelayed(this, 200); + } + }; + + public FullscreenVideoLayout(Context context) { + super(context); + } + + public FullscreenVideoLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FullscreenVideoLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void release() { + super.release(); + } + + @Override + protected void initObjects() { + super.initObjects(); + + if (this.videoControlsView == null) { + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + this.videoControlsView = inflater.inflate(R.layout.view_videocontrols, this, false); + } + + if (videoControlsView != null) { + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + params.addRule(ALIGN_PARENT_BOTTOM); + addView(videoControlsView, params); + + this.seekBar = this.videoControlsView.findViewById(R.id.vcv_seekbar); + this.imgfullscreen = this.videoControlsView.findViewById(R.id.vcv_img_fullscreen); + this.imgplay = this.videoControlsView.findViewById(R.id.vcv_img_play); + this.textTotal = this.videoControlsView.findViewById(R.id.vcv_txt_total); + this.textElapsed = this.videoControlsView.findViewById(R.id.vcv_txt_elapsed); + } + + if (this.imgplay != null) + this.imgplay.setOnClickListener(this); + if (this.imgfullscreen != null) + this.imgfullscreen.setOnClickListener(this); + if (this.seekBar != null) + this.seekBar.setOnSeekBarChangeListener(this); + + // Start controls invisible. Make it visible when it is prepared + if (this.videoControlsView != null) + this.videoControlsView.setVisibility(View.INVISIBLE); + } + + @Override + protected void releaseObjects() { + super.releaseObjects(); + + if (this.videoControlsView != null) + removeView(this.videoControlsView); + } + + protected void startCounter() { + Log.d(TAG, "startCounter"); + + TIME_THREAD.postDelayed(updateTimeRunnable, 200); + } + + protected void stopCounter() { + Log.d(TAG, "stopCounter"); + + TIME_THREAD.removeCallbacks(updateTimeRunnable); + } + + protected void updateCounter() { + if (this.textElapsed == null) + return; + + int elapsed = getCurrentPosition(); + // getCurrentPosition is a little bit buggy :( + if (elapsed > 0 && elapsed < getDuration()) { + seekBar.setProgress(elapsed); + + elapsed = Math.round(elapsed / 1000.f); + long s = elapsed % 60; + long m = (elapsed / 60) % 60; + long h = (elapsed / (60 * 60)) % 24; + + if (h > 0) + textElapsed.setText(String.format(Locale.US, "%d:%02d:%02d", h, m, s)); + else + textElapsed.setText(String.format(Locale.US, "%02d:%02d", m, s)); + } + } + + @Override + public void onCompletion(MediaPlayer mp) { + Log.d(TAG, "onCompletion"); + + super.onCompletion(mp); + stopCounter(); + updateControls(); + if (currentState != State.ERROR) + updateCounter(); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + boolean result = super.onError(mp, what, extra); + stopCounter(); + updateControls(); + return result; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (getCurrentState() == State.END) { + Log.d(TAG, "onDetachedFromWindow END"); + stopCounter(); + } + } + + @Override + protected void tryToPrepare() { + Log.d(TAG, "tryToPrepare"); + super.tryToPrepare(); + + if (getCurrentState() == State.PREPARED || getCurrentState() == State.STARTED) { + if (textElapsed != null && textTotal != null) { + int total = getDuration(); + if (total > 0) { + seekBar.setMax(total); + seekBar.setProgress(0); + + total = total / 1000; + long s = total % 60; + long m = (total / 60) % 60; + long h = (total / (60 * 60)) % 24; + if (h > 0) { + textElapsed.setText("00:00:00"); + textTotal.setText(String.format(Locale.US, "%d:%02d:%02d", h, m, s)); + } else { + textElapsed.setText("00:00"); + textTotal.setText(String.format(Locale.US, "%02d:%02d", m, s)); + } + } + } + + if (videoControlsView != null) + videoControlsView.setVisibility(View.VISIBLE); + } + } + + @Override + public void start() throws IllegalStateException { + Log.d(TAG, "start"); + + if (!isPlaying()) { + super.start(); + startCounter(); + updateControls(); + } + } + + @Override + public void pause() throws IllegalStateException { + Log.d(TAG, "pause"); + + if (isPlaying()) { + stopCounter(); + super.pause(); + updateControls(); + } + } + + @Override + public void reset() { + Log.d(TAG, "reset"); + + super.reset(); + + stopCounter(); + updateControls(); + } + + @Override + public void stop() throws IllegalStateException { + Log.d(TAG, "stop"); + + super.stop(); + stopCounter(); + updateControls(); + } + + protected void updateControls() { + if (imgplay == null) return; + + Drawable icon; + if (getCurrentState() == State.STARTED) { + icon = context.getResources().getDrawable(R.drawable.fvl_selector_pause); + } else { + icon = context.getResources().getDrawable(R.drawable.fvl_selector_play); + } + imgplay.setBackgroundDrawable(icon); + } + + public void hideControls() { + Log.d(TAG, "hideControls"); + if (videoControlsView != null) { + videoControlsView.setVisibility(View.INVISIBLE); + } + } + + public void showControls() { + Log.d(TAG, "showControls"); + if (videoControlsView != null) { + videoControlsView.setVisibility(View.VISIBLE); + } + } + + /** + * Onclick action + * Controls play button and fullscreen button. + * + * @param v View defined in XML + */ + @Override + public void onClick(View v) { + if (v.getId() == R.id.vcv_img_play) { + if (isPlaying()) { + pause(); + } else { + start(); + } + } else { + setFullscreen(!isFullscreen()); + } + } + + /** + * SeekBar Listener + */ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + Log.d(TAG, "onProgressChanged " + progress); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + stopCounter(); + Log.d(TAG, "onStartTrackingTouch"); + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + int progress = seekBar.getProgress(); + seekTo(progress); + Log.d(TAG, "onStopTrackingTouch"); + + } +} diff --git a/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java new file mode 100644 index 000000000..f068a468b --- /dev/null +++ b/libs/fullscreenvideoview/src/main/java/com/github/rtoshiro/view/video/FullscreenVideoView.java @@ -0,0 +1,1014 @@ +/** + * Copyright (C) 2016 Toshiro Sugii + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.rtoshiro.view.video; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.Color; +import android.graphics.SurfaceTexture; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnBufferingUpdateListener; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnInfoListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.media.MediaPlayer.OnSeekCompleteListener; +import android.media.MediaPlayer.OnVideoSizeChangedListener; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; + +import java.io.IOException; + +/** + * Acts like a android.widget.VideoView with fullscreen functionality + * + * @author rtoshiro + * @version 2015.0527 + * @since 1.0 + */ +@SuppressLint("NewApi") +public class FullscreenVideoView extends RelativeLayout implements SurfaceHolder.Callback, OnPreparedListener, OnErrorListener, OnSeekCompleteListener, OnCompletionListener, OnInfoListener, OnVideoSizeChangedListener, OnBufferingUpdateListener, TextureView.SurfaceTextureListener { + + /** + * Debug Tag for use logging debug output to LogCat + */ + private final static String TAG = "FullscreenVideoView"; + + protected Context context; + protected Activity activity; // Used when orientation changes is not static + + protected MediaPlayer mediaPlayer; + protected SurfaceHolder surfaceHolder; + protected SurfaceView surfaceView; + protected boolean videoIsReady, surfaceIsReady; + protected boolean detachedByFullscreen; + protected State currentState; + protected State lastState; // Tells onSeekCompletion what to do + + protected View onProgressView; + + protected ViewGroup parentView; // Controls fullscreen container + protected ViewGroup.LayoutParams currentLayoutParams; + + protected boolean fullscreen; + protected boolean shouldAutoplay; + protected int initialConfigOrientation; + protected int initialMovieWidth, initialMovieHeight; + protected String videoPath; + protected Uri videoUri; + + protected OnBufferingUpdateListener bufferingUpdateListener; + protected OnCompletionListener completionListener; + protected OnErrorListener errorListener; + protected OnInfoListener infoListener; + protected OnPreparedListener preparedListener; + protected OnSeekCompleteListener seekCompleteListener; + protected OnVideoSizeChangedListener videoSizeChangedListener; + + protected TextureView textureView; + + /** + * States of MediaPlayer + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#StateDiagram">MediaPlayer</a> + */ + public enum State { + IDLE, + INITIALIZED, + PREPARED, + PREPARING, + STARTED, + STOPPED, + PAUSED, + PLAYBACKCOMPLETED, + ERROR, + END + } + + public FullscreenVideoView(Context context) { + super(context); + this.context = context; + + init(); + } + + public FullscreenVideoView(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + + init(); + } + + public FullscreenVideoView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.context = context; + + init(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + Log.d(TAG, "onSizeChanged - w = " + w + " - h: " + h + " - oldw: " + oldw + " - oldh:" + oldw); + super.onSizeChanged(w, h, oldw, oldh); + resize(); + } + + @Override + public Parcelable onSaveInstanceState() { + Log.d(TAG, "onSaveInstanceState"); + Parcelable p = super.onSaveInstanceState(); + return p; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + Log.d(TAG, "onRestoreInstanceState"); + super.onRestoreInstanceState(state); + } + + @Override + protected void onDetachedFromWindow() { + Log.d(TAG, "onDetachedFromWindow - detachedByFullscreen: " + detachedByFullscreen); + + super.onDetachedFromWindow(); + + if (!this.detachedByFullscreen) { + release(); + } + + this.detachedByFullscreen = false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + Log.d(TAG, "onAttachedToWindow"); + + // If Object still exists, reload the video + if (this.mediaPlayer == null && + this.currentState == State.END) { + initObjects(); + try { + if (this.videoPath != null) + setVideoPath(this.videoPath); + else if (this.videoUri != null) + setVideoURI(this.videoUri); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // TextureView + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + Log.d(TAG, "onSurfaceTextureAvailable - state: " + this.currentState); + + Surface surface = new Surface(surfaceTexture); + if (this.mediaPlayer != null) { + this.mediaPlayer.setSurface(surface); + + // If is not prepared yet - tryToPrepare() + if (!this.surfaceIsReady) { + this.surfaceIsReady = true; + if (this.currentState == State.INITIALIZED || + this.currentState == State.PREPARING) + tryToPrepare(); + } + } + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + Log.d(TAG, "onSurfaceTextureSizeChanged - width: " + width + " - height: " + height); + resize(); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + Log.d(TAG, "onSurfaceTextureDestroyed"); + if (mediaPlayer != null && mediaPlayer.isPlaying()) + mediaPlayer.pause(); + + surfaceIsReady = false; + + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + Log.d(TAG, "onSurfaceTextureUpdated"); + } + + // SurfaceView methods + @Override + synchronized public void surfaceCreated(SurfaceHolder holder) { + Log.d(TAG, "surfaceCreated called = " + currentState); + + if (this.mediaPlayer != null) { + this.mediaPlayer.setDisplay(surfaceHolder); + + // If is not prepared yet - tryToPrepare() + if (!this.surfaceIsReady) { + this.surfaceIsReady = true; + if (this.currentState == State.INITIALIZED || + this.currentState == State.PREPARING) + tryToPrepare(); + } + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.d(TAG, "surfaceChanged called"); + resize(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(TAG, "surfaceDestroyed called"); + if (mediaPlayer != null && mediaPlayer.isPlaying()) + mediaPlayer.pause(); + + surfaceIsReady = false; + } + + @Override + synchronized public void onPrepared(MediaPlayer mp) { + Log.d(TAG, "onPrepared called"); + videoIsReady = true; + tryToPrepare(); + } + + /** + * Restore the last State before seekTo() + * + * @param mp the MediaPlayer that issued the seek operation + */ + @Override + public void onSeekComplete(MediaPlayer mp) { + Log.d(TAG, "onSeekComplete"); + + stopLoading(); + if (lastState != null) { + switch (lastState) { + case STARTED: { + start(); + break; + } + case PLAYBACKCOMPLETED: { + currentState = State.PLAYBACKCOMPLETED; + break; + } + case PREPARED: { + currentState = State.PREPARED; + break; + } + } + } + + if (this.seekCompleteListener != null) + this.seekCompleteListener.onSeekComplete(mp); + } + + @Override + public void onCompletion(MediaPlayer mp) { + if (this.mediaPlayer != null) { + if (this.currentState != State.ERROR) { + Log.d(TAG, "onCompletion"); + if (!this.mediaPlayer.isLooping()) + this.currentState = State.PLAYBACKCOMPLETED; + else + start(); + } + } + + if (this.completionListener != null) + this.completionListener.onCompletion(mp); + } + + @Override + public boolean onInfo(MediaPlayer mediaPlayer, int what, int extra) { + Log.d(TAG, "onInfo " + what); + + if (this.infoListener != null) + return this.infoListener.onInfo(mediaPlayer, what, extra); + + return false; + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.d(TAG, "onError called - " + what + " - " + extra); + + stopLoading(); + this.currentState = State.ERROR; + + if (this.errorListener != null) + return this.errorListener.onError(mp, what, extra); + return false; + } + + @Override + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + Log.d(TAG, "onVideoSizeChanged = " + width + " - " + height); + + if (this.initialMovieWidth == -1 && + this.initialMovieHeight == -1 && + width != 0 && + height != 0) { + initialMovieWidth = width; + initialMovieHeight = height; + resize(); + } + + if (this.videoSizeChangedListener != null) + this.videoSizeChangedListener.onVideoSizeChanged(mp, width, height); + } + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { +// Log.d(TAG, "onBufferingUpdate = " + percent); + + if (this.bufferingUpdateListener != null) + this.bufferingUpdateListener.onBufferingUpdate(mp, percent); + } + + /** + * Initializes the default configuration + */ + protected void init() { + Log.d(TAG, "init"); + + if (isInEditMode()) + return; + + this.mediaPlayer = null; + this.shouldAutoplay = false; + this.fullscreen = false; + this.initialConfigOrientation = -1; + this.videoIsReady = false; + this.surfaceIsReady = false; + this.initialMovieHeight = -1; + this.initialMovieWidth = -1; + this.setBackgroundColor(Color.BLACK); + + initObjects(); + } + + /** + * Releases and ends the current Object + */ + protected void release() { + Log.d(TAG, "release"); + releaseObjects(); + + if (this.mediaPlayer != null) { + this.mediaPlayer.setOnBufferingUpdateListener(null); + this.mediaPlayer.setOnPreparedListener(null); + this.mediaPlayer.setOnErrorListener(null); + this.mediaPlayer.setOnSeekCompleteListener(null); + this.mediaPlayer.setOnCompletionListener(null); + this.mediaPlayer.setOnInfoListener(null); + this.mediaPlayer.setOnVideoSizeChangedListener(null); + this.mediaPlayer.release(); + this.mediaPlayer = null; + } + + this.currentState = State.END; + } + + /** + * Initializes all objects FullscreenVideoView depends on + * It does not interfere with configuration properties + * because it is supposed to be called when this Object + * still exists + */ + protected void initObjects() { + Log.d(TAG, "initObjects"); + + if (this.mediaPlayer == null) { + this.mediaPlayer = new MediaPlayer(); + this.mediaPlayer.setOnInfoListener(this); + this.mediaPlayer.setOnErrorListener(this); + this.mediaPlayer.setOnPreparedListener(this); + this.mediaPlayer.setOnCompletionListener(this); + this.mediaPlayer.setOnSeekCompleteListener(this); + this.mediaPlayer.setOnBufferingUpdateListener(this); + this.mediaPlayer.setOnVideoSizeChangedListener(this); + this.mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + } + + RelativeLayout.LayoutParams layoutParams; + View view; + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (this.textureView == null) { + this.textureView = new TextureView(this.context); + this.textureView.setSurfaceTextureListener(this); + } + view = this.textureView; + + } else { + if (this.surfaceView == null) { + this.surfaceView = new SurfaceView(context); + } + view = this.surfaceView; + + if (this.surfaceHolder == null) { + this.surfaceHolder = this.surfaceView.getHolder(); + //noinspection deprecation + this.surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + this.surfaceHolder.addCallback(this); + } + } + + layoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + layoutParams.addRule(CENTER_IN_PARENT); + view.setLayoutParams(layoutParams); + addView(view); + + // Try not reset onProgressView + if (this.onProgressView == null) + this.onProgressView = new ProgressBar(context); + + layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + layoutParams.addRule(CENTER_IN_PARENT); + this.onProgressView.setLayoutParams(layoutParams); + addView(this.onProgressView); + + stopLoading(); + this.currentState = State.IDLE; + } + + /** + * Releases all objects FullscreenVideoView depends on + * It does not interfere with configuration properties + * because it is supposed to be called when this Object + * still exists + */ + protected void releaseObjects() { + Log.d(TAG, "releaseObjects"); + if (this.mediaPlayer != null) { + this.mediaPlayer.setSurface(null); + this.mediaPlayer.reset(); + } + + this.videoIsReady = false; + this.surfaceIsReady = false; + this.initialMovieHeight = -1; + this.initialMovieWidth = -1; + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (this.textureView != null) { + this.textureView.setSurfaceTextureListener(null); + removeView(this.textureView); + this.textureView = null; + } + } else { + if (this.surfaceHolder != null) { + this.surfaceHolder.removeCallback(this); + this.surfaceHolder = null; + } + if (this.surfaceView != null) { + removeView(this.surfaceView); + this.surfaceView = null; + } + } + + if (this.onProgressView != null) { + removeView(this.onProgressView); + } + } + + /** + * Calls prepare() method of MediaPlayer + */ + + protected void prepare() throws IllegalStateException { + Log.d(TAG, "prepare"); + startLoading(); + + this.currentState = State.PREPARING; + this.mediaPlayer.prepareAsync(); + } + + /** + * Try to call state PREPARED + * Only if SurfaceView is already created and MediaPlayer is prepared + * Video is loaded and is ok to play. + */ + protected void tryToPrepare() { + Log.d(TAG, "tryToPrepare"); + if (this.surfaceIsReady && this.videoIsReady) { + if (this.mediaPlayer != null && + this.mediaPlayer.getVideoWidth() != 0 && + this.mediaPlayer.getVideoHeight() != 0) { + this.initialMovieWidth = this.mediaPlayer.getVideoWidth(); + this.initialMovieHeight = this.mediaPlayer.getVideoHeight(); + } + + resize(); + stopLoading(); + currentState = State.PREPARED; + + if (shouldAutoplay) + start(); + + if (this.preparedListener != null) + this.preparedListener.onPrepared(mediaPlayer); + } + } + + protected void startLoading() { + if (this.onProgressView != null) + this.onProgressView.setVisibility(View.VISIBLE); + } + + protected void stopLoading() { + if (this.onProgressView != null) + this.onProgressView.setVisibility(View.GONE); + } + + /** + * Get the current {@link FullscreenVideoView.State}. + * + * @return Current {@link FullscreenVideoView.State} + */ + synchronized public State getCurrentState() { + return currentState; + } + + /** + * Returns if VideoView is in fullscreen mode + * + * @return true if is in fullscreen mode otherwise false + * @since 1.1 + */ + public boolean isFullscreen() { + return fullscreen; + } + + /** + * Turn VideoView fulllscreen mode on or off. + * + * @param fullscreen true to turn on fullscreen mode or false to turn off + * @throws RuntimeException In case of mediaPlayer doesn't exist or illegal state exception + * @since 1.1 + */ + public void setFullscreen(final boolean fullscreen) throws RuntimeException { + + if (mediaPlayer == null) + throw new RuntimeException("Media Player is not initialized"); + + if (this.currentState != State.ERROR) { + if (FullscreenVideoView.this.fullscreen == fullscreen) return; + FullscreenVideoView.this.fullscreen = fullscreen; + + final boolean wasPlaying = mediaPlayer.isPlaying(); + if (wasPlaying) + pause(); + + if (FullscreenVideoView.this.fullscreen) { + if (activity != null) + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + + View rootView = getRootView(); + View v = rootView.findViewById(android.R.id.content); + ViewParent viewParent = getParent(); + if (viewParent instanceof ViewGroup) { + if (parentView == null) + parentView = (ViewGroup) viewParent; + + // Prevents MediaPlayer to became invalidated and released + detachedByFullscreen = true; + + // Saves the last state (LayoutParams) of view to restore after + currentLayoutParams = FullscreenVideoView.this.getLayoutParams(); + + parentView.removeView(FullscreenVideoView.this); + } else + Log.e(TAG, "Parent View is not a ViewGroup"); + + if (v instanceof ViewGroup) { + ((ViewGroup) v).addView(FullscreenVideoView.this); + } else + Log.e(TAG, "RootView is not a ViewGroup"); + } else { + if (activity != null) + activity.setRequestedOrientation(initialConfigOrientation); + + ViewParent viewParent = getParent(); + if (viewParent instanceof ViewGroup) { + // Check if parent view is still available + boolean parentHasParent = false; + if (parentView != null && parentView.getParent() != null) { + parentHasParent = true; + detachedByFullscreen = true; + } + + ((ViewGroup) viewParent).removeView(FullscreenVideoView.this); + if (parentHasParent) { + parentView.addView(FullscreenVideoView.this); + FullscreenVideoView.this.setLayoutParams(currentLayoutParams); + } + } + } + + resize(); + + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + if (wasPlaying && mediaPlayer != null) + start(); + } + }); + } + } + + /** + * Binds an Activity to VideoView. This is necessary to keep tracking on orientation changes + * + * @param activity The activity that VideoView is related to + */ + public void setActivity(Activity activity) { + this.activity = activity; + this.initialConfigOrientation = activity.getRequestedOrientation(); + } + + public void resize() { + if (initialMovieHeight == -1 || initialMovieWidth == -1 || (surfaceView == null && this.textureView == null)) + return; + + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + + View currentParent = (View) getParent(); + if (currentParent != null) { + float videoProportion = (float) initialMovieWidth / (float) initialMovieHeight; + + int screenWidth = currentParent.getWidth(); + int screenHeight = currentParent.getHeight(); + float screenProportion = (float) screenWidth / (float) screenHeight; + + int newWidth, newHeight; + if (videoProportion > screenProportion) { + newWidth = screenWidth; + newHeight = (int) ((float) screenWidth / videoProportion); + } else { + newWidth = (int) (videoProportion * (float) screenHeight); + newHeight = screenHeight; + } + + View currentView = null; + RelativeLayout.LayoutParams lp; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + currentView = textureView; + } else { + currentView = surfaceView; + } + + if (currentView != null) { + lp = (RelativeLayout.LayoutParams) currentView.getLayoutParams(); + lp.addRule(CENTER_IN_PARENT); + lp.width = newWidth; + lp.height = newHeight; + currentView.setLayoutParams(lp); + } + + Log.d(TAG, "Resizing: initialMovieWidth: " + initialMovieWidth + " - initialMovieHeight: " + initialMovieHeight); + Log.d(TAG, "Resizing: screenWidth: " + screenWidth + " - screenHeight: " + screenHeight); + Log.d(TAG, "Resizing To: newWidth: " + newWidth + " - newHeight: " + newHeight); + } + } + }); + } + + /** + * Tells if application should autoplay videos as soon as it is prepared + * + * @return true if application are going to play videos as soon as it is prepared + */ + public boolean isShouldAutoplay() { + return shouldAutoplay; + } + + /** + * Tells application that it should begin playing as soon as buffering + * is ok + * + * @param shouldAutoplay If true, call start() method when getCurrentState() == PREPARED. Default is false. + */ + public void setShouldAutoplay(boolean shouldAutoplay) { + this.shouldAutoplay = shouldAutoplay; + } + + /** + * Toggles view to fullscreen mode + * It saves currentState and calls pause() method. + * When fullscreen is finished, it calls the saved currentState before pause() + * In practice, it only affects STARTED state. + * If currenteState was STARTED when fullscreen() is called, it calls start() method + * after fullscreen() has ended. + * + * @deprecated As of release 1.1.0, replaced by {@link #setFullscreen(boolean)} + */ + @Deprecated + public void fullscreen() throws IllegalStateException { + setFullscreen(!fullscreen); + } + + /** + * MediaPlayer method (getCurrentPosition) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getCurrentPosition%28%29">getCurrentPosition</a> + */ + public int getCurrentPosition() { + if (mediaPlayer != null) + return mediaPlayer.getCurrentPosition(); + else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (getDuration) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getDuration%28%29">getDuration</a> + */ + public int getDuration() { + if (mediaPlayer != null) + return mediaPlayer.getDuration(); + else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (getVideoHeight) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getVideoHeight%28%29">getVideoHeight</a> + */ + public int getVideoHeight() { + if (mediaPlayer != null) + return mediaPlayer.getVideoHeight(); + else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (getVideoWidth) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#getVideoWidth%28%29">getVideoWidth</a> + */ + public int getVideoWidth() { + if (mediaPlayer != null) + return mediaPlayer.getVideoWidth(); + else throw new RuntimeException("Media Player is not initialized"); + } + + + /** + * MediaPlayer method (isLooping) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#isLooping%28%29">isLooping</a> + */ + public boolean isLooping() { + if (mediaPlayer != null) + return mediaPlayer.isLooping(); + else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (isPlaying) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#isPlaying%28%29">isPlaying</a> + */ + public boolean isPlaying() throws IllegalStateException { + if (mediaPlayer != null) + return mediaPlayer.isPlaying(); + else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (pause) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#pause%28%29">pause</a> + */ + public void pause() throws IllegalStateException { + Log.d(TAG, "pause"); + if (mediaPlayer != null) { + currentState = State.PAUSED; + mediaPlayer.pause(); + } else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * Due to a lack of access of SurfaceView, it rebuilds mediaPlayer and all + * views to update SurfaceView canvas + */ + public void reset() { + Log.d(TAG, "reset"); + + releaseObjects(); + initObjects(); + } + + /** + * MediaPlayer method (start) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#start%28%29">start</a> + */ + public void start() throws IllegalStateException { + Log.d(TAG, "start"); + + if (mediaPlayer != null) { + currentState = State.STARTED; + mediaPlayer.setOnCompletionListener(this); + mediaPlayer.start(); + } else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (stop) + * + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#stop%28%29">stop</a> + */ + public void stop() throws IllegalStateException { + Log.d(TAG, "stop"); + + if (mediaPlayer != null) { + currentState = State.STOPPED; + mediaPlayer.stop(); + } else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * MediaPlayer method (seekTo) + * It calls pause() method before calling MediaPlayer.seekTo() + * + * @param msec the offset in milliseconds from the start to seek to + * @throws IllegalStateException if the internal player engine has not been initialized + * @see <a href="http://developer.android.com/reference/android/media/MediaPlayer.html#seekTo%28%29">seekTo</a> + */ + public void seekTo(int msec) throws IllegalStateException { + Log.d(TAG, "seekTo = " + msec); + + if (mediaPlayer != null) { + // No live streaming + if (mediaPlayer.getDuration() > -1 && msec <= mediaPlayer.getDuration()) { + lastState = currentState; + pause(); + mediaPlayer.seekTo(msec); + + startLoading(); + } + } else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnCompletionListener(OnCompletionListener l) { + if (mediaPlayer != null) + this.completionListener = l; + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnErrorListener(OnErrorListener l) { + if (mediaPlayer != null) + errorListener = l; + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnBufferingUpdateListener(OnBufferingUpdateListener l) { + if (mediaPlayer != null) { + this.bufferingUpdateListener = l; + this.mediaPlayer.setOnBufferingUpdateListener(this); + } else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnInfoListener(OnInfoListener l) { + if (mediaPlayer != null) + this.infoListener = l; + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnSeekCompleteListener(OnSeekCompleteListener l) { + if (mediaPlayer != null) + this.seekCompleteListener = l; + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener l) { + if (mediaPlayer != null) + this.videoSizeChangedListener = l; + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setOnPreparedListener(OnPreparedListener l) { + if (mediaPlayer != null) + this.preparedListener = l; + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setLooping(boolean looping) { + if (mediaPlayer != null) + mediaPlayer.setLooping(looping); + else throw new RuntimeException("Media Player is not initialized"); + } + + public void setVolume(float leftVolume, float rightVolume) { + if (mediaPlayer != null) + mediaPlayer.setVolume(leftVolume, rightVolume); + else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * VideoView method (setVideoPath) + */ + public void setVideoPath(String path) throws IOException, IllegalStateException, SecurityException, IllegalArgumentException, RuntimeException { + Log.d(TAG, "setVideoPath"); + if (mediaPlayer != null) { + if (currentState != State.IDLE) + throw new IllegalStateException("FullscreenVideoView Invalid State: " + currentState); + + this.videoPath = path; + this.videoUri = null; + this.mediaPlayer.setDataSource(path); + + this.currentState = State.INITIALIZED; + prepare(); + } else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * VideoView method (setVideoURI) + */ + public void setVideoURI(Uri uri) throws IOException, IllegalStateException, SecurityException, IllegalArgumentException, RuntimeException { + Log.d(TAG, "setVideoURI"); + if (mediaPlayer != null) { + if (currentState != State.IDLE) + throw new IllegalStateException("FullscreenVideoView Invalid State: " + currentState); + + this.videoUri = uri; + this.videoPath = null; + this.mediaPlayer.setDataSource(context, uri); + + this.currentState = State.INITIALIZED; + prepare(); + } else throw new RuntimeException("Media Player is not initialized"); + } + + /** + * Overwrite the default ProgressView to represent loading progress state + * It is controlled by stopLoading and startLoading methods, that only sets it to VISIBLE and GONE + * <p> + * Remember to set RelativeLayout.LayoutParams before setting the view. + * + * @param v The custom View that will be used as progress view. + * Set it to null to remove the default one + */ + public void setOnProgressView(View v) { + int progressViewVisibility = -1; + if (this.onProgressView != null) { + progressViewVisibility = this.onProgressView.getVisibility(); + removeView(this.onProgressView); + } + + this.onProgressView = v; + if (this.onProgressView != null) { + addView(this.onProgressView); + if (progressViewVisibility != -1) + this.onProgressView.setVisibility(progressViewVisibility); + } + } +} diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_disabled.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_disabled.png Binary files differnew file mode 100644 index 000000000..df0552fdc --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_disabled.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_focused.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_focused.png Binary files differnew file mode 100644 index 000000000..59ebe18c7 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_focused.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_normal.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_normal.png Binary files differnew file mode 100644 index 000000000..f9c26fc5b --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_normal.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_pressed.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_pressed.png Binary files differnew file mode 100644 index 000000000..239ffc541 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_control_pressed.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_fullscreen_reader.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_fullscreen_reader.png Binary files differnew file mode 100644 index 000000000..2e9f6c3d0 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_fullscreen_reader.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_fullscreen_reader_white.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_fullscreen_reader_white.png Binary files differnew file mode 100644 index 000000000..969056d24 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_fullscreen_reader_white.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_pause_reader.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_pause_reader.png Binary files differnew file mode 100644 index 000000000..c508211bf --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_pause_reader.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_pause_reader_white.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_pause_reader_white.png Binary files differnew file mode 100644 index 000000000..2c386c24d --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_pause_reader_white.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_play_reader.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_play_reader.png Binary files differnew file mode 100644 index 000000000..9df16013b --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_play_reader.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_play_reader_white.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_play_reader_white.png Binary files differnew file mode 100644 index 000000000..b5bc9a96c --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_play_reader_white.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_primary.9.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_primary.9.png Binary files differnew file mode 100644 index 000000000..ed4171531 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_primary.9.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_secondary.9.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_secondary.9.png Binary files differnew file mode 100644 index 000000000..0f2de4d95 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_secondary.9.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_track.9.png b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_track.9.png Binary files differnew file mode 100644 index 000000000..4014860d6 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable-xxhdpi/fvl_track.9.png diff --git a/libs/fullscreenvideoview/src/main/res/drawable/fvl_progress.xml b/libs/fullscreenvideoview/src/main/res/drawable/fvl_progress.xml new file mode 100644 index 000000000..0a8173737 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable/fvl_progress.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@android:id/background" android:drawable="@drawable/fvl_track" /> + <item android:id="@android:id/secondaryProgress"> + <scale android:scaleWidth="100%" + android:drawable="@drawable/fvl_secondary" /> + </item> + <item android:id="@android:id/progress"> + <scale android:scaleWidth="100%" + android:drawable="@drawable/fvl_primary" /> + </item> +</layer-list> diff --git a/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_fullscreen.xml b/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_fullscreen.xml new file mode 100644 index 000000000..9cce2d136 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_fullscreen.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/fvl_fullscreen_reader" android:state_pressed="false" /> + <item android:drawable="@drawable/fvl_fullscreen_reader_white" android:state_pressed="true" /> +</selector>
\ No newline at end of file diff --git a/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_pause.xml b/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_pause.xml new file mode 100644 index 000000000..ed0d8a9a6 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_pause.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/fvl_pause_reader" android:state_pressed="false" /> + <item android:drawable="@drawable/fvl_pause_reader_white" android:state_pressed="true" /> +</selector>
\ No newline at end of file diff --git a/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_play.xml b/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_play.xml new file mode 100644 index 000000000..9511e6538 --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/drawable/fvl_selector_play.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/fvl_play_reader" android:state_pressed="false" /> + <item android:drawable="@drawable/fvl_play_reader_white" android:state_pressed="true" /> +</selector>
\ No newline at end of file diff --git a/libs/fullscreenvideoview/src/main/res/layout/view_videocontrols.xml b/libs/fullscreenvideoview/src/main/res/layout/view_videocontrols.xml new file mode 100644 index 000000000..8c8b53edf --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/layout/view_videocontrols.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + android:id="@+id/rel_videocontrols" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="0dp" + android:background="#80cccccc"> + + <ImageButton + android:id="@+id/vcv_img_play" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_centerVertical="true" + android:layout_marginStart="0dp" + android:layout_marginLeft="0dp" + android:layout_marginTop="0dp" + android:layout_marginEnd="0dp" + android:layout_marginRight="0dp" + android:layout_marginBottom="0dp" + android:background="@drawable/fvl_selector_play" /> + + <TextView + android:id="@+id/vcv_txt_elapsed" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/vcv_img_play" + android:text="00:00" + android:textColor="@android:color/black" /> + + <ImageButton + android:id="@+id/vcv_img_fullscreen" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginStart="0dp" + android:layout_marginLeft="0dp" + android:layout_marginTop="0dp" + android:layout_marginEnd="0dp" + android:layout_marginRight="0dp" + android:layout_marginBottom="0dp" + android:background="@drawable/fvl_selector_fullscreen" /> + + <TextView + android:id="@+id/vcv_txt_total" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginRight="5dp" + android:layout_toLeftOf="@+id/vcv_img_fullscreen" + android:text="00:00" + android:textColor="@android:color/black" /> + + <SeekBar + android:id="@+id/vcv_seekbar" + style="@android:style/Widget.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_margin="2dp" + android:layout_marginStart="5dp" + android:layout_marginLeft="5dp" + android:layout_marginTop="5dp" + android:layout_marginEnd="5dp" + android:layout_marginRight="5dp" + android:layout_marginBottom="5dp" + android:layout_toLeftOf="@+id/vcv_txt_total" + android:layout_toRightOf="@+id/vcv_txt_elapsed" + android:indeterminateDrawable="@drawable/fvl_progress" + android:maxHeight="13dp" + android:minHeight="13dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:progressDrawable="@drawable/fvl_progress" + android:thumb="@drawable/fvl_control_normal" + android:thumbOffset="16dp" /> + + +</RelativeLayout>
\ No newline at end of file diff --git a/libs/fullscreenvideoview/src/main/res/values/strings.xml b/libs/fullscreenvideoview/src/main/res/values/strings.xml new file mode 100644 index 000000000..92951c27a --- /dev/null +++ b/libs/fullscreenvideoview/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ +<resources> + <string name="app_name">VideoLayout</string> + + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> +</resources> diff --git a/settings.gradle b/settings.gradle index 333bfa6bc..591d3e182 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ include ':libs:android-transcoder' include ':libs:xmpp-addr' +include ':libs:fullscreenvideoview' rootProject.name = 'PixArtMessenger' diff --git a/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java b/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java index a51ef21ab..f9e5b1c2f 100644 --- a/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java +++ b/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java @@ -10,13 +10,17 @@ import android.content.res.Configuration; import android.databinding.DataBindingUtil; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.PointF; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.preference.PreferenceManager; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.webkit.MimeTypeMap; @@ -52,6 +56,7 @@ public class MediaViewerActivity extends XmppActivity { boolean isImage = false; boolean isVideo = false; private ActivityMediaViewerBinding binding; + private GestureDetector gestureDetector; public static String getMimeType(String path) { try { @@ -73,6 +78,15 @@ public class MediaViewerActivity extends XmppActivity { this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_viewer); this.mTheme = findTheme(); setTheme(this.mTheme); + gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(MotionEvent e) { + showFab(); + return super.onDown(e); + } + + + }); ActionBar actionBar = getSupportActionBar(); if (actionBar != null && actionBar.isShowing()) { @@ -110,6 +124,7 @@ public class MediaViewerActivity extends XmppActivity { } return false; }); + showFab(); } private void share() { @@ -226,9 +241,11 @@ public class MediaViewerActivity extends XmppActivity { if (gif) { binding.messageGifView.setVisibility(View.VISIBLE); binding.messageGifView.setImageURI(FileUri); + binding.messageGifView.setOnTouchListener((view, motionEvent) -> gestureDetector.onTouchEvent(motionEvent)); } else { binding.messageImageView.setVisibility(View.VISIBLE); binding.messageImageView.setImage(ImageSource.uri(FileUri)); + binding.messageImageView.setOnTouchListener((view, motionEvent) -> gestureDetector.onTouchEvent(motionEvent)); } } catch (Exception e) { Toast.makeText(this, getString(R.string.error_file_corrupt), Toast.LENGTH_LONG).show(); @@ -262,6 +279,7 @@ public class MediaViewerActivity extends XmppActivity { binding.messageVideoView.setVideoURI(uri); mFullscreenbutton.setVisibility(View.INVISIBLE); binding.messageVideoView.setShouldAutoplay(true); + binding.messageVideoView.setOnTouchListener((view, motionEvent) -> gestureDetector.onTouchEvent(motionEvent)); } catch (IOException e) { Toast.makeText(this, getString(R.string.error_file_corrupt), Toast.LENGTH_LONG).show(); @@ -356,4 +374,17 @@ public class MediaViewerActivity extends XmppActivity { public SharedPreferences getPreferences() { return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); } + + private void showFab() { + binding.speedDial.show(); + hideFab(); + } + + private void hideFab() { + new Handler().postDelayed(() -> { + binding.speedDial.hide(); + }, 3000); + } + + }
\ No newline at end of file |