blob: 3de7aa198598477afadb4da24996f8d2875d3706 [file] [log] [blame]
/*
* 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:
* <ul>
* <li>{@link MediaPlayer#MEDIA_ERROR_UNKNOWN}
* <li>{@link MediaPlayer#MEDIA_ERROR_SERVER_DIED}
* </ul>
* @param extra an extra code, specific to the error. Typically
* implementation dependent.
* <ul>
* <li>{@link MediaPlayer#MEDIA_ERROR_IO}
* <li>{@link MediaPlayer#MEDIA_ERROR_MALFORMED}
* <li>{@link MediaPlayer#MEDIA_ERROR_UNSUPPORTED}
* <li>{@link MediaPlayer#MEDIA_ERROR_TIMED_OUT}
* <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error.
* </ul>
* @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.
* <ul>
* <li>{@link MediaPlayer#MEDIA_INFO_UNKNOWN}
* <li>{@link MediaPlayer#MEDIA_INFO_VIDEO_TRACK_LAGGING}
* <li>{@link MediaPlayer#MEDIA_INFO_VIDEO_RENDERING_START}
* <li>{@link MediaPlayer#MEDIA_INFO_BUFFERING_START}
* <li>{@link MediaPlayer#MEDIA_INFO_BUFFERING_END}
* <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> -
* bandwidth information is available (as <code>extra</code> kbps)
* <li>{@link MediaPlayer#MEDIA_INFO_BAD_INTERLEAVING}
* <li>{@link MediaPlayer#MEDIA_INFO_NOT_SEEKABLE}
* <li>{@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE}
* <li>{@link MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE}
* <li>{@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT}
* </ul>
* @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 <code>true</code> if uri represents a new media; <code>false</code>
* 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);
}
}
}