1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.leanback.app; 18 19 import android.animation.Animator; 20 import android.animation.ValueAnimator; 21 import android.graphics.drawable.Drawable; 22 23 import androidx.leanback.media.PlaybackGlue; 24 import androidx.leanback.widget.DetailsParallax; 25 import androidx.leanback.widget.Parallax; 26 import androidx.leanback.widget.ParallaxEffect; 27 import androidx.leanback.widget.ParallaxTarget; 28 29 /** 30 * Helper class responsible for controlling video playback in {@link DetailsFragment}. This 31 * takes {@link DetailsParallax}, {@link PlaybackGlue} and a drawable as input. 32 * Video is played when {@link DetailsParallax#getOverviewRowTop()} moved bellow top edge of screen. 33 * Video is stopped when {@link DetailsParallax#getOverviewRowTop()} reaches or scrolls above top 34 * edge of screen. The drawable will change alpha to 0 when video is ready to play. 35 * App does not directly use this class. 36 * @see DetailsFragmentBackgroundController 37 * @see DetailsSupportFragmentBackgroundController 38 */ 39 final class DetailsBackgroundVideoHelper { 40 private static final long BACKGROUND_CROSS_FADE_DURATION = 500; 41 // Temporarily add CROSSFADE_DELAY waiting for video surface ready. 42 // We will remove this delay once PlaybackGlue have a callback for videoRenderingReady event. 43 private static final long CROSSFADE_DELAY = 1000; 44 45 /** 46 * Different states {@link DetailsFragment} can be in. 47 */ 48 static final int INITIAL = 0; 49 static final int PLAY_VIDEO = 1; 50 static final int NO_VIDEO = 2; 51 52 private final DetailsParallax mDetailsParallax; 53 private ParallaxEffect mParallaxEffect; 54 55 private int mCurrentState = INITIAL; 56 57 private ValueAnimator mBackgroundAnimator; 58 private Drawable mBackgroundDrawable; 59 private PlaybackGlue mPlaybackGlue; 60 private boolean mBackgroundDrawableVisible; 61 62 /** 63 * Constructor to setup a Helper for controlling video playback in DetailsFragment. 64 * @param playbackGlue The PlaybackGlue used to control underlying player. 65 * @param detailsParallax The DetailsParallax to add special parallax effect to control video 66 * start/stop. Video is played when 67 * {@link DetailsParallax#getOverviewRowTop()} moved bellow top edge of 68 * screen. Video is stopped when 69 * {@link DetailsParallax#getOverviewRowTop()} reaches or scrolls above 70 * top edge of screen. 71 * @param backgroundDrawable The drawable will change alpha to 0 when video is ready to play. 72 */ DetailsBackgroundVideoHelper( PlaybackGlue playbackGlue, DetailsParallax detailsParallax, Drawable backgroundDrawable)73 DetailsBackgroundVideoHelper( 74 PlaybackGlue playbackGlue, 75 DetailsParallax detailsParallax, 76 Drawable backgroundDrawable) { 77 this.mPlaybackGlue = playbackGlue; 78 this.mDetailsParallax = detailsParallax; 79 this.mBackgroundDrawable = backgroundDrawable; 80 mBackgroundDrawableVisible = true; 81 mBackgroundDrawable.setAlpha(255); 82 startParallax(); 83 } 84 startParallax()85 void startParallax() { 86 if (mParallaxEffect != null) { 87 return; 88 } 89 Parallax.IntProperty frameTop = mDetailsParallax.getOverviewRowTop(); 90 final float maxFrameTop = 1f; 91 final float minFrameTop = 0f; 92 mParallaxEffect = mDetailsParallax 93 .addEffect(frameTop.atFraction(maxFrameTop), frameTop.atFraction(minFrameTop)) 94 .target(new ParallaxTarget() { 95 @Override 96 public void update(float fraction) { 97 if (fraction == maxFrameTop) { 98 updateState(NO_VIDEO); 99 } else { 100 updateState(PLAY_VIDEO); 101 } 102 } 103 }); 104 // In case the VideoHelper is created after RecyclerView is created: perform initial 105 // parallax effect. 106 mDetailsParallax.updateValues(); 107 } 108 stopParallax()109 void stopParallax() { 110 mDetailsParallax.removeEffect(mParallaxEffect); 111 } 112 isVideoVisible()113 boolean isVideoVisible() { 114 return mCurrentState == PLAY_VIDEO; 115 } 116 updateState(int state)117 private void updateState(int state) { 118 if (state == mCurrentState) { 119 return; 120 } 121 mCurrentState = state; 122 applyState(); 123 } 124 applyState()125 private void applyState() { 126 switch (mCurrentState) { 127 case PLAY_VIDEO: 128 if (mPlaybackGlue != null) { 129 if (mPlaybackGlue.isPrepared()) { 130 internalStartPlayback(); 131 } else { 132 mPlaybackGlue.addPlayerCallback(mControlStateCallback); 133 } 134 } else { 135 crossFadeBackgroundToVideo(false); 136 } 137 break; 138 case NO_VIDEO: 139 crossFadeBackgroundToVideo(false); 140 if (mPlaybackGlue != null) { 141 mPlaybackGlue.removePlayerCallback(mControlStateCallback); 142 mPlaybackGlue.pause(); 143 } 144 break; 145 } 146 } 147 setPlaybackGlue(PlaybackGlue playbackGlue)148 void setPlaybackGlue(PlaybackGlue playbackGlue) { 149 if (mPlaybackGlue != null) { 150 mPlaybackGlue.removePlayerCallback(mControlStateCallback); 151 } 152 mPlaybackGlue = playbackGlue; 153 applyState(); 154 } 155 internalStartPlayback()156 private void internalStartPlayback() { 157 if (mPlaybackGlue != null) { 158 mPlaybackGlue.play(); 159 } 160 mDetailsParallax.getRecyclerView().postDelayed(new Runnable() { 161 @Override 162 public void run() { 163 crossFadeBackgroundToVideo(true); 164 } 165 }, CROSSFADE_DELAY); 166 } 167 crossFadeBackgroundToVideo(boolean crossFadeToVideo)168 void crossFadeBackgroundToVideo(boolean crossFadeToVideo) { 169 crossFadeBackgroundToVideo(crossFadeToVideo, false); 170 } 171 crossFadeBackgroundToVideo(boolean crossFadeToVideo, boolean immediate)172 void crossFadeBackgroundToVideo(boolean crossFadeToVideo, boolean immediate) { 173 final boolean newVisible = !crossFadeToVideo; 174 if (mBackgroundDrawableVisible == newVisible) { 175 if (immediate) { 176 if (mBackgroundAnimator != null) { 177 mBackgroundAnimator.cancel(); 178 mBackgroundAnimator = null; 179 } 180 if (mBackgroundDrawable != null) { 181 mBackgroundDrawable.setAlpha(crossFadeToVideo ? 0 : 255); 182 return; 183 } 184 } 185 return; 186 } 187 mBackgroundDrawableVisible = newVisible; 188 if (mBackgroundAnimator != null) { 189 mBackgroundAnimator.cancel(); 190 mBackgroundAnimator = null; 191 } 192 193 float startAlpha = crossFadeToVideo ? 1f : 0f; 194 float endAlpha = crossFadeToVideo ? 0f : 1f; 195 196 if (mBackgroundDrawable == null) { 197 return; 198 } 199 if (immediate) { 200 mBackgroundDrawable.setAlpha(crossFadeToVideo ? 0 : 255); 201 return; 202 } 203 mBackgroundAnimator = ValueAnimator.ofFloat(startAlpha, endAlpha); 204 mBackgroundAnimator.setDuration(BACKGROUND_CROSS_FADE_DURATION); 205 mBackgroundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 206 @Override 207 public void onAnimationUpdate(ValueAnimator valueAnimator) { 208 mBackgroundDrawable.setAlpha( 209 (int) ((Float) (valueAnimator.getAnimatedValue()) * 255)); 210 } 211 }); 212 213 mBackgroundAnimator.addListener(new Animator.AnimatorListener() { 214 @Override 215 public void onAnimationStart(Animator animator) { 216 } 217 218 @Override 219 public void onAnimationEnd(Animator animator) { 220 mBackgroundAnimator = null; 221 } 222 223 @Override 224 public void onAnimationCancel(Animator animator) { 225 } 226 227 @Override 228 public void onAnimationRepeat(Animator animator) { 229 } 230 }); 231 232 mBackgroundAnimator.start(); 233 } 234 235 private class PlaybackControlStateCallback extends PlaybackGlue.PlayerCallback { 236 237 @Override onPreparedStateChanged(PlaybackGlue glue)238 public void onPreparedStateChanged(PlaybackGlue glue) { 239 if (glue.isPrepared()) { 240 internalStartPlayback(); 241 } 242 } 243 } 244 245 PlaybackControlStateCallback mControlStateCallback = new PlaybackControlStateCallback(); 246 } 247