summaryrefslogtreecommitdiff
path: root/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java
diff options
context:
space:
mode:
Diffstat (limited to 'library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java')
-rw-r--r--library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java467
1 files changed, 256 insertions, 211 deletions
diff --git a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java
index 53149ea..375ee18 100644
--- a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java
+++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java
@@ -22,18 +22,23 @@ import android.content.res.TypedArray;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.Animatable;
import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnInfoListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.MediaPlayer.OnSeekCompleteListener;
+import android.net.Uri;
import android.os.Build.VERSION_CODES;
+import androidx.annotation.Nullable;
+import androidx.annotation.RawRes;
+import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RawRes;
-import androidx.annotation.VisibleForTesting;
-
import com.android.setupwizardlib.R;
+import java.io.IOException;
/**
* A view for displaying videos in a continuous loop (without audio). This is typically used for
@@ -44,255 +49,295 @@ import com.android.setupwizardlib.R;
* should loop back to
*
* <p>For optimal file size, use avconv or other video compression tool to remove the unused audio
- * track and reduce the size of your video asset:
- * avconv -i [input file] -vcodec h264 -crf 20 -an [output_file]
+ * track and reduce the size of your video asset: avconv -i [input file] -vcodec h264 -crf 20 -an
+ * [output_file]
*/
@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH)
-public class IllustrationVideoView extends TextureView implements Animatable,
- TextureView.SurfaceTextureListener,
- MediaPlayer.OnPreparedListener,
- MediaPlayer.OnSeekCompleteListener,
- MediaPlayer.OnInfoListener {
+public class IllustrationVideoView extends TextureView
+ implements Animatable,
+ SurfaceTextureListener,
+ OnPreparedListener,
+ OnSeekCompleteListener,
+ OnInfoListener,
+ OnErrorListener {
- private static final String TAG = "IllustrationVideoView";
+ private static final String TAG = "IllustrationVideoView";
- protected float mAspectRatio = 1.0f; // initial guess until we know
+ protected float mAspectRatio = 1.0f; // initial guess until we know
- @Nullable // Can be null when media player fails to initialize
- protected MediaPlayer mMediaPlayer;
+ @Nullable // Can be null when media player fails to initialize
+ protected MediaPlayer mMediaPlayer;
- private @RawRes int mVideoResId = 0;
+ private @RawRes int videoResId = 0;
- @VisibleForTesting Surface mSurface;
+ private String videoResPackageName;
- protected int mWindowVisibility;
+ @VisibleForTesting Surface surface;
- public IllustrationVideoView(Context context, AttributeSet attrs) {
- super(context, attrs);
- final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.SuwIllustrationVideoView);
- mVideoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0);
- a.recycle();
+ private boolean prepared;
- // By default the video scales without interpolation, resulting in jagged edges in the
- // video. This works around it by making the view go through scaling, which will apply
- // anti-aliasing effects.
- setScaleX(0.9999999f);
- setScaleX(0.9999999f);
+ public IllustrationVideoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ final TypedArray a =
+ context.obtainStyledAttributes(attrs, R.styleable.SuwIllustrationVideoView);
+ final int videoResId = a.getResourceId(R.styleable.SuwIllustrationVideoView_suwVideo, 0);
+ a.recycle();
+ setVideoResource(videoResId);
- setSurfaceTextureListener(this);
- }
+ // By default the video scales without interpolation, resulting in jagged edges in the
+ // video. This works around it by making the view go through scaling, which will apply
+ // anti-aliasing effects.
+ setScaleX(0.9999999f);
+ setScaleX(0.9999999f);
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
-
- if (height < width * mAspectRatio) {
- // Height constraint is tighter. Need to scale down the width to fit aspect ratio.
- width = (int) (height / mAspectRatio);
- } else {
- // Width constraint is tighter. Need to scale down the height to fit aspect ratio.
- height = (int) (width * mAspectRatio);
- }
-
- super.onMeasure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- }
+ setSurfaceTextureListener(this);
+ }
- /**
- * Set the video to be played by this view.
- *
- * @param resId Resource ID of the video, typically an MP4 under res/raw.
- */
- public void setVideoResource(@RawRes int resId) {
- if (resId != mVideoResId) {
- mVideoResId = resId;
- createMediaPlayer();
- }
- }
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (hasWindowFocus) {
- start();
- } else {
- stop();
- }
+ if (height < width * mAspectRatio) {
+ // Height constraint is tighter. Need to scale down the width to fit aspect ratio.
+ width = (int) (height / mAspectRatio);
+ } else {
+ // Width constraint is tighter. Need to scale down the height to fit aspect ratio.
+ height = (int) (width * mAspectRatio);
}
- /**
- * Creates a media player for the current URI. The media player will be started immediately if
- * the view's window is visible. If there is an existing media player, it will be released.
- */
- protected void createMediaPlayer() {
- if (mMediaPlayer != null) {
- mMediaPlayer.release();
- }
- if (mSurface == null || mVideoResId == 0) {
- return;
- }
-
- mMediaPlayer = MediaPlayer.create(getContext(), mVideoResId);
-
- if (mMediaPlayer != null) {
- mMediaPlayer.setSurface(mSurface);
- mMediaPlayer.setOnPreparedListener(this);
- mMediaPlayer.setOnSeekCompleteListener(this);
- mMediaPlayer.setOnInfoListener(this);
-
- float aspectRatio =
- (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth();
- if (mAspectRatio != aspectRatio) {
- mAspectRatio = aspectRatio;
- requestLayout();
- }
- } else {
- Log.wtf(TAG, "Unable to initialize media player for video view");
- }
- if (mWindowVisibility == View.VISIBLE) {
- start();
- }
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ }
+
+ /**
+ * Set the video and video package name to be played by this view.
+ *
+ * @param videoResId Resource ID of the video, typically an MP4 under res/raw.
+ * @param videoResPackageName The package name of videoResId.
+ */
+ public void setVideoResource(@RawRes int videoResId, String videoResPackageName) {
+ if (videoResId != this.videoResId
+ || (videoResPackageName != null && !videoResPackageName.equals(this.videoResPackageName))) {
+ this.videoResId = videoResId;
+ this.videoResPackageName = videoResPackageName;
+ createMediaPlayer();
}
-
- protected void createSurface() {
- if (mSurface != null) {
- mSurface.release();
- mSurface = null;
- }
- // Reattach only if it has been previously released
- SurfaceTexture surfaceTexture = getSurfaceTexture();
- if (surfaceTexture != null) {
- setVisibility(View.INVISIBLE);
- mSurface = new Surface(surfaceTexture);
- }
+ }
+
+ /**
+ * Set the video to be played by this view.
+ *
+ * @param resId Resource ID of the video, typically an MP4 under res/raw.
+ */
+ public void setVideoResource(@RawRes int resId) {
+ setVideoResource(resId, getContext().getPackageName());
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ start();
+ } else {
+ stop();
}
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- mWindowVisibility = visibility;
- if (visibility == View.VISIBLE) {
- reattach();
- } else {
- release();
- }
+ }
+
+ /**
+ * Creates a media player for the current URI. The media player will be started immediately if the
+ * view's window is visible. If there is an existing media player, it will be released.
+ */
+ protected void createMediaPlayer() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
}
-
- /**
- * Whether the media player should play the video in a continuous loop. The default value is
- * true.
- */
- protected boolean shouldLoop() {
- return true;
+ if (surface == null || videoResId == 0) {
+ return;
}
- /**
- * Release any resources used by this view. This is automatically called in
- * onSurfaceTextureDestroyed so in most cases you don't have to call this.
- */
- public void release() {
- if (mMediaPlayer != null) {
- mMediaPlayer.stop();
- mMediaPlayer.release();
- mMediaPlayer = null;
- }
- if (mSurface != null) {
- mSurface.release();
- mSurface = null;
- }
+ mMediaPlayer = new MediaPlayer();
+
+ mMediaPlayer.setSurface(surface);
+ mMediaPlayer.setOnPreparedListener(this);
+ mMediaPlayer.setOnSeekCompleteListener(this);
+ mMediaPlayer.setOnInfoListener(this);
+ mMediaPlayer.setOnErrorListener(this);
+
+ setVideoResourceInternal(videoResId, videoResPackageName);
+ }
+
+ private void setVideoResourceInternal(@RawRes int videoRes, String videoResPackageName) {
+ Uri uri = Uri.parse("android.resource://" + videoResPackageName + "/" + videoRes);
+ try {
+ mMediaPlayer.setDataSource(getContext(), uri, null);
+ mMediaPlayer.prepareAsync();
+ } catch (IOException e) {
+ Log.wtf(TAG, "Unable to set data source", e);
}
+ }
- private void reattach() {
- if (mSurface == null) {
- initVideo();
- }
+ protected void createSurface() {
+ if (surface != null) {
+ surface.release();
+ surface = null;
+ }
+ // Reattach only if it has been previously released
+ SurfaceTexture surfaceTexture = getSurfaceTexture();
+ if (surfaceTexture != null) {
+ setVisibility(View.INVISIBLE);
+ surface = new Surface(surfaceTexture);
}
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility == View.VISIBLE) {
+ reattach();
+ } else {
+ release();
+ }
+ }
+
+ /**
+ * Whether the media player should play the video in a continuous loop. The default value is true.
+ */
+ protected boolean shouldLoop() {
+ return true;
+ }
+
+ /**
+ * Release any resources used by this view. This is automatically called in
+ * onSurfaceTextureDestroyed so in most cases you don't have to call this.
+ */
+ public void release() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ prepared = false;
+ }
+ if (surface != null) {
+ surface.release();
+ surface = null;
+ }
+ }
- private void initVideo() {
- if (mWindowVisibility != View.VISIBLE) {
- return;
- }
- createSurface();
- if (mSurface != null) {
- createMediaPlayer();
- } else {
- Log.w("IllustrationVideoView", "Surface creation failed");
- }
+ private void reattach() {
+ if (surface == null) {
+ initVideo();
}
+ }
- protected void onRenderingStart() {
+ private void initVideo() {
+ if (getWindowVisibility() != View.VISIBLE) {
+ return;
}
+ createSurface();
+ if (surface != null) {
+ createMediaPlayer();
+ } else {
+ Log.w(TAG, "Surface creation failed");
+ }
+ }
- /* SurfaceTextureListener methods */
+ protected void onRenderingStart() {}
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
- // Keep the view hidden until video starts
- setVisibility(View.INVISIBLE);
- initVideo();
- }
+ /* SurfaceTextureListener methods */
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
- }
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ // Keep the view hidden until video starts
+ setVisibility(View.INVISIBLE);
+ initVideo();
+ }
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
- release();
- return true;
- }
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {}
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
- }
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ release();
+ return true;
+ }
- /* Animatable methods */
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
- @Override
- public void start() {
- if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
- mMediaPlayer.start();
- }
- }
+ /* Animatable methods */
- @Override
- public void stop() {
- if (mMediaPlayer != null) {
- mMediaPlayer.pause();
- }
+ @Override
+ public void start() {
+ if (prepared && mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
+ mMediaPlayer.start();
}
+ }
- @Override
- public boolean isRunning() {
- return mMediaPlayer != null && mMediaPlayer.isPlaying();
+ @Override
+ public void stop() {
+ if (prepared && mMediaPlayer != null) {
+ mMediaPlayer.pause();
}
+ }
- /* MediaPlayer callbacks */
+ @Override
+ public boolean isRunning() {
+ return mMediaPlayer != null && mMediaPlayer.isPlaying();
+ }
- @Override
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
- // Video available, show view now
- setVisibility(View.VISIBLE);
- onRenderingStart();
- }
- return false;
- }
+ /* MediaPlayer callbacks */
- @Override
- public void onPrepared(MediaPlayer mp) {
- mp.setLooping(shouldLoop());
+ @Override
+ public boolean onInfo(MediaPlayer mp, int what, int extra) {
+ if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
+ // Video available, show view now
+ setVisibility(View.VISIBLE);
+ onRenderingStart();
}
-
- @Override
- public void onSeekComplete(MediaPlayer mp) {
- mp.start();
+ return false;
+ }
+
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ prepared = true;
+ mp.setLooping(shouldLoop());
+
+ float aspectRatio = 0.0f;
+ if (mp.getVideoWidth() > 0 && mp.getVideoHeight() > 0) {
+ aspectRatio = (float) mp.getVideoHeight() / mp.getVideoWidth();
+ } else {
+ Log.w(TAG, "Unexpected video size=" + mp.getVideoWidth() + "x" + mp.getVideoHeight());
}
-
- public int getCurrentPosition() {
- return mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition();
+ if (Float.compare(mAspectRatio, aspectRatio) != 0) {
+ mAspectRatio = aspectRatio;
+ requestLayout();
+ }
+ if (getWindowVisibility() == View.VISIBLE) {
+ start();
}
+ }
+
+ @Override
+ public void onSeekComplete(MediaPlayer mp) {
+ if (isPrepared()) {
+ mp.start();
+ } else {
+ Log.wtf(TAG, "Seek complete but media player not prepared");
+ }
+ }
+
+ public int getCurrentPosition() {
+ return mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition();
+ }
+
+ protected boolean isPrepared() {
+ return prepared;
+ }
+
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
+ Log.w(TAG, "MediaPlayer error. what=" + what + " extra=" + extra);
+ return false;
+ }
}