diff options
author | Maurice Lam <yukl@google.com> | 2017-05-12 18:05:25 -0700 |
---|---|---|
committer | Maurice Lam <yukl@google.com> | 2017-05-17 17:05:16 +0000 |
commit | 9395f90b26e072b2748db315aaffef27c257a82d (patch) | |
tree | b0d16ef5339ce8391be36e7be922555dddb68b3d /library/main | |
parent | 2da78450d5e9723ca93fa39bfdc3f8dd27b41e89 (diff) | |
download | setupwizard-9395f90b26e072b2748db315aaffef27c257a82d.tar.gz |
Add IllustrationVideoView to setup wizard library
- Upstreamed setup wizard's IllustrationVideoView to SuwLib
- Modified the view to automatically listen to window focus changes
to play and pause the video
- The view will now release the MediaPlayer as well in
onSurfaceTextureDestroyed
- Added checkstyle_suppression XML so it will ignore the
"__constructor__" magic method name from the style check.
Bug: 36584499
Test: ./gradlew connectedAndroidTest test
Change-Id: Id045467d5d544a5f54464a0c938d3d56e758e455
Diffstat (limited to 'library/main')
-rw-r--r-- | library/main/res/values/attrs.xml | 4 | ||||
-rw-r--r-- | library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java | 230 |
2 files changed, 234 insertions, 0 deletions
diff --git a/library/main/res/values/attrs.xml b/library/main/res/values/attrs.xml index ec6489e..36d5fb7 100644 --- a/library/main/res/values/attrs.xml +++ b/library/main/res/values/attrs.xml @@ -93,6 +93,10 @@ <attr name="suwHeader" /> </declare-styleable> + <declare-styleable name="SuwIllustrationVideoView"> + <attr name="suwVideo" format="reference" /> + </declare-styleable> + <declare-styleable name="SuwGlifLayout"> <attr name="suwBackgroundPatterned" format="boolean" /> <attr name="suwBackgroundBaseColor" format="color" /> diff --git a/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java new file mode 100644 index 0000000..989f3e6 --- /dev/null +++ b/library/main/src/com/android/setupwizardlib/view/IllustrationVideoView.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2017 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. + */ + +package com.android.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.SurfaceTexture; +import android.graphics.drawable.Animatable; +import android.media.MediaPlayer; +import android.os.Build.VERSION_CODES; +import android.support.annotation.RawRes; +import android.support.annotation.VisibleForTesting; +import android.util.AttributeSet; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; + +import com.android.setupwizardlib.R; + +/** + * A view for displaying videos in a continuous loop (without audio). This is typically used for + * animated illustrations. + * + * <p>The video can be specified using {@code app:suwVideo}, specifying the raw resource to the mp4 + * video. Optionally, {@code app:suwLoopStartMs} can be used to specify which part of the video it + * 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] + */ +@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) +public class IllustrationVideoView extends TextureView implements Animatable, + TextureView.SurfaceTextureListener, + MediaPlayer.OnPreparedListener, + MediaPlayer.OnSeekCompleteListener, + MediaPlayer.OnInfoListener { + + protected float mAspectRatio = 1.0f; // initial guess until we know + + @VisibleForTesting MediaPlayer mMediaPlayer; + + private @RawRes int mVideoResId = 0; + + @VisibleForTesting Surface mSurface; + + 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(); + + setSurfaceTextureListener(this); + } + + @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)); + } + + /** + * 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 + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + start(); + } else { + stop(); + } + } + + /** + * 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. + */ + private void createMediaPlayer() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + } + if (mSurface == null || mVideoResId == 0) { + return; + } + + mMediaPlayer = MediaPlayer.create(getContext(), mVideoResId); + + 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(); + } + if (getWindowVisibility() == View.VISIBLE) { + start(); + } + } + + /** + * 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.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + } + + /* SurfaceTextureListener methods */ + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + // Keep the view hidden until video starts + setVisibility(View.INVISIBLE); + mSurface = new Surface(surfaceTexture); + createMediaPlayer(); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + release(); + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + } + + /* Animatable methods */ + + @Override + public void start() { + if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { + mMediaPlayer.start(); + } + } + + @Override + public void stop() { + if (mMediaPlayer != null) { + mMediaPlayer.pause(); + } + } + + @Override + public boolean isRunning() { + return mMediaPlayer != null && mMediaPlayer.isPlaying(); + } + + /* MediaPlayer callbacks */ + + @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); + } + return false; + } + + @Override + public void onPrepared(MediaPlayer mp) { + mp.setLooping(shouldLoop()); + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + mp.start(); + } +} |