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 17package android.support.v17.leanback.media; 18 19import android.content.Context; 20import android.media.AudioManager; 21import android.media.MediaPlayer; 22import android.net.Uri; 23import android.os.Handler; 24import android.support.v17.leanback.R; 25import android.view.SurfaceHolder; 26 27import java.io.IOException; 28 29/** 30 * This implementation extends the {@link PlayerAdapter} with a {@link MediaPlayer}. 31 */ 32public class MediaPlayerAdapter extends PlayerAdapter { 33 34 Context mContext; 35 final MediaPlayer mPlayer = new MediaPlayer(); 36 SurfaceHolderGlueHost mSurfaceHolderGlueHost; 37 final Runnable mRunnable = new Runnable() { 38 @Override 39 public void run() { 40 getCallback().onCurrentPositionChanged(MediaPlayerAdapter.this); 41 mHandler.postDelayed(this, getUpdatePeriod()); 42 } 43 };; 44 final Handler mHandler = new Handler(); 45 boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized 46 Uri mMediaSourceUri = null; 47 boolean mHasDisplay; 48 long mBufferedProgress; 49 50 MediaPlayer.OnPreparedListener mOnPreparedListener = new MediaPlayer.OnPreparedListener() { 51 @Override 52 public void onPrepared(MediaPlayer mp) { 53 mInitialized = true; 54 notifyBufferingStartEnd(); 55 if (mSurfaceHolderGlueHost == null || mHasDisplay) { 56 getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); 57 } 58 } 59 }; 60 61 final MediaPlayer.OnCompletionListener mOnCompletionListener = 62 new MediaPlayer.OnCompletionListener() { 63 @Override 64 public void onCompletion(MediaPlayer mediaPlayer) { 65 getCallback().onPlayStateChanged(MediaPlayerAdapter.this); 66 getCallback().onPlayCompleted(MediaPlayerAdapter.this); 67 } 68 }; 69 70 final MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = 71 new MediaPlayer.OnBufferingUpdateListener() { 72 @Override 73 public void onBufferingUpdate(MediaPlayer mp, int percent) { 74 mBufferedProgress = getDuration() * percent / 100; 75 getCallback().onBufferedPositionChanged(MediaPlayerAdapter.this); 76 } 77 }; 78 79 final MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = 80 new MediaPlayer.OnVideoSizeChangedListener() { 81 @Override 82 public void onVideoSizeChanged(MediaPlayer mediaPlayer, int width, int height) { 83 getCallback().onVideoSizeChanged(MediaPlayerAdapter.this, width, height); 84 } 85 }; 86 87 final MediaPlayer.OnErrorListener mOnErrorListener = 88 new MediaPlayer.OnErrorListener() { 89 @Override 90 public boolean onError(MediaPlayer mp, int what, int extra) { 91 getCallback().onError(MediaPlayerAdapter.this, what, 92 mContext.getString(R.string.lb_media_player_error, what, extra)); 93 return MediaPlayerAdapter.this.onError(what, extra); 94 } 95 }; 96 97 final MediaPlayer.OnSeekCompleteListener mOnSeekCompleteListener = 98 new MediaPlayer.OnSeekCompleteListener() { 99 @Override 100 public void onSeekComplete(MediaPlayer mp) { 101 MediaPlayerAdapter.this.onSeekComplete(); 102 } 103 }; 104 105 final MediaPlayer.OnInfoListener mOnInfoListener = new MediaPlayer.OnInfoListener() { 106 @Override 107 public boolean onInfo(MediaPlayer mp, int what, int extra) { 108 boolean handled = false; 109 switch (what) { 110 case MediaPlayer.MEDIA_INFO_BUFFERING_START: 111 mBufferingStart = true; 112 notifyBufferingStartEnd(); 113 handled = true; 114 break; 115 case MediaPlayer.MEDIA_INFO_BUFFERING_END: 116 mBufferingStart = false; 117 notifyBufferingStartEnd(); 118 handled = true; 119 break; 120 } 121 boolean thisHandled = MediaPlayerAdapter.this.onInfo(what, extra); 122 return handled || thisHandled; 123 } 124 }; 125 126 boolean mBufferingStart; 127 128 void notifyBufferingStartEnd() { 129 getCallback().onBufferingStateChanged(MediaPlayerAdapter.this, 130 mBufferingStart || !mInitialized); 131 } 132 133 /** 134 * Constructor. 135 */ 136 public MediaPlayerAdapter(Context context) { 137 mContext = context; 138 } 139 140 @Override 141 public void onAttachedToHost(PlaybackGlueHost host) { 142 if (host instanceof SurfaceHolderGlueHost) { 143 mSurfaceHolderGlueHost = ((SurfaceHolderGlueHost) host); 144 mSurfaceHolderGlueHost.setSurfaceHolderCallback(new VideoPlayerSurfaceHolderCallback()); 145 } 146 } 147 148 /** 149 * Will reset the {@link MediaPlayer} and the glue such that a new file can be played. You are 150 * not required to call this method before playing the first file. However you have to call it 151 * before playing a second one. 152 */ 153 public void reset() { 154 changeToUnitialized(); 155 mPlayer.reset(); 156 } 157 158 void changeToUnitialized() { 159 if (mInitialized) { 160 mInitialized = false; 161 notifyBufferingStartEnd(); 162 if (mHasDisplay) { 163 getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); 164 } 165 } 166 } 167 168 /** 169 * Release internal MediaPlayer. Should not use the object after call release(). 170 */ 171 public void release() { 172 changeToUnitialized(); 173 mHasDisplay = false; 174 mPlayer.release(); 175 } 176 177 @Override 178 public void onDetachedFromHost() { 179 if (mSurfaceHolderGlueHost != null) { 180 mSurfaceHolderGlueHost.setSurfaceHolderCallback(null); 181 mSurfaceHolderGlueHost = null; 182 } 183 reset(); 184 release(); 185 } 186 187 /** 188 * Called to indicate an error. 189 * 190 * @param what the type of error that has occurred: 191 * <ul> 192 * <li>{@link MediaPlayer#MEDIA_ERROR_UNKNOWN} 193 * <li>{@link MediaPlayer#MEDIA_ERROR_SERVER_DIED} 194 * </ul> 195 * @param extra an extra code, specific to the error. Typically 196 * implementation dependent. 197 * <ul> 198 * <li>{@link MediaPlayer#MEDIA_ERROR_IO} 199 * <li>{@link MediaPlayer#MEDIA_ERROR_MALFORMED} 200 * <li>{@link MediaPlayer#MEDIA_ERROR_UNSUPPORTED} 201 * <li>{@link MediaPlayer#MEDIA_ERROR_TIMED_OUT} 202 * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error. 203 * </ul> 204 * @return True if the method handled the error, false if it didn't. 205 * Returning false, will cause the {@link PlayerAdapter.Callback#onPlayCompleted(PlayerAdapter)} 206 * being called. 207 */ 208 protected boolean onError(int what, int extra) { 209 return false; 210 } 211 212 /** 213 * Called to indicate the completion of a seek operation. 214 */ 215 protected void onSeekComplete() { 216 } 217 218 /** 219 * Called to indicate an info or a warning. 220 * 221 * @param what the type of info or warning. 222 * <ul> 223 * <li>{@link MediaPlayer#MEDIA_INFO_UNKNOWN} 224 * <li>{@link MediaPlayer#MEDIA_INFO_VIDEO_TRACK_LAGGING} 225 * <li>{@link MediaPlayer#MEDIA_INFO_VIDEO_RENDERING_START} 226 * <li>{@link MediaPlayer#MEDIA_INFO_BUFFERING_START} 227 * <li>{@link MediaPlayer#MEDIA_INFO_BUFFERING_END} 228 * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> - 229 * bandwidth information is available (as <code>extra</code> kbps) 230 * <li>{@link MediaPlayer#MEDIA_INFO_BAD_INTERLEAVING} 231 * <li>{@link MediaPlayer#MEDIA_INFO_NOT_SEEKABLE} 232 * <li>{@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE} 233 * <li>{@link MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} 234 * <li>{@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT} 235 * </ul> 236 * @param extra an extra code, specific to the info. Typically 237 * implementation dependent. 238 * @return True if the method handled the info, false if it didn't. 239 * Returning false, will cause the info to be discarded. 240 */ 241 protected boolean onInfo(int what, int extra) { 242 return false; 243 } 244 245 /** 246 * @see MediaPlayer#setDisplay(SurfaceHolder) 247 */ 248 void setDisplay(SurfaceHolder surfaceHolder) { 249 boolean hadDisplay = mHasDisplay; 250 mHasDisplay = surfaceHolder != null; 251 if (hadDisplay == mHasDisplay) { 252 return; 253 } 254 mPlayer.setDisplay(surfaceHolder); 255 if (mHasDisplay) { 256 if (mInitialized) { 257 getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); 258 } 259 } else { 260 if (mInitialized) { 261 getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); 262 } 263 } 264 265 } 266 267 @Override 268 public void setProgressUpdatingEnabled(final boolean enabled) { 269 mHandler.removeCallbacks(mRunnable); 270 if (!enabled) { 271 return; 272 } 273 mHandler.postDelayed(mRunnable, getUpdatePeriod()); 274 } 275 276 int getUpdatePeriod() { 277 return 16; 278 } 279 280 @Override 281 public boolean isPlaying() { 282 return mInitialized && mPlayer.isPlaying(); 283 } 284 285 @Override 286 public long getDuration() { 287 return mInitialized ? mPlayer.getDuration() : -1; 288 } 289 290 @Override 291 public long getCurrentPosition() { 292 return mInitialized ? mPlayer.getCurrentPosition() : -1; 293 } 294 295 @Override 296 public void play() { 297 if (!mInitialized || mPlayer.isPlaying()) { 298 return; 299 } 300 mPlayer.start(); 301 getCallback().onPlayStateChanged(MediaPlayerAdapter.this); 302 getCallback().onCurrentPositionChanged(MediaPlayerAdapter.this); 303 } 304 305 @Override 306 public void pause() { 307 if (isPlaying()) { 308 mPlayer.pause(); 309 getCallback().onPlayStateChanged(MediaPlayerAdapter.this); 310 } 311 } 312 313 @Override 314 public void seekTo(long newPosition) { 315 if (!mInitialized) { 316 return; 317 } 318 mPlayer.seekTo((int) newPosition); 319 } 320 321 @Override 322 public long getBufferedPosition() { 323 return mBufferedProgress; 324 } 325 326 /** 327 * Sets the media source of the player witha given URI. 328 * 329 * @return Returns <code>true</code> if uri represents a new media; <code>false</code> 330 * otherwise. 331 * @see MediaPlayer#setDataSource(String) 332 */ 333 public boolean setDataSource(Uri uri) { 334 if (mMediaSourceUri != null ? mMediaSourceUri.equals(uri) : uri == null) { 335 return false; 336 } 337 mMediaSourceUri = uri; 338 prepareMediaForPlaying(); 339 return true; 340 } 341 342 private void prepareMediaForPlaying() { 343 reset(); 344 try { 345 if (mMediaSourceUri != null) { 346 mPlayer.setDataSource(mContext, mMediaSourceUri); 347 } else { 348 return; 349 } 350 } catch (IOException e) { 351 e.printStackTrace(); 352 throw new RuntimeException(e); 353 } 354 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 355 mPlayer.setOnPreparedListener(mOnPreparedListener); 356 mPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); 357 mPlayer.setOnErrorListener(mOnErrorListener); 358 mPlayer.setOnSeekCompleteListener(mOnSeekCompleteListener); 359 mPlayer.setOnCompletionListener(mOnCompletionListener); 360 mPlayer.setOnInfoListener(mOnInfoListener); 361 mPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); 362 notifyBufferingStartEnd(); 363 mPlayer.prepareAsync(); 364 getCallback().onPlayStateChanged(MediaPlayerAdapter.this); 365 } 366 367 /** 368 * @return True if MediaPlayer OnPreparedListener is invoked and got a SurfaceHolder if 369 * {@link PlaybackGlueHost} provides SurfaceHolder. 370 */ 371 @Override 372 public boolean isPrepared() { 373 return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay); 374 } 375 376 /** 377 * Implements {@link SurfaceHolder.Callback} that can then be set on the 378 * {@link PlaybackGlueHost}. 379 */ 380 class VideoPlayerSurfaceHolderCallback implements SurfaceHolder.Callback { 381 @Override 382 public void surfaceCreated(SurfaceHolder surfaceHolder) { 383 setDisplay(surfaceHolder); 384 } 385 386 @Override 387 public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { 388 } 389 390 @Override 391 public void surfaceDestroyed(SurfaceHolder surfaceHolder) { 392 setDisplay(null); 393 } 394 } 395} 396