1 /*
2  * Copyright 2018 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.widget;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.media.AudioAttributes;
24 import android.media.AudioManager;
25 import android.media.DataSourceDesc;
26 import android.media.MediaItem2;
27 import android.media.MediaMetadata2;
28 import android.media.MediaPlayer2;
29 import android.media.SessionToken2;
30 import android.media.session.MediaController;
31 import android.media.session.PlaybackState;
32 import android.media.update.ApiLoader;
33 import android.media.update.VideoView2Provider;
34 import android.media.update.ViewGroupHelper;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.util.AttributeSet;
38 import android.view.View;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.concurrent.Executor;
47 
48 // TODO: Replace MediaSession wtih MediaSession2 once MediaSession2 is submitted.
49 /**
50  * @hide
51  * Displays a video file.  VideoView2 class is a View class which is wrapping {@link MediaPlayer2}
52  * so that developers can easily implement a video rendering application.
53  *
54  * <p>
55  * <em> Data sources that VideoView2 supports : </em>
56  * VideoView2 can play video files and audio-only files as
57  * well. It can load from various sources such as resources or content providers. The supported
58  * media file formats are the same as {@link MediaPlayer2}.
59  *
60  * <p>
61  * <em> View type can be selected : </em>
62  * VideoView2 can render videos on top of TextureView as well as
63  * SurfaceView selectively. The default is SurfaceView and it can be changed using
64  * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving
65  * battery. TextureView might be preferred for supporting various UIs such as animation and
66  * translucency.
67  *
68  * <p>
69  * <em> Differences between {@link VideoView} class : </em>
70  * VideoView2 covers and inherits the most of
71  * VideoView's functionalities. The main differences are
72  * <ul>
73  * <li> VideoView2 inherits FrameLayout and renders videos using SurfaceView and TextureView
74  * selectively while VideoView inherits SurfaceView class.
75  * <li> VideoView2 is integrated with MediaControlView2 and a default MediaControlView2 instance is
76  * attached to VideoView2 by default. If a developer does not want to use the default
77  * MediaControlView2, needs to set enableControlView attribute to false. For instance,
78  * <pre>
79  * &lt;VideoView2
80  *     android:id="@+id/video_view"
81  *     xmlns:widget="http://schemas.android.com/apk/com.android.media.update"
82  *     widget:enableControlView="false" /&gt;
83  * </pre>
84  * If a developer wants to attach a customed MediaControlView2, then set enableControlView attribute
85  * to false and assign the customed media control widget using {@link #setMediaControlView2}.
86  * <li> VideoView2 is integrated with MediaPlayer2 while VideoView is integrated with MediaPlayer.
87  * <li> VideoView2 is integrated with MediaSession and so it responses with media key events.
88  * A VideoView2 keeps a MediaSession instance internally and connects it to a corresponding
89  * MediaControlView2 instance.
90  * </p>
91  * </ul>
92  *
93  * <p>
94  * <em> Audio focus and audio attributes : </em>
95  * By default, VideoView2 requests audio focus with
96  * {@link AudioManager#AUDIOFOCUS_GAIN}. Use {@link #setAudioFocusRequest(int)} to change this
97  * behavior. The default {@link AudioAttributes} used during playback have a usage of
98  * {@link AudioAttributes#USAGE_MEDIA} and a content type of
99  * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to
100  * modify them.
101  *
102  * <p>
103  * Note: VideoView2 does not retain its full state when going into the background. In particular, it
104  * does not restore the current play state, play position, selected tracks. Applications should save
105  * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and
106  * {@link android.app.Activity#onRestoreInstanceState}.
107  */
108 public class VideoView2 extends ViewGroupHelper<VideoView2Provider> {
109     /** @hide */
110     @IntDef({
111             VIEW_TYPE_TEXTUREVIEW,
112             VIEW_TYPE_SURFACEVIEW
113     })
114     @Retention(RetentionPolicy.SOURCE)
115     public @interface ViewType {}
116 
117     /**
118      * Indicates video is rendering on SurfaceView.
119      *
120      * @see #setViewType
121      */
122     public static final int VIEW_TYPE_SURFACEVIEW = 1;
123 
124     /**
125      * Indicates video is rendering on TextureView.
126      *
127      * @see #setViewType
128      */
129     public static final int VIEW_TYPE_TEXTUREVIEW = 2;
130 
VideoView2(@onNull Context context)131     public VideoView2(@NonNull Context context) {
132         this(context, null);
133     }
134 
VideoView2(@onNull Context context, @Nullable AttributeSet attrs)135     public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) {
136         this(context, attrs, 0);
137     }
138 
VideoView2(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)139     public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
140         this(context, attrs, defStyleAttr, 0);
141     }
142 
VideoView2( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)143     public VideoView2(
144             @NonNull Context context, @Nullable AttributeSet attrs,
145             int defStyleAttr, int defStyleRes) {
146         super((instance, superProvider, privateProvider) ->
147                 ApiLoader.getProvider().createVideoView2(
148                         (VideoView2) instance, superProvider, privateProvider,
149                         attrs, defStyleAttr, defStyleRes),
150                 context, attrs, defStyleAttr, defStyleRes);
151         mProvider.initialize(attrs, defStyleAttr, defStyleRes);
152     }
153 
154     /**
155      * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2
156      * instance if any.
157      *
158      * @param mediaControlView a media control view2 instance.
159      * @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2.
160      */
setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs)161     public void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs) {
162         mProvider.setMediaControlView2_impl(mediaControlView, intervalMs);
163     }
164 
165     /**
166      * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by
167      * {@link #setMediaControlView2} method.
168      */
getMediaControlView2()169     public MediaControlView2 getMediaControlView2() {
170         return mProvider.getMediaControlView2_impl();
171     }
172 
173     /**
174      * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance
175      * if any.
176      *
177      * @param metadata a MediaMetadata2 instance.
178      * @hide
179      */
setMediaMetadata(MediaMetadata2 metadata)180     public void setMediaMetadata(MediaMetadata2 metadata) {
181         mProvider.setMediaMetadata_impl(metadata);
182     }
183 
184     /**
185      * Returns MediaMetadata2 instance which is retrieved from MediaPlayer2 inside VideoView2 by
186      * default or by {@link #setMediaMetadata} method.
187      * @hide
188      */
getMediaMetadata()189     public MediaMetadata2 getMediaMetadata() {
190         // TODO: add to Javadoc whether this value can be null or not when integrating with
191         // MediaSession2.
192         return mProvider.getMediaMetadata_impl();
193     }
194 
195     /**
196      * Returns MediaController instance which is connected with MediaSession that VideoView2 is
197      * using. This method should be called when VideoView2 is attached to window, or it throws
198      * IllegalStateException, since internal MediaSession instance is not available until
199      * this view is attached to window. Please check {@link android.view.View#isAttachedToWindow}
200      * before calling this method.
201      *
202      * @throws IllegalStateException if interal MediaSession is not created yet.
203      * @hide  TODO: remove
204      */
getMediaController()205     public MediaController getMediaController() {
206         return mProvider.getMediaController_impl();
207     }
208 
209     /**
210      * Returns {@link android.media.SessionToken2} so that developers create their own
211      * {@link android.media.MediaController2} instance. This method should be called when VideoView2
212      * is attached to window, or it throws IllegalStateException.
213      *
214      * @throws IllegalStateException if interal MediaSession is not created yet.
215      */
getMediaSessionToken()216     public SessionToken2 getMediaSessionToken() {
217         return mProvider.getMediaSessionToken_impl();
218     }
219 
220     /**
221      * Shows or hides closed caption or subtitles if there is any.
222      * The first subtitle track will be chosen if there multiple subtitle tracks exist.
223      * Default behavior of VideoView2 is not showing subtitle.
224      * @param enable shows closed caption or subtitles if this value is true, or hides.
225      */
setSubtitleEnabled(boolean enable)226     public void setSubtitleEnabled(boolean enable) {
227         mProvider.setSubtitleEnabled_impl(enable);
228     }
229 
230     /**
231      * Returns true if showing subtitle feature is enabled or returns false.
232      * Although there is no subtitle track or closed caption, it can return true, if the feature
233      * has been enabled by {@link #setSubtitleEnabled}.
234      */
isSubtitleEnabled()235     public boolean isSubtitleEnabled() {
236         return mProvider.isSubtitleEnabled_impl();
237     }
238 
239     /**
240      * Sets playback speed.
241      *
242      * It is expressed as a multiplicative factor, where normal speed is 1.0f. If it is less than
243      * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the
244      * maximum speed that internal engine supports, system will determine best handling or it will
245      * be reset to the normal speed 1.0f.
246      * @param speed the playback speed. It should be positive.
247      */
248     // TODO: Support this via MediaController2.
setSpeed(float speed)249     public void setSpeed(float speed) {
250         mProvider.setSpeed_impl(speed);
251     }
252 
253     /**
254      * Sets which type of audio focus will be requested during the playback, or configures playback
255      * to not request audio focus. Valid values for focus requests are
256      * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
257      * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
258      * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
259      * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
260      * requested when playback starts. You can for instance use this when playing a silent animation
261      * through this class, and you don't want to affect other audio applications playing in the
262      * background.
263      *
264      * @param focusGain the type of audio focus gain that will be requested, or
265      *                  {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
266      *                  playback.
267      */
setAudioFocusRequest(int focusGain)268     public void setAudioFocusRequest(int focusGain) {
269         mProvider.setAudioFocusRequest_impl(focusGain);
270     }
271 
272     /**
273      * Sets the {@link AudioAttributes} to be used during the playback of the video.
274      *
275      * @param attributes non-null <code>AudioAttributes</code>.
276      */
setAudioAttributes(@onNull AudioAttributes attributes)277     public void setAudioAttributes(@NonNull AudioAttributes attributes) {
278         mProvider.setAudioAttributes_impl(attributes);
279     }
280 
281     /**
282      * Sets video path.
283      *
284      * @param path the path of the video.
285      *
286      * @hide TODO remove
287      */
setVideoPath(String path)288     public void setVideoPath(String path) {
289         mProvider.setVideoPath_impl(path);
290     }
291 
292     /**
293      * Sets video URI.
294      *
295      * @param uri the URI of the video.
296      *
297      * @hide TODO remove
298      */
setVideoUri(Uri uri)299     public void setVideoUri(Uri uri) {
300         mProvider.setVideoUri_impl(uri);
301     }
302 
303     /**
304      * Sets video URI using specific headers.
305      *
306      * @param uri     the URI of the video.
307      * @param headers the headers for the URI request.
308      *                Note that the cross domain redirection is allowed by default, but that can be
309      *                changed with key/value pairs through the headers parameter with
310      *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
311      *                to disallow or allow cross domain redirection.
312      *
313      * @hide TODO remove
314      */
setVideoUri(Uri uri, Map<String, String> headers)315     public void setVideoUri(Uri uri, Map<String, String> headers) {
316         mProvider.setVideoUri_impl(uri, headers);
317     }
318 
319     /**
320      * Sets {@link MediaItem2} object to render using VideoView2. Alternative way to set media
321      * object to VideoView2 is {@link #setDataSource}.
322      * @param mediaItem the MediaItem2 to play
323      * @see #setDataSource
324      */
setMediaItem(@onNull MediaItem2 mediaItem)325     public void setMediaItem(@NonNull MediaItem2 mediaItem) {
326         mProvider.setMediaItem_impl(mediaItem);
327     }
328 
329     /**
330      * Sets {@link DataSourceDesc} object to render using VideoView2.
331      * @param dataSource the {@link DataSourceDesc} object to play.
332      * @see #setMediaItem
333      */
setDataSource(@onNull DataSourceDesc dataSource)334     public void setDataSource(@NonNull DataSourceDesc dataSource) {
335         mProvider.setDataSource_impl(dataSource);
336     }
337 
338     /**
339      * Selects which view will be used to render video between SurfacView and TextureView.
340      *
341      * @param viewType the view type to render video
342      * <ul>
343      * <li>{@link #VIEW_TYPE_SURFACEVIEW}
344      * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
345      * </ul>
346      */
setViewType(@iewType int viewType)347     public void setViewType(@ViewType int viewType) {
348         mProvider.setViewType_impl(viewType);
349     }
350 
351     /**
352      * Returns view type.
353      *
354      * @return view type. See {@see setViewType}.
355      */
356     @ViewType
getViewType()357     public int getViewType() {
358         return mProvider.getViewType_impl();
359     }
360 
361     /**
362      * Sets custom actions which will be shown as custom buttons in {@link MediaControlView2}.
363      *
364      * @param actionList A list of {@link PlaybackState.CustomAction}. The return value of
365      *                   {@link PlaybackState.CustomAction#getIcon()} will be used to draw buttons
366      *                   in {@link MediaControlView2}.
367      * @param executor executor to run callbacks on.
368      * @param listener A listener to be called when a custom button is clicked.
369      * @hide  TODO remove
370      */
setCustomActions(List<PlaybackState.CustomAction> actionList, Executor executor, OnCustomActionListener listener)371     public void setCustomActions(List<PlaybackState.CustomAction> actionList,
372             Executor executor, OnCustomActionListener listener) {
373         mProvider.setCustomActions_impl(actionList, executor, listener);
374     }
375 
376     /**
377      * Registers a callback to be invoked when a view type change is done.
378      * {@see #setViewType(int)}
379      * @param l The callback that will be run
380      * @hide
381      */
382     @VisibleForTesting
setOnViewTypeChangedListener(OnViewTypeChangedListener l)383     public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) {
384         mProvider.setOnViewTypeChangedListener_impl(l);
385     }
386 
387     /**
388      * Registers a callback to be invoked when the fullscreen mode should be changed.
389      * @param l The callback that will be run
390      * @hide  TODO remove
391      */
setFullScreenRequestListener(OnFullScreenRequestListener l)392     public void setFullScreenRequestListener(OnFullScreenRequestListener l) {
393         mProvider.setFullScreenRequestListener_impl(l);
394     }
395 
396     /**
397      * Interface definition of a callback to be invoked when the view type has been changed.
398      *
399      * @hide
400      */
401     @VisibleForTesting
402     public interface OnViewTypeChangedListener {
403         /**
404          * Called when the view type has been changed.
405          * @see #setViewType(int)
406          * @param view the View whose view type is changed
407          * @param viewType
408          * <ul>
409          * <li>{@link #VIEW_TYPE_SURFACEVIEW}
410          * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
411          * </ul>
412          */
onViewTypeChanged(View view, @ViewType int viewType)413         void onViewTypeChanged(View view, @ViewType int viewType);
414     }
415 
416     /**
417      * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
418      * Application should handle the fullscreen mode accordingly.
419      * @hide  TODO remove
420      */
421     public interface OnFullScreenRequestListener {
422         /**
423          * Called to indicate a fullscreen mode change.
424          */
onFullScreenRequest(View view, boolean fullScreen)425         void onFullScreenRequest(View view, boolean fullScreen);
426     }
427 
428     /**
429      * Interface definition of a callback to be invoked to inform that a custom action is performed.
430      * @hide  TODO remove
431      */
432     public interface OnCustomActionListener {
433         /**
434          * Called to indicate that a custom action is performed.
435          *
436          * @param action The action that was originally sent in the
437          *               {@link PlaybackState.CustomAction}.
438          * @param extras Optional extras.
439          */
onCustomAction(String action, Bundle extras)440         void onCustomAction(String action, Bundle extras);
441     }
442 
443     @Override
onLayout(boolean changed, int l, int t, int r, int b)444     protected void onLayout(boolean changed, int l, int t, int r, int b) {
445         mProvider.onLayout_impl(changed, l, t, r, b);
446     }
447 }
448