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