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