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