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 android.content.Context;
20import android.media.MediaPlayer;
21import android.media.MediaPlayer.OnSubtitleDataListener;
22import android.media.SubtitleData;
23import android.net.Uri;
24import android.os.Bundle;
25import android.util.AttributeSet;
26import android.util.Log;
27import android.util.Pair;
28import android.view.View;
29import android.view.ViewGroup.LayoutParams;
30
31import androidx.annotation.Nullable;
32import androidx.annotation.RequiresApi;
33import androidx.media.subtitle.ClosedCaptionRenderer;
34import androidx.media.subtitle.SubtitleController;
35import androidx.media.subtitle.SubtitleTrack;
36
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.Map;
40
41/**
42 * Base implementation of VideoView2.
43 */
44@RequiresApi(28)
45class VideoView2ImplApi28WithMp1 extends VideoView2ImplBaseWithMp1 {
46    private static final String TAG = "VideoView2ImplApi28_1";
47    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
48
49    private static final int INVALID_TRACK_INDEX = -1;
50
51    private ArrayList<Pair<Integer, SubtitleTrack>> mSubtitleTrackIndices;
52    private SubtitleController mSubtitleController;
53
54    // selected video/audio/subtitle track index as MediaPlayer returns
55    private int mSelectedSubtitleTrackIndex;
56
57    private SubtitleView mSubtitleView;
58    private boolean mSubtitleEnabled;
59
60    @Override
61    public void initialize(
62            VideoView2 instance, Context context,
63            @Nullable AttributeSet attrs, int defStyleAttr) {
64        super.initialize(instance, context, attrs, defStyleAttr);
65        mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
66
67        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
68                LayoutParams.MATCH_PARENT);
69        mSubtitleView = new SubtitleView(context);
70        mSubtitleView.setLayoutParams(params);
71        mSubtitleView.setBackgroundColor(0);
72        mInstance.addView(mSubtitleView);
73
74        mSubtitleEnabled = (attrs == null) || attrs.getAttributeBooleanValue(
75                "http://schemas.android.com/apk/res/android",
76                "enableSubtitle", false);
77    }
78
79    /**
80     * Shows or hides closed caption or subtitles if there is any.
81     * The first subtitle track will be chosen if there multiple subtitle tracks exist.
82     * Default behavior of VideoView2 is not showing subtitle.
83     * @param enable shows closed caption or subtitles if this value is true, or hides.
84     */
85    @Override
86    public void setSubtitleEnabled(boolean enable) {
87        if (enable != mSubtitleEnabled) {
88            selectOrDeselectSubtitle(enable);
89        }
90        mSubtitleEnabled = enable;
91    }
92
93    /**
94     * Returns true if showing subtitle feature is enabled or returns false.
95     * Although there is no subtitle track or closed caption, it can return true, if the feature
96     * has been enabled by {@link #setSubtitleEnabled}.
97     */
98    @Override
99    public boolean isSubtitleEnabled() {
100        return mSubtitleEnabled;
101    }
102
103    ///////////////////////////////////////////////////
104    // Protected or private methods
105    ///////////////////////////////////////////////////
106
107    /**
108     * Used in openVideo(). Setup MediaPlayer and related objects before calling prepare.
109     */
110    @Override
111    protected void setupMediaPlayer(Context context, Uri uri, Map<String, String> headers)
112            throws IOException {
113        super.setupMediaPlayer(context, uri, headers);
114
115        mSubtitleController = new SubtitleController(context);
116        mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
117        mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleView);
118
119        mMediaPlayer.setOnSubtitleDataListener(mSubtitleListener);
120    }
121
122    private void selectOrDeselectSubtitle(boolean select) {
123        if (!isInPlaybackState()) {
124            return;
125        }
126        if (select) {
127            if (mSubtitleTrackIndices.size() > 0) {
128                mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0).first;
129                mSubtitleController.selectTrack(mSubtitleTrackIndices.get(0).second);
130                mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
131                mSubtitleView.setVisibility(View.VISIBLE);
132            }
133        } else {
134            if (mSelectedSubtitleTrackIndex != INVALID_TRACK_INDEX) {
135                mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
136                mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
137                mSubtitleView.setVisibility(View.GONE);
138            }
139        }
140    }
141
142    @Override
143    protected void extractTracks() {
144        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
145        mVideoTrackIndices = new ArrayList<>();
146        mAudioTrackIndices = new ArrayList<>();
147        mSubtitleTrackIndices = new ArrayList<>();
148        mSubtitleController.reset();
149        for (int i = 0; i < trackInfos.length; ++i) {
150            int trackType = trackInfos[i].getTrackType();
151            if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
152                mVideoTrackIndices.add(i);
153            } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
154                mAudioTrackIndices.add(i);
155            } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
156                SubtitleTrack track = mSubtitleController.addTrack(trackInfos[i].getFormat());
157                if (track != null) {
158                    mSubtitleTrackIndices.add(new Pair<>(i, track));
159                }
160            }
161        }
162        // Select first tracks as default
163        if (mVideoTrackIndices.size() > 0) {
164            mSelectedVideoTrackIndex = 0;
165        }
166        if (mAudioTrackIndices.size() > 0) {
167            mSelectedAudioTrackIndex = 0;
168        }
169
170        Bundle data = new Bundle();
171        data.putInt(MediaControlView2.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
172        data.putInt(MediaControlView2.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
173        data.putInt(MediaControlView2.KEY_SUBTITLE_TRACK_COUNT, mSubtitleTrackIndices.size());
174        if (mSubtitleTrackIndices.size() > 0) {
175            selectOrDeselectSubtitle(mSubtitleEnabled);
176        }
177        mMediaSession.sendSessionEvent(MediaControlView2.EVENT_UPDATE_TRACK_STATUS, data);
178    }
179
180    private OnSubtitleDataListener mSubtitleListener =
181            new OnSubtitleDataListener() {
182                @Override
183                public void onSubtitleData(MediaPlayer mp, SubtitleData data) {
184                    if (DEBUG) {
185                        Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
186                                + ", getCurrentPosition: " + mp.getCurrentPosition()
187                                + ", getStartTimeUs(): " + data.getStartTimeUs()
188                                + ", diff: "
189                                + (data.getStartTimeUs() / 1000 - mp.getCurrentPosition())
190                                + "ms, getDurationUs(): " + data.getDurationUs());
191
192                    }
193                    final int index = data.getTrackIndex();
194                    if (index != mSelectedSubtitleTrackIndex) {
195                        Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
196                                + ", selected track index: " + mSelectedSubtitleTrackIndex);
197                        return;
198                    }
199                    for (Pair<Integer, SubtitleTrack> p : mSubtitleTrackIndices) {
200                        if (p.first == index) {
201                            SubtitleTrack track = p.second;
202                            track.onData(data);
203                        }
204                    }
205                }
206            };
207
208    @Override
209    protected void doShowSubtitleCommand(Bundle args) {
210        int subtitleIndex = args.getInt(
211                MediaControlView2.KEY_SELECTED_SUBTITLE_INDEX,
212                INVALID_TRACK_INDEX);
213        if (subtitleIndex != INVALID_TRACK_INDEX) {
214            int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex).first;
215            if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
216                mSelectedSubtitleTrackIndex = subtitleTrackIndex;
217                mInstance.setSubtitleEnabled(true);
218            }
219        }
220    }
221
222    @Override
223    protected void doHideSubtitleCommand() {
224        mInstance.setSubtitleEnabled(false);
225    }
226}
227