/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v17.leanback.media; import android.content.Context; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.os.Handler; import android.support.v17.leanback.R; import android.view.SurfaceHolder; import java.io.IOException; /** * This implementation extends the {@link PlayerAdapter} with a {@link MediaPlayer}. */ public class MediaPlayerAdapter extends PlayerAdapter { Context mContext; final MediaPlayer mPlayer = new MediaPlayer(); SurfaceHolderGlueHost mSurfaceHolderGlueHost; final Runnable mRunnable = new Runnable() { @Override public void run() { getCallback().onCurrentPositionChanged(MediaPlayerAdapter.this); mHandler.postDelayed(this, getUpdatePeriod()); } };; final Handler mHandler = new Handler(); boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized Uri mMediaSourceUri = null; boolean mHasDisplay; long mBufferedProgress; MediaPlayer.OnPreparedListener mOnPreparedListener = new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mInitialized = true; notifyBufferingStartEnd(); if (mSurfaceHolderGlueHost == null || mHasDisplay) { getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); } } }; final MediaPlayer.OnCompletionListener mOnCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { getCallback().onPlayStateChanged(MediaPlayerAdapter.this); getCallback().onPlayCompleted(MediaPlayerAdapter.this); } }; final MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { mBufferedProgress = getDuration() * percent / 100; getCallback().onBufferedPositionChanged(MediaPlayerAdapter.this); } }; final MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(MediaPlayer mediaPlayer, int width, int height) { getCallback().onVideoSizeChanged(MediaPlayerAdapter.this, width, height); } }; final MediaPlayer.OnErrorListener mOnErrorListener = new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { getCallback().onError(MediaPlayerAdapter.this, what, mContext.getString(R.string.lb_media_player_error, what, extra)); return MediaPlayerAdapter.this.onError(what, extra); } }; final MediaPlayer.OnSeekCompleteListener mOnSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(MediaPlayer mp) { MediaPlayerAdapter.this.onSeekComplete(); } }; final MediaPlayer.OnInfoListener mOnInfoListener = new MediaPlayer.OnInfoListener() { @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { boolean handled = false; switch (what) { case MediaPlayer.MEDIA_INFO_BUFFERING_START: mBufferingStart = true; notifyBufferingStartEnd(); handled = true; break; case MediaPlayer.MEDIA_INFO_BUFFERING_END: mBufferingStart = false; notifyBufferingStartEnd(); handled = true; break; } boolean thisHandled = MediaPlayerAdapter.this.onInfo(what, extra); return handled || thisHandled; } }; boolean mBufferingStart; void notifyBufferingStartEnd() { getCallback().onBufferingStateChanged(MediaPlayerAdapter.this, mBufferingStart || !mInitialized); } /** * Constructor. */ public MediaPlayerAdapter(Context context) { mContext = context; } @Override public void onAttachedToHost(PlaybackGlueHost host) { if (host instanceof SurfaceHolderGlueHost) { mSurfaceHolderGlueHost = ((SurfaceHolderGlueHost) host); mSurfaceHolderGlueHost.setSurfaceHolderCallback(new VideoPlayerSurfaceHolderCallback()); } } /** * Will reset the {@link MediaPlayer} and the glue such that a new file can be played. You are * not required to call this method before playing the first file. However you have to call it * before playing a second one. */ public void reset() { changeToUnitialized(); mPlayer.reset(); } void changeToUnitialized() { if (mInitialized) { mInitialized = false; notifyBufferingStartEnd(); if (mHasDisplay) { getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); } } } /** * Release internal MediaPlayer. Should not use the object after call release(). */ public void release() { changeToUnitialized(); mHasDisplay = false; mPlayer.release(); } @Override public void onDetachedFromHost() { if (mSurfaceHolderGlueHost != null) { mSurfaceHolderGlueHost.setSurfaceHolderCallback(null); mSurfaceHolderGlueHost = null; } reset(); release(); } /** * Called to indicate an error. * * @param what the type of error that has occurred: * * @param extra an extra code, specific to the error. Typically * implementation dependent. * * @return True if the method handled the error, false if it didn't. * Returning false, will cause the {@link PlayerAdapter.Callback#onPlayCompleted(PlayerAdapter)} * being called. */ protected boolean onError(int what, int extra) { return false; } /** * Called to indicate the completion of a seek operation. */ protected void onSeekComplete() { } /** * Called to indicate an info or a warning. * * @param what the type of info or warning. * * @param extra an extra code, specific to the info. Typically * implementation dependent. * @return True if the method handled the info, false if it didn't. * Returning false, will cause the info to be discarded. */ protected boolean onInfo(int what, int extra) { return false; } /** * @see MediaPlayer#setDisplay(SurfaceHolder) */ void setDisplay(SurfaceHolder surfaceHolder) { boolean hadDisplay = mHasDisplay; mHasDisplay = surfaceHolder != null; if (hadDisplay == mHasDisplay) { return; } mPlayer.setDisplay(surfaceHolder); if (mHasDisplay) { if (mInitialized) { getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); } } else { if (mInitialized) { getCallback().onPreparedStateChanged(MediaPlayerAdapter.this); } } } @Override public void setProgressUpdatingEnabled(final boolean enabled) { mHandler.removeCallbacks(mRunnable); if (!enabled) { return; } mHandler.postDelayed(mRunnable, getUpdatePeriod()); } int getUpdatePeriod() { return 16; } @Override public boolean isPlaying() { return mInitialized && mPlayer.isPlaying(); } @Override public long getDuration() { return mInitialized ? mPlayer.getDuration() : -1; } @Override public long getCurrentPosition() { return mInitialized ? mPlayer.getCurrentPosition() : -1; } @Override public void play() { if (!mInitialized || mPlayer.isPlaying()) { return; } mPlayer.start(); getCallback().onPlayStateChanged(MediaPlayerAdapter.this); getCallback().onCurrentPositionChanged(MediaPlayerAdapter.this); } @Override public void pause() { if (isPlaying()) { mPlayer.pause(); getCallback().onPlayStateChanged(MediaPlayerAdapter.this); } } @Override public void seekTo(long newPosition) { if (!mInitialized) { return; } mPlayer.seekTo((int) newPosition); } @Override public long getBufferedPosition() { return mBufferedProgress; } /** * Sets the media source of the player witha given URI. * * @return Returns true if uri represents a new media; false * otherwise. * @see MediaPlayer#setDataSource(String) */ public boolean setDataSource(Uri uri) { if (mMediaSourceUri != null ? mMediaSourceUri.equals(uri) : uri == null) { return false; } mMediaSourceUri = uri; prepareMediaForPlaying(); return true; } private void prepareMediaForPlaying() { reset(); try { if (mMediaSourceUri != null) { mPlayer.setDataSource(mContext, mMediaSourceUri); } else { return; } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setOnPreparedListener(mOnPreparedListener); mPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); mPlayer.setOnErrorListener(mOnErrorListener); mPlayer.setOnSeekCompleteListener(mOnSeekCompleteListener); mPlayer.setOnCompletionListener(mOnCompletionListener); mPlayer.setOnInfoListener(mOnInfoListener); mPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); notifyBufferingStartEnd(); mPlayer.prepareAsync(); getCallback().onPlayStateChanged(MediaPlayerAdapter.this); } /** * @return True if MediaPlayer OnPreparedListener is invoked and got a SurfaceHolder if * {@link PlaybackGlueHost} provides SurfaceHolder. */ @Override public boolean isPrepared() { return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay); } /** * Implements {@link SurfaceHolder.Callback} that can then be set on the * {@link PlaybackGlueHost}. */ class VideoPlayerSurfaceHolderCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { setDisplay(surfaceHolder); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { setDisplay(null); } } }