package com.pili.pldroid.playerdemo; import android.content.Context; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Gravity; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.Toast; import com.pili.pldroid.player.AVOptions; import com.pili.pldroid.player.PLMediaPlayer; import com.pili.pldroid.playerdemo.utils.Utils; import java.io.IOException; import java.util.HashMap; /** * This demo shows how to use PLMediaPlayer API playing video stream */ public class PLMediaPlayerActivity extends VideoPlayerBaseActivity { private static final String TAG = PLMediaPlayerActivity.class.getSimpleName(); private static final int MESSAGE_ID_RECONNECTING = 0x01; private SurfaceView mSurfaceView; private PLMediaPlayer mMediaPlayer; private View mLoadingView; private AVOptions mAVOptions; private int mSurfaceWidth = 0; private int mSurfaceHeight = 0; private String mVideoPath = null; private boolean mIsStopped = false; private boolean mIsActivityPaused = true; private Toast mToast = null; private boolean mIsLiveStreaming = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_media_player); mLoadingView = findViewById(R.id.LoadingView); mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView); mSurfaceView.getHolder().addCallback(mCallback); mVideoPath = getIntent().getStringExtra("videoPath"); mAVOptions = new AVOptions(); int isLiveStreaming = getIntent().getIntExtra("liveStreaming", 1); // the unit of timeout is ms mAVOptions.setInteger(AVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000); mAVOptions.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); mAVOptions.setInteger(AVOptions.KEY_PROBESIZE, 128 * 1024); // Some optimization with buffering mechanism when be set to 1 mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, isLiveStreaming); mIsLiveStreaming = isLiveStreaming == 1; if (mIsLiveStreaming) { mAVOptions.setInteger(AVOptions.KEY_DELAY_OPTIMIZATION, 1); } // 1 -> hw codec enable, 0 -> disable [recommended] int iCodec = getIntent().getIntExtra("mediaCodec", AVOptions.MEDIA_CODEC_SW_DECODE); mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, iCodec); // whether start play automatically after prepared, default value is 1 mAVOptions.setInteger(AVOptions.KEY_START_ON_PREPARED, 0); AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } @Override protected void onDestroy() { super.onDestroy(); release(); AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); audioManager.abandonAudioFocus(null); } @Override protected void onResume() { super.onResume(); mIsActivityPaused = false; } @Override protected void onPause() { super.onPause(); mIsActivityPaused = true; } public void onClickPlay(View v) { if (mIsStopped) { prepare(); } else { mMediaPlayer.start(); } } public void onClickPause(View v) { if (mMediaPlayer != null) { Log.d(TAG, "bitrate = " + mMediaPlayer.getVideoBitrate() + " bps, fps = " + mMediaPlayer.getVideoFps() + ", resolution = " + mMediaPlayer.getResolutionInline()); mMediaPlayer.pause(); } } public void onClickResume(View v) { if (mMediaPlayer != null) { mMediaPlayer.start(); } } public void onClickStop(View v) { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.reset(); } mIsStopped = true; mMediaPlayer = null; } public void releaseWithoutStop() { if (mMediaPlayer != null) { mMediaPlayer.setDisplay(null); } } public void release() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; } } private void prepare() { if (mMediaPlayer != null) { mMediaPlayer.setDisplay(mSurfaceView.getHolder()); if (!mIsLiveStreaming) { mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition()); } return; } try { mMediaPlayer = new PLMediaPlayer(this, mAVOptions); mMediaPlayer.setOnPreparedListener(mOnPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); mMediaPlayer.setOnCompletionListener(mOnCompletionListener); mMediaPlayer.setOnErrorListener(mOnErrorListener); mMediaPlayer.setOnInfoListener(mOnInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); // set replay if completed // mMediaPlayer.setLooping(true); mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); mMediaPlayer.setDataSource(mVideoPath); mMediaPlayer.setDisplay(mSurfaceView.getHolder()); mMediaPlayer.prepareAsync(); } catch (UnsatisfiedLinkError e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { prepare(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mSurfaceWidth = width; mSurfaceHeight = height; } @Override public void surfaceDestroyed(SurfaceHolder holder) { // release(); releaseWithoutStop(); } }; private PLMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new PLMediaPlayer.OnVideoSizeChangedListener() { public void onVideoSizeChanged(PLMediaPlayer mp, int width, int height, int videoSar, int videoDen) { Log.d(TAG, "onVideoSizeChanged: width = " + width + ", height = " + height + ", sar = " + videoSar + ", den = " + videoDen); // resize the display window to fit the screen if (width != 0 && height != 0) { float ratioW = (float) width / (float) mSurfaceWidth; float ratioH = (float) height / (float) mSurfaceHeight; float ratio = Math.max(ratioW, ratioH); width = (int) Math.ceil((float) width / ratio); height = (int) Math.ceil((float) height / ratio); FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(width, height); layout.gravity = Gravity.CENTER; mSurfaceView.setLayoutParams(layout); } } }; private PLMediaPlayer.OnPreparedListener mOnPreparedListener = new PLMediaPlayer.OnPreparedListener() { @Override public void onPrepared(PLMediaPlayer mp) { Log.i(TAG, "On Prepared !"); mMediaPlayer.start(); mIsStopped = false; } }; private PLMediaPlayer.OnInfoListener mOnInfoListener = new PLMediaPlayer.OnInfoListener() { @Override public boolean onInfo(PLMediaPlayer mp, int what, int extra) { Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); switch (what) { case PLMediaPlayer.MEDIA_INFO_BUFFERING_START: mLoadingView.setVisibility(View.VISIBLE); break; case PLMediaPlayer.MEDIA_INFO_BUFFERING_END: case PLMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: mLoadingView.setVisibility(View.GONE); HashMap<String, String> meta = mMediaPlayer.getMetadata(); Log.i(TAG, "meta: " + meta.toString()); showToastTips(meta.toString()); break; case PLMediaPlayer.MEDIA_INFO_SWITCHING_SW_DECODE: Log.i(TAG, "Hardware decoding failure, switching software decoding!"); break; default: break; } return true; } }; private PLMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new PLMediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(PLMediaPlayer mp, int percent) { Log.d(TAG, "onBufferingUpdate: " + percent + "%"); } }; /** * Listen the event of playing complete * For playing local file, it's called when reading the file EOF * For playing network stream, it's called when the buffered bytes played over * <p> * If setLooping(true) is called, the player will restart automatically * And `onCompletion` will not be called */ private PLMediaPlayer.OnCompletionListener mOnCompletionListener = new PLMediaPlayer.OnCompletionListener() { @Override public void onCompletion(PLMediaPlayer mp) { Log.d(TAG, "Play Completed !"); showToastTips("Play Completed !"); finish(); } }; private PLMediaPlayer.OnErrorListener mOnErrorListener = new PLMediaPlayer.OnErrorListener() { @Override public boolean onError(PLMediaPlayer mp, int errorCode) { boolean isNeedReconnect = false; Log.e(TAG, "Error happened, errorCode = " + errorCode); switch (errorCode) { case PLMediaPlayer.ERROR_CODE_INVALID_URI: showToastTips("Invalid URL !"); break; case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND: showToastTips("404 resource not found !"); break; case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: showToastTips("Connection refused !"); break; case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: showToastTips("Connection timeout !"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: showToastTips("Empty playlist !"); break; case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: showToastTips("Stream disconnected !"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_IO_ERROR: showToastTips("Network IO Error !"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_UNAUTHORIZED: showToastTips("Unauthorized Error !"); break; case PLMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: showToastTips("Prepare timeout !"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: showToastTips("Read frame timeout !"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_HW_DECODE_FAILURE: mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, AVOptions.MEDIA_CODEC_SW_DECODE); isNeedReconnect = true; break; case PLMediaPlayer.MEDIA_ERROR_UNKNOWN: break; default: showToastTips("unknown error !"); break; } // Todo pls handle the error status here, reconnect or call finish() release(); if (isNeedReconnect) { sendReconnectMessage(); } else { finish(); } // Return true means the error has been handled // If return false, then `onCompletion` will be called return true; } }; private void showToastTips(final String tips) { if (mIsActivityPaused) { return; } runOnUiThread(new Runnable() { @Override public void run() { if (mToast != null) { mToast.cancel(); } mToast = Toast.makeText(PLMediaPlayerActivity.this, tips, Toast.LENGTH_SHORT); mToast.show(); } }); } private void sendReconnectMessage() { showToastTips("正在重连..."); mLoadingView.setVisibility(View.VISIBLE); mHandler.removeCallbacksAndMessages(null); mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500); } protected Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what != MESSAGE_ID_RECONNECTING) { return; } if (mIsActivityPaused || !Utils.isLiveStreamingAvailable()) { finish(); return; } if (!Utils.isNetworkAvailable(PLMediaPlayerActivity.this)) { sendReconnectMessage(); return; } prepare(); } }; }