Android(安卓)用SurfaceView+PLMediaPlayer 打造一个平板端的网络播放器
16lz
2021-01-26
最近我开始做我接手的项目中最后一个模块:微课。真的太累了,前所未有的累过,感觉身体被掏空。也让我不停的思考,究竟什么是高效,难道人多就是高效?
之前闲来无事的时候写过一个播放器,但好像跟网络播放器不搭,因为不会有任何一家软件公司会把核心视频不做任何处理的挂在服务器上的。然而关于视频的解码与解密我真的是一窍不通啊,不懂c语言,也就无法再android运用c库,再次暴露了自己的弱点。
要求:1.对视频边解码边播放边缓存
2.全屏与半屏
3.手势
效果:
准备:
PLMediaPlayer 使用的github上别人封装的播放器 点击打开链接
遵循的原则:当surfaceview创建的时候,再初始化PLMediaPlayer,当PLMediaPlayer 准备好的时候再播放或 者初始化控制栏与手势
先贴上我写的完整代码:(缺滑动seekbar视频跳转)
package microclassroommodule.customview;import android.content.Context;import android.graphics.Color;import android.media.AudioManager;import android.net.Uri;import android.os.Handler;import android.os.Message;import android.os.PowerManager;import android.text.Spannable;import android.text.SpannableStringBuilder;import android.text.style.ForegroundColorSpan;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.view.GestureDetector;import android.view.Gravity;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.view.View;import android.view.ViewGroup;import android.widget.FrameLayout;import android.widget.ImageView;import android.widget.ProgressBar;import android.widget.RelativeLayout;import android.widget.SeekBar;import android.widget.TextView;import android.widget.Toast;import com.jakewharton.rxbinding.view.RxView;import com.orhanobut.logger.Logger;import com.pili.pldroid.player.AVOptions;import com.pili.pldroid.player.PLMediaPlayer;import com.wyt.hcy.aiyixue_teacher.R;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.TimeUnit;import rx.Observable;import rx.Subscription;import rx.android.schedulers.AndroidSchedulers;import rx.functions.Action1;import utils.BrightnessUtils;import utils.NetUtil;import utils.RxUtils;import utils.TrafficBean;/** * Created by hcy on 2017/5/4 0004. */public class WytVideoPlayView extends FrameLayout implements SurfaceHolder.Callback, PLMediaPlayer.OnPreparedListener, PLMediaPlayer.OnVideoSizeChangedListener, PLMediaPlayer.OnCompletionListener, PLMediaPlayer.OnErrorListener, PLMediaPlayer.OnInfoListener, PLMediaPlayer.OnBufferingUpdateListener, PLMediaPlayer.OnSeekCompleteListener, View.OnTouchListener { private static final String TAG = "WytVideoPlayView"; private SurfaceView mSurfaceView; private PLMediaPlayer mMediaPlayer; private Context currentContext; //播放参数配置 private AVOptions mAVOptions; private ImageView iv_play; private FrameLayout fl_control; private ImageView iv_full; private TextView tv_start; private TextView tv_end; private int mSurfaceWidth = 0; private int mSurfaceHeight = 0; private Toast mToast = null; private FrameLayout myframe; private int initialWidth = 0; private int initialHeight = 0; private Subscription subscription; private SeekBar seekbar; private TextView tv_speed; private boolean isChongLian = false;//判断是否重连 private ImageView iv_back; public long chongLianPosition; public void setInitialWidth(int initialWidth) { this.initialWidth = initialWidth; } public void setInitialHeight(int initialHeight) { this.initialHeight = initialHeight; } private boolean isFullScreen = false;//一开始不是全屏 public boolean isFullScreen() { return isFullScreen; } private View mLoadingView; public WytVideoPlayView(Context context) { super(context); } private Handler mHandler; private TrafficBean trafficBean; private TextView tv_currentNet; private boolean isCountTime = false; private Timer timer; private static final int PLAY_TIME = 0; private String uri; private static final int CONTROLLER_AUTO_HIDE_TIME = 5000;//5s自动隐藏控制条 private WytMediaPlayerimp wytMediaPlayerimp; /** * 手势控制 */ private GestureDetector gestureDetector; private Subscription subscribe_forwardorback; private Subscription subscribe_volume; private Subscription subscribe_bright; /** * surfaceView的Touch监听 * * @param v * @param event * @return */ @Override public boolean onTouch(View v, MotionEvent event) { gestureDetector.onTouchEvent(event); int action = event.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mGestureOperationType = GestureOperationType.NONE; } return true; } private static enum GestureOperationType { NONE,//无手势操作类型 VOLUME,//音量 BRIGHTNESS,//亮度 FASTFORWARDORBACKWARD,//快进 快退 } private GestureOperationType mGestureOperationType = GestureOperationType.NONE; public interface GestureOperationHelper { void SingleTap(); } private GestureOperationHelper gestureOperationHelper; private RelativeLayout rl_forward, rl_volume, rl_bright; private ImageView iv_jindu_tag; private TextView tv_jindu; private ProgressBar pb_jindu, pb_jindu_volume, pb_jindu_bright; /*音量*/ private AudioManager manager; public void setWytMediaPlayerimp(WytMediaPlayerimp wytMediaPlayerimp) { this.wytMediaPlayerimp = wytMediaPlayerimp; } private void countTime() { isCountTime = true; if (timer == null) { timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { if (isCountTime) { mHandler.sendEmptyMessage(PLAY_TIME); } } }, 0, 1000); } } /** * 为了结局点击surfaceview与控制器出现与隐藏的联动bug * func:等上一个Runnable执行完才会执行下一个Runnable */ private Runnable mRunnable = new Runnable() { @Override public void run() { if (fl_control.getVisibility() == View.VISIBLE) { fl_control.setVisibility(View.GONE); } } }; /** * 控制模块的显示或者隐藏 */ private void showOrHideController() { if (fl_control.getVisibility() == View.VISIBLE) { fl_control.setVisibility(View.GONE); } else { fl_control.setVisibility(View.VISIBLE); mHandler.removeCallbacks(mRunnable); mHandler.postDelayed(mRunnable, CONTROLLER_AUTO_HIDE_TIME); } } public void startVideo(final String uri, String previewUri) { try { this.uri = uri; isChongLian = false; if (mMediaPlayer == null) { Logger.e(TAG + "播放:mMediaPlayer==null"); initMediaPlayer(); }else{ try { mMediaPlayer.isPlaying(); release(); initMediaPlayer(); } catch (IllegalStateException e) { initMediaPlayer(); } Logger.e(TAG + "播放:mMediaPlayer=isPlaying"); } mMediaPlayer.setDataSource(currentContext, Uri.parse(uri)); mMediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } } private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); super.handleMessage(msg); if (msg.what == 1) { if (tv_speed != null) { tv_speed.setText(msg.obj + "kb/s"); } if (tv_currentNet != null) { tv_currentNet.setText("当前网络速度:" + msg.obj + "kb/s"); } } if (msg.what == PLAY_TIME) { if (mMediaPlayer != null) { try { long currentPosition = mMediaPlayer.getCurrentPosition(); if (currentPosition > 0) { int progress = (int) ((currentPosition / (float) mMediaPlayer .getDuration()) * 100); Log.i("PRogress::", progress + ""); seekbar.setProgress(progress); tv_start.setText(formatTime(currentPosition)); } else { tv_start.setText("00:00"); seekbar.setProgress(0); } } catch (IllegalStateException ex) { // mMediaPlayer.release(); Logger.i(TAG + "计时时候出异常"); } } } } } public WytVideoPlayView(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.custom_video, this, true); currentContext = context; mHandler = new MyHandler(); initUI(context); trafficBean = new TrafficBean(context, mHandler, 12580); trafficBean.startCalculateNetSpeed(); } private void initUI(Context context) { myframe = (FrameLayout) findViewById(R.id.myframe); iv_back= (ImageView) findViewById(R.id.iv_back); mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView); // 通过surfaceHolder的addCallBack()方法来监听surfaceCreated。 mSurfaceView.getHolder().addCallback(this); /** * 确保底层的表面是一个推送缓冲区表面,用于视频播放和摄像头的浏览 */ RxView.clicks(iv_back).throttleFirst(2,TimeUnit.SECONDS).subscribe(new Action1() { @Override public void call(Void aVoid) { if (isFullScreen){ //就让他还原 nofullScreen(); }else { //退出 if (wytMediaPlayerimp!=null){ wytMediaPlayerimp.finishCurrentView(); } } } }); SurfaceHolder currentSurfaceHolder = mSurfaceView.getHolder(); currentSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); initLoadingView(); initMediaController(); } private void initGestureController() { initGestureControllerUI(); mSurfaceView.setOnTouchListener(this); gestureOperationHelper = new GestureOperationHelper() { @Override public void SingleTap() { } }; gestureDetector = new GestureDetector(currentContext, new GestureDetector.SimpleOnGestureListener() { //双击 @Override public boolean onDoubleTap(MotionEvent e) { if (isFullScreen) { nofullScreen(); showToastTips("还原"); } else { fullScreen(); showToastTips("全屏"); } return true; } @Override public void onShowPress(MotionEvent e) { super.onShowPress(e); showOrHideController(); } /** * ,一次单独的轻击抬起操作,也就是轻击一下屏幕 * @param e * @return */ @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (mGestureOperationType == GestureOperationType.NONE) { if (Math.abs(distanceX) > Math.abs(distanceY)) { //如果X轴的绝对值大于Y轴的绝对值 说明此时在快进或者快退 Log.i(TAG + "手势::", "快进快退"); mGestureOperationType = GestureOperationType.FASTFORWARDORBACKWARD; } else { if (e1.getX() < mSurfaceView.getWidth() / 2) { //说名在屏幕的左侧,左侧调节亮度 Log.i(TAG + "手势::", "亮度"); mGestureOperationType = GestureOperationType.BRIGHTNESS; } else { //右侧调节音量 Log.i(TAG + "手势::", "音量"); mGestureOperationType = GestureOperationType.VOLUME; } } } return DealWithScrollGestureOperation(e1, e2, distanceX, distanceY); } }); } private void initGestureControllerUI() { /*快进与快退*/ rl_forward = (RelativeLayout) findViewById(R.id.rl_forward); iv_jindu_tag = (ImageView) findViewById(R.id.iv_jindu_tag); tv_jindu = (TextView) findViewById(R.id.tv_jindu); pb_jindu = (ProgressBar) findViewById(R.id.pb_jindu); /*音量*/ rl_volume = (RelativeLayout) findViewById(R.id.rl_volume); pb_jindu_volume = (ProgressBar) findViewById(R.id.pb_jindu_volume); // iv_volume_tag = (ImageView) findViewById(R.id.iv_volume_tag); /*亮度*/ rl_bright = (RelativeLayout) findViewById(R.id.rl_bright); pb_jindu_bright = (ProgressBar) findViewById(R.id.pb_jindu_bright); } private void initLoadingView() { mLoadingView = findViewById(R.id.LoadingView); tv_speed = (TextView) findViewById(R.id.tv_speed); } private void initMediaController() { fl_control = (FrameLayout) findViewById(R.id.fl_control); fl_control.setVisibility(VISIBLE);//默认是隐藏的 iv_play = (ImageView) findViewById(R.id.iv_play); //默认他应该是播放状态 iv_full = (ImageView) findViewById(R.id.iv_full); iv_full.setSelected(false);//默认是缩小状态 tv_start = (TextView) findViewById(R.id.tv_start); tv_end = (TextView) findViewById(R.id.tv_end); seekbar = (SeekBar) findViewById(R.id.seekbar); // seekbar.setOnSeekBarChangeListener(this); tv_currentNet = (TextView) findViewById(R.id.tv_currentNet); RxView.clicks(iv_play).throttleFirst(1, TimeUnit.SECONDS).subscribe(new Action1() { @Override public void call(Void aVoid) { if (iv_play.isSelected()) { //正在播放 if (mMediaPlayer != null) { Log.i(TAG, "bitrate = " + mMediaPlayer.getVideoBitrate() + " bps, fps = " + mMediaPlayer.getVideoFps() + ", resolution = " + mMediaPlayer.getResolutionInline()); mMediaPlayer.pause(); iv_play.setSelected(false); } } else { //播放暂停 if (mMediaPlayer != null) { mMediaPlayer.start(); iv_play.setSelected(true); } } } }); RxView.clicks(iv_full).throttleFirst(2, TimeUnit.SECONDS).subscribe(new Action1() { @Override public void call(Void aVoid) { if (!isFullScreen) { //设置全屏状态,所谓全屏状态,就是将容器满屏 //注意点:容器满屏的高度!=屏幕高度 //容器满屏的高度==屏幕高度-状态栏的高度 fullScreen(); } else { nofullScreen(); } } }); } //全屏 public void fullScreen() { DisplayMetrics dm = getResources().getDisplayMetrics(); Logger.i(TAG + "屏幕" + dm.widthPixels + "/" + dm.heightPixels); ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = dm.widthPixels; layoutParams.height = dm.heightPixels - getStatusBarHeight(); setLayoutParams(layoutParams); isFullScreen = true; iv_full.setSelected(isFullScreen); mSurfaceView.setLayoutParams(layoutParams); } //还原 public void nofullScreen() { ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = initialWidth; layoutParams.height = initialHeight; setLayoutParams(layoutParams); isFullScreen = false; iv_full.setSelected(isFullScreen); mSurfaceView.setLayoutParams(layoutParams); } public WytVideoPlayView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 此时surface已创建 * * @param holder */ @Override public void surfaceCreated(SurfaceHolder holder) { initAVOptions();//初始化播放参数 initMediaPlayer();//初始化播放器 // } private void initAVOptions() { mAVOptions = new AVOptions(); //解码类型 // 解码方式: // codec=AVOptions.MEDIA_CODEC_HW_DECODE,硬解 // codec=AVOptions.MEDIA_CODEC_SW_DECODE, 软解 // codec=AVOptions.MEDIA_CODEC_AUTO, 硬解优先,失败后自动切换到软解 // 默认值是:MEDIA_CODEC_SW_DECODE mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, AVOptions.MEDIA_CODEC_AUTO); // 准备超时时间,包括创建资源、建立连接、请求码流等,单位是 ms // 默认值是:无 mAVOptions.setInteger(AVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000); // 读取视频流超时时间,单位是 ms // 默认值是:10 * 1000 mAVOptions.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); // 当前播放的是否为在线直播,如果是,则底层会有一些播放优化 // 默认值是:0 mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, 0); // 默认的缓存大小,单位是 ms // 默认值是:2000 mAVOptions.setInteger(AVOptions.KEY_CACHE_BUFFER_DURATION, 2000); // 最大的缓存大小,单位是 ms // 默认值是:4000 mAVOptions.setInteger(AVOptions.KEY_MAX_CACHE_BUFFER_DURATION, 4000); // 是否自动启动播放,如果设置为 1,则在调用 `prepareAsync` 或者 `setVideoPath` 之后自动启动播放,无需调用 `start()` // 默认值是:1 mAVOptions.setInteger(AVOptions.KEY_START_ON_PREPARED, 1); // 播放前最大探测流的字节数,单位是 byte // 默认值是:128 * 1024 mAVOptions.setInteger(AVOptions.KEY_PROBESIZE, 128 * 1024);/* //liveStreaming 直播 1 点播0 mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, 0); // 是否开启"延时优化",只在在线直播流中有效 // mAVOptions.setInteger(AVOptions.KEY_DELAY_OPTIMIZATION, 1);*/ } private void initMediaPlayer() { /*//如果media已创建 if (mMediaPlayer != null) { Logger.i(TAG + "mMediaPlayer != null"); if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); //mMediaPlayer.start(); mMediaPlayer.setDisplay(mSurfaceView.getHolder()); mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition()); Logger.i(TAG + "重连:isPlaying"); } else { Logger.i(TAG + "重连:NoPlaying"); mMediaPlayer.setDisplay(mSurfaceView.getHolder()); mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition()); } //mMediaPlayer.setDisplay(mSurfaceView.getHolder()); return; *//* mMediaPlayer.pause(); mMediaPlayer.setDisplay(mSurfaceView.getHolder()); mMediaPlayer.prepareAsync(); mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition()); Logger.i(TAG + "MediaPlayer已经创建"); return;*//* }*/ try { Logger.i(TAG + "mMediaPlayer == null"); //mediaplayer没创建 mMediaPlayer = new PLMediaPlayer(currentContext, mAVOptions); mMediaPlayer.setLooping(false);//不循环播放 /* * * 注册一个回调函数,在seek操作完成后调用。*/ mMediaPlayer.setOnSeekCompleteListener(this); //注册一个回调函数,在视频预处理完成后调用 mMediaPlayer.setOnPreparedListener(this); //注册一个回调函数,在视频大小已知或更新后调用 mMediaPlayer.setOnVideoSizeChangedListener(this); //监听视频播放结束 mMediaPlayer.setOnCompletionListener(this); //播放视频出错的监听 mMediaPlayer.setOnErrorListener(this); //注册一个回调函数,在有警告或错误信息时调用 mMediaPlayer.setOnInfoListener(this); //监听事件,网络流媒体的缓冲监听 mMediaPlayer.setOnBufferingUpdateListener(this); mMediaPlayer.setWakeMode(currentContext, PowerManager.PARTIAL_WAKE_LOCK); mMediaPlayer.setDisplay(mSurfaceView.getHolder()); /* if (wytMediaPlayerimp != null) { wytMediaPlayerimp.surfaceAndMediaPlayerCreated(); }*/ if (isChongLian) { try { mMediaPlayer.setDataSource(currentContext, Uri.parse(uri)); mMediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } } //if (uri != null && !TextUtils.isEmpty(uri)) { Logger.i(TAG + "MediaPlayer初始化"); } catch (UnsatisfiedLinkError e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } /*catch (IOException e) { e.printStackTrace(); }*/ } /** * 当SurfaceView 的底层表面的宽度、高度或者其他参数发生变化时 调用 * * @param holder * @param format * @param width * @param height */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i(TAG, "surfaceChanged" + width + "/" + height); mSurfaceWidth = width; mSurfaceHeight = height; } /** * 当销毁 SurfaceView的底层表面时,调用 * * @param holder */ @Override public void surfaceDestroyed(SurfaceHolder holder) { releaseWithoutStop(); } public void releaseWithoutStop() { if (mMediaPlayer != null) { mMediaPlayer.setDisplay(null); } } @Override public void onPrepared(PLMediaPlayer plMediaPlayer) { Logger.i(TAG + "视频准备好了"); //在mediaplayer准备好的时候再初始化 initGestureController();//初始化手势 if (isChongLian) { mMediaPlayer.seekTo(chongLianPosition); isChongLian = false; showToastTips("重连成功"); } else { //隐藏预览图 mMediaPlayer.start(); iv_play.setSelected(true); } //获取视屏的长度 tv_end.setText(formatTime(mMediaPlayer.getDuration())); } private String formatTime(long time) { SimpleDateFormat formatter = new SimpleDateFormat("mm:ss"); return formatter.format(new Date(time)); } /** * 当视频的大小发生变化的时候调用一次 * 当指定的数据源和读取视频的元数据后将至少调用它一次 */ @Override public void onVideoSizeChanged(PLMediaPlayer plMediaPlayer, int width, int height, int videoSar, int videoDen) { Log.i(TAG, "onVideoSizeChanged: width = " + width + ", height = " + height + ", sar = " + videoSar + ", den = " + videoDen); if (width != 0 && height != 0) { float ratioW = (float) width / (float) mSurfaceWidth; float ratioH = (float) height / (float) mSurfaceHeight; float ratio = Math.min(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); } } @Override public void onCompletion(PLMediaPlayer plMediaPlayer) { Log.i(TAG, "Play Completed !"); showToastTips("视频播放完毕"); if (iv_play != null) { iv_play.setSelected(false); //按钮显示暂停 } } @Override public boolean onError(PLMediaPlayer plMediaPlayer, int errorCode) { Log.e(TAG, "Error happened, errorCode = " + errorCode); Log.e(TAG, "Error happened, errorCode = " + errorCode); boolean isNeedReconnect = false; switch (errorCode) { case PLMediaPlayer.ERROR_CODE_INVALID_URI: showToastTips("无效的视频链接!"); Logger.e(TAG + "无效的视频链接"); break; case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND: showToastTips("播放资源不存在!"); Logger.e(TAG + "播放资源不存在"); break; case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: showToastTips("服务器拒绝连接!"); Logger.e(TAG + "服务器拒绝连接"); break; case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: showToastTips("连接超时!"); Logger.e(TAG + "连接超时"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: showToastTips("空的播放列表!"); Logger.e(TAG + "空的播放列表"); break; case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: showToastTips("与服务器连接断开!"); Logger.e(TAG + "与服务器连接断开"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_IO_ERROR: showToastTips("网络异常!"); Logger.e(TAG + "网络异常"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_UNAUTHORIZED: showToastTips("未授权,播放一个禁播的流!"); Logger.e(TAG + "未授权,播放一个禁播的流!"); break; case PLMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT: showToastTips("播放器准备超时!"); Logger.e(TAG + "播放器准备超时"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT: showToastTips("读取数据超时!"); Logger.e(TAG + "读取数据超时"); isNeedReconnect = true; break; case PLMediaPlayer.ERROR_CODE_HW_DECODE_FAILURE: showToastTips("硬解码失败!"); Logger.e(TAG + "硬解码失败"); mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, AVOptions.MEDIA_CODEC_SW_DECODE); isNeedReconnect = true; break; case PLMediaPlayer.MEDIA_ERROR_UNKNOWN: showToastTips("未知错误!"); Logger.e(TAG + "未知错误"); break; default: showToastTips("unknown error !"); break; } if (isNeedReconnect) { if (mMediaPlayer != null) { chongLianPosition = mMediaPlayer.getCurrentPosition(); } release();//释放MediaPlayer sendReconnectMessage(); } else { //不需要重连 isChongLian = false; release();//释放MediaPlayer } return true; } private void sendReconnectMessage() { showToastTips("正在重连..."); isChongLian = true; mLoadingView.setVisibility(View.VISIBLE); RxUtils.unsubscribe(subscription); subscription = Observable.timer(500, TimeUnit.MILLISECONDS).subscribe(new Action1() { @Override public void call(Long aLong) { if (NetUtil.getNetWorkState(currentContext) == -1) { //没有网 Logger.i(TAG + "没网"); sendReconnectMessage(); } else { initMediaPlayer(); } } }); // mHandler.removeCallbacksAndMessages(null); // mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500); } public void release() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; } } private void showToastTips(final String tips) {// if (mIsActivityPaused) {// return;// } Observable.just(tips).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1() { @Override public void call(String s) { if (mToast != null) { mToast.cancel(); } mToast = Toast.makeText(currentContext, tips, Toast.LENGTH_SHORT); mToast.show(); } }); } /** * 当出现关于播放媒体的特定信息或者需要发出警告的时候,将调用onInfo * * @return */ @Override public boolean onInfo(PLMediaPlayer plMediaPlayer, int what, int extra) { // Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); // Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); switch (what) { case PLMediaPlayer.MEDIA_INFO_BUFFERING_START: //开始缓冲 Log.i(TAG, "开始缓冲, what = " + what + ", extra = " + extra); mLoadingView.setVisibility(View.VISIBLE); isCountTime = false; /* long videoBitrate = mMediaPlayer.getVideoBitrate(); tv_speed.setText((videoBitrate / 1024) + "kb/s");*/ break; case PLMediaPlayer.MEDIA_INFO_BUFFERING_END: //停止缓冲 Log.i(TAG, "停止缓冲, what = " + what + ", extra = " + extra); mLoadingView.setVisibility(View.GONE); isCountTime = true; break; case PLMediaPlayer.MEDIA_INFO_UNKNOWN: //未知消息 Log.i(TAG, "未知消息, what = " + what + ", extra = " + extra); break; case PLMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED: //获取到视频的播放角度 Log.i(TAG, "获取到视频的播放角度, what = " + what + ", extra = " + extra); break; case PLMediaPlayer.MEDIA_INFO_VIDEO_GOP_TIME: //获取视频的I帧间隔 Log.i(TAG, "获取视频的I帧间隔, what = " + what + ", extra = " + extra); break; case PLMediaPlayer.MEDIA_INFO_SWITCHING_SW_DECODE: //硬解失败,自动切换软解 Log.i(TAG, "硬解失败,自动切换软解, what = " + what + ", extra = " + extra); break; case PLMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START: // 第一帧音频已成功播放 Log.i(TAG, "第一帧音频已成功播放, what = " + what + ", extra = " + extra); mLoadingView.setVisibility(View.GONE); // time=0; countTime(); break; case PLMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: //第一帧视频已成功渲染 Log.i(TAG, "第一帧视频已成功渲染, what = " + what + ", extra = " + extra); HashMap meta = mMediaPlayer.getMetadata(); Log.i(TAG, "meta: " + meta.toString()); showToastTips(meta.toString()); break; /* case PLMediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED: //显示 下载速度 Logger.e("download rate:" + arg2); break;*/ default: break; } return true; } @Override public void onBufferingUpdate(PLMediaPlayer plMediaPlayer, int percent) { Log.i(TAG, "onBufferingUpdate: " + percent + "%"); seekbar.setSecondaryProgress(percent); } //获取状态栏的高度 private int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; } @Override public void onSeekComplete(PLMediaPlayer plMediaPlayer) { Log.i(TAG, "onSeekComplete:"); } private boolean DealWithScrollGestureOperation(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Logger.i("HHHGGDD::" + distanceY); if (isFullScreen) { //满屏的时候才能使用手势 /** * 手势操作的时候如果控制栏是显示的,就要影长 */ if (fl_control.getVisibility() == View.VISIBLE) { fl_control.setVisibility(View.GONE); mHandler.removeCallbacks(mRunnable); } if (mGestureOperationType == GestureOperationType.FASTFORWARDORBACKWARD) { //快进,快退操作的处理 if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) { //不执行 Logger.i(TAG + "手势:" + "不执行"); return false; } /* if (iv_voice.getVisibility()==View.VISIBLE){ iv_voice.setVisibility(View.GONE); }*/ /** * 向右边滑动disX为负数 向左边滑动disX为正数 * func:无论快进还是快退都是一分钟 * */ if (rl_forward.getVisibility() == GONE) { rl_forward.setVisibility(VISIBLE); } long pos = mMediaPlayer.getCurrentPosition(); long total = mMediaPlayer.getDuration(); if (distanceX < 0) { //右话 // iv_flag.setImageResource(R.mipmap.fast); pos = pos + 1 * 10 * 1000; iv_jindu_tag.setSelected(false); } else if (distanceX > 0) { //左话 // iv_flag.setImageResource(R.mipmap.rewind); pos = pos - 1 * 10 * 1000; iv_jindu_tag.setSelected(true); } // mHandler.removeMessages(11); // mHandler.sendEmptyMessageDelayed(11, 5000); int progress = (int) ((pos / (float) total) * 100); pb_jindu.setProgress(progress); Log.i("GGGGG:", progress + " "); seekbar.setProgress(progress); if (pos < 0) { pos = 0; } else if (pos > total) { pos = total; } long currentPosition = mMediaPlayer.getCurrentPosition(); String start = null; if (currentPosition > 0) { start = formatTime(currentPosition); } else { start = "00:00"; } String str = new StringBuffer().append(start).append("\t/\t").append(tv_end.getText().toString()).toString(); SpannableStringBuilder builder = new SpannableStringBuilder(str); //ForegroundColorSpan 为文字前景色,BackgroundColorSpan为文字背景色 ForegroundColorSpan redSpan = new ForegroundColorSpan(Color.parseColor("#00b776")); builder.setSpan(redSpan, 0, str.indexOf("/"), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); tv_jindu.setText(builder); /** * 2000ms后隐藏快进快退的界面 */ final long finalPos = pos; RxUtils.unsubscribe(subscribe_forwardorback); subscribe_forwardorback = Observable.timer(1000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1() { @Override public void call(Long aLong) { if (rl_forward.getVisibility() == VISIBLE) { mMediaPlayer.seekTo(finalPos); rl_forward.setVisibility(GONE); } } }); } else if (mGestureOperationType == GestureOperationType.VOLUME) { /*音量*/ if (rl_volume.getVisibility() == GONE) { rl_volume.setVisibility(VISIBLE); }// iv_flag.setImageResource(R.mipmap.huatong);//// iv_voice.setVisibility(View.VISIBLE);//// if (imageView_main_show.getVisibility() == View.VISIBLE) {// imageView_main_show.setVisibility(View.GONE);// } manager = (AudioManager) currentContext.getSystemService(Context.AUDIO_SERVICE); int maxVolume = manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); int currentVolume = manager.getStreamVolume(AudioManager.STREAM_MUSIC); Logger.i("音量++" + distanceY + "?" + maxVolume + "/" + currentVolume); if (Math.abs(distanceY) >= 2) { if (distanceY < 0) { currentVolume--; } else if (distanceY > 0) { currentVolume++; } } if (currentVolume >= 15) { currentVolume = 15; showToastTips("声音已经调到最大了,请保护耳朵奥"); } else if (currentVolume <= 0) { currentVolume = 0; } pb_jindu_volume.setMax(maxVolume); pb_jindu_volume.setProgress(currentVolume); Log.i("音量:当前", currentVolume + ""); Log.i("音量:最大", maxVolume + ""); manager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0); RxUtils.unsubscribe(subscribe_volume); subscribe_volume = Observable.timer(1000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1() { @Override public void call(Long aLong) { if (rl_volume.getVisibility() == VISIBLE) { rl_volume.setVisibility(GONE); } } }); /* * 发现音量最小0 最大15 */ /** * 上滑是正数,下滑是负数 */ /* float percent = distanceY / (float) mSurfaceView.getMeasuredHeight(); float volumeOffsetAccurate = maxVolume * percent; int volumeOffset = (int) volumeOffsetAccurate; Logger.i("音量:"+"percent::"+percent+"/volumeOffsetAccurate:"+volumeOffsetAccurate+"/volumeOffset"+volumeOffset); if (volumeOffset == 0 && Math.abs(volumeOffsetAccurate) > 0.2f) { if (distanceY > 0) { volumeOffset = 1; } else if (distanceY < 0) { volumeOffset = -1; } } currentVolume += volumeOffset; if (currentVolume < 0) { currentVolume = 0; } else if (currentVolume >= maxVolume) { currentVolume = maxVolume; }*/ /* if (currentVolume == 0) { iv_voice.setImageResource(R.mipmap.vc_0); } else if (currentVolume <= 2) { iv_voice.setImageResource(R.mipmap.vc_1); } else if (currentVolume <= 4) { iv_voice.setImageResource(R.mipmap.vc_2); } else if (currentVolume <= 6) { iv_voice.setImageResource(R.mipmap.vc_3); } else if (currentVolume <= 8) { iv_voice.setImageResource(R.mipmap.vc_4); } else if (currentVolume <= 11) { iv_voice.setImageResource(R.mipmap.vc_5); } else if (currentVolume <= 14) { iv_voice.setImageResource(R.mipmap.vc_6); } else if (currentVolume == 15) { iv_voice.setImageResource(R.mipmap.vc_7); }*/ } else { if (mGestureOperationType == GestureOperationType.BRIGHTNESS) { if (rl_bright.getVisibility() == GONE) { rl_bright.setVisibility(VISIBLE); } // float percent = distanceY / (float) mSurfaceView.getMeasuredHeight(); int currentBrightness = BrightnessUtils.getScreenBrightness(currentContext); Log.i("FFFFFWW", currentBrightness + ""); if (Math.abs(distanceY) >= 2) { if (distanceY < 0) { currentBrightness -= 10; } else if (distanceY > 0) { currentBrightness += 10; } } pb_jindu_bright.setMax(255); pb_jindu_bright.setProgress(currentBrightness); BrightnessUtils.setSystemBrightness(currentContext, currentBrightness); RxUtils.unsubscribe(subscribe_bright); subscribe_bright = Observable.timer(1000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1() { @Override public void call(Long aLong) { if (rl_bright.getVisibility() == VISIBLE) { rl_bright.setVisibility(GONE); } } }); } } } return true; }}
custom_video
<?xml version="1.0" encoding="utf-8"?>
我遇到的问题: 1.因为是平板端,默认都是横屏的,所以我就需要什么重力切换横竖屏的功能,只需要将屏幕切换到满屏以及还原。所以我必须要记录屏幕的宽高以及原来组件的宽高。测量组件宽高的代码宽高之测量一次():
ViewTreeObserver vto2 = ll_video.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { ll_video.getViewTreeObserver().removeGlobalOnLayoutListener(this); Logger.i("当前宽度DDD:" + ll_video.getWidth() + "/" + ll_video.getHeight()); wytVideoPlayView.setInitialWidth(ll_video.getWidth()); wytVideoPlayView.setInitialHeight(ll_video.getHeight()); ViewGroup.LayoutParams layoutParams = wytVideoPlayView.getLayoutParams(); layoutParams.width = ll_video.getWidth(); layoutParams.height = ll_video.getHeight(); wytVideoPlayView.setLayoutParams(layoutParams); } });
至于屏幕的宽高,这里的高度必须要减去系统栏的高度: //获取状态栏的高度 private int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; }
满屏操作 public void fullScreen() { DisplayMetrics dm = getResources().getDisplayMetrics(); Logger.i(TAG + "屏幕" + dm.widthPixels + "/" + dm.heightPixels); ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = dm.widthPixels; layoutParams.height = dm.heightPixels - getStatusBarHeight(); setLayoutParams(layoutParams); isFullScreen = true; iv_full.setSelected(isFullScreen); mSurfaceView.setLayoutParams(layoutParams); }
2.用于缓冲的加载的view究竟什么时候显示:
我是在缓冲的时候显示,在缓冲结束隐藏,在获取第一帧图隐藏
3.缓冲的时候如何加载当前网速:
package utils;import android.content.Context;import android.content.pm.ApplicationInfo;import android.content.pm.PackageManager;import android.net.TrafficStats;import android.os.Handler;import android.os.Message;import android.util.Log;import java.io.FileNotFoundException;import java.io.IOException;import java.io.RandomAccessFile;import java.math.BigDecimal;import java.util.Timer;import java.util.TimerTask;/** * Created by hcy on 2017/5/8 0008. */public class TrafficBean { /** * static long getMobileRxBytes()//获取通过Mobile连接收到的字节总数,但不包含WiFi static long * getMobileRxPackets()//获取Mobile连接收到的数据包总数 static long * getMobileTxBytes()//Mobile发送的总字节数 static long * getMobileTxPackets()//Mobile发送的总数据包数 static long * getTotalRxBytes()//获取总的接受字节数,包含Mobile和WiFi等 static long * getTotalRxPackets()//总的接受数据包数,包含Mobile和WiFi等 static long * getTotalTxBytes()//总的发送字节数,包含Mobile和WiFi等 static long * getTotalTxPackets()//发送的总数据包数,包含Mobile和WiFi等 static long * getUidRxBytes(int uid)//获取某个网络UID的接受字节数 static long getUidTxBytes(int * uid) //获取某个网络UID的发送字节数 */ /** * 不支持状态【标识变量】 */ private static final int UNSUPPORT = -1; /** * 打印信息标志 */ private static final String TAG = "TrafficBean"; /** * 当前对象实例 */ private static TrafficBean instance; /** * 当前应用的uid */ static int UUID; /** * 上一次记录网络字节流 */ private long preRxBytes = 0; /** * */ private Timer mTimer = null; /** * 上下文对象 */ private Context context; /** * 消息处理器 */ private Handler handler; /** * 更新频率 */ private final int UPDATE_FREQUENCY = 1; private int times = 1; /** * 构造方法 * * @param context * @param handler * @param uid */ public TrafficBean(Context context, Handler handler, int uid) { this.context = context; this.handler = handler; this.UUID = uid; } public TrafficBean(Context context, Handler handler) { this.context = context; this.handler = handler; } /** * 获取实例对象 * * @param context * @param handler * @return */ public static TrafficBean getInstance(Context context, Handler handler) { if (instance == null) { instance = new TrafficBean(context, handler); } return instance; } /** * 获取总流量 * * @return */ public long getTrafficInfo() { long recTraffic = UNSUPPORT;//下载流量 long sendTraffic = UNSUPPORT;//上传流量 recTraffic = getRecTraffic(); sendTraffic = getSendTraffic(); if (recTraffic == UNSUPPORT || sendTraffic == UNSUPPORT) { return UNSUPPORT; } else { return recTraffic + sendTraffic; } } /** * 获取上传流量 * * @return */ private long getSendTraffic() { long sendTraffic = UNSUPPORT; sendTraffic = TrafficStats.getUidTxBytes(UUID); if (sendTraffic == UNSUPPORT) { return UNSUPPORT; } RandomAccessFile rafSend = null; String sndPath = "/proc/uid_stat/" + UUID + "/tcp_snd"; try { rafSend = new RandomAccessFile(sndPath, "r"); sendTraffic = Long.parseLong(rafSend.readLine()); } catch (FileNotFoundException e) { Log.e(TAG, "FileNotFoundException: " + e.getMessage()); sendTraffic = UNSUPPORT; } catch (IOException e) { Log.e(TAG, "IOException: " + e.getMessage()); e.printStackTrace(); } finally { try { if (rafSend != null) rafSend.close(); } catch (IOException e) { Log.w(TAG, "Close RandomAccessFile exception: " + e.getMessage()); } } return sendTraffic; } /** * 获取下载流量 * 某个应用的网络流量数据保存在系统的 * /proc/uid_stat/$UID/tcp_rcv | tcp_snd文件中 * * @return */ private long getRecTraffic() { long recTraffic = UNSUPPORT; recTraffic = TrafficStats.getUidRxBytes(UUID); if (recTraffic == UNSUPPORT) { return UNSUPPORT; } Log.i(TAG, recTraffic + " ---1"); //访问数据文件 RandomAccessFile rafRec = null; String rcvPath = "/proc/uid_stat/" + UUID + "/tcp_rcv"; try { rafRec = new RandomAccessFile(rcvPath, "r"); recTraffic = Long.parseLong(rafRec.readLine()); // 读取流量统计 } catch (FileNotFoundException e) { Log.e(TAG, "FileNotFoundException: " + e.getMessage()); recTraffic = UNSUPPORT; } catch (IOException e) { Log.e(TAG, "IOException: " + e.getMessage()); e.printStackTrace(); } finally { try { if (rafRec != null) rafRec.close(); } catch (IOException e) { Log.w(TAG, "Close RandomAccessFile exception: " + e.getMessage()); } } Log.i("test", recTraffic + "--2"); return recTraffic; } /** * 获取当前下载流量总和 * * @return */ public static long getNetworkRxBytes() { return TrafficStats.getTotalRxBytes(); } /** * 获取当前上传流量总和 * * @return */ public static long getNetworkTxBytes() { return TrafficStats.getTotalTxBytes(); } /** * 获取当前网速 * * @return */ public double getNetSpeed() { long curRxBytes = getNetworkRxBytes(); if (preRxBytes == 0) preRxBytes = curRxBytes; long bytes = curRxBytes - preRxBytes; preRxBytes = curRxBytes; //int kb = (int) Math.floor(bytes / 1024 + 0.5); double kb = (double) bytes / (double) 1024; BigDecimal bd = new BigDecimal(kb); return bd.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 开启流量监控 */ public void startCalculateNetSpeed() { preRxBytes = getNetworkRxBytes(); if (mTimer != null) { mTimer.cancel(); mTimer = null; } if (mTimer == null) { mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { if (times == UPDATE_FREQUENCY) { Message msg = new Message(); msg.what = 1; //msg.arg1 = getNetSpeed(); msg.obj = getNetSpeed(); handler.sendMessage(msg); times = 1; } else { times++; } } }, 1000, 1000); } } /** * 停止网速监听计算 */ public void stopCalculateNetSpeed() { if (mTimer != null) { mTimer.cancel(); mTimer = null; } } /** * 获取当前应用uid * * @return */ public int getUid() { try { PackageManager pm = context.getPackageManager(); //修改 ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); return ai.uid; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return -1; }}
4.手势滑动不准怎么解决:
gestureDetector = new GestureDetector(currentContext, new GestureDetector.SimpleOnGestureListener() { //双击 @Override public boolean onDoubleTap(MotionEvent e) { if (isFullScreen) { nofullScreen(); showToastTips("还原"); } else { fullScreen(); showToastTips("全屏"); } return true; } @Override public void onShowPress(MotionEvent e) { super.onShowPress(e); showOrHideController(); } /** * ,一次单独的轻击抬起操作,也就是轻击一下屏幕 * @param e * @return */ @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (mGestureOperationType == GestureOperationType.NONE) { if (Math.abs(distanceX) > Math.abs(distanceY)) { //如果X轴的绝对值大于Y轴的绝对值 说明此时在快进或者快退 Log.i(TAG + "手势::", "快进快退"); mGestureOperationType = GestureOperationType.FASTFORWARDORBACKWARD; } else { if (e1.getX() < mSurfaceView.getWidth() / 2) { //说名在屏幕的左侧,左侧调节亮度 Log.i(TAG + "手势::", "亮度"); mGestureOperationType = GestureOperationType.BRIGHTNESS; } else { //右侧调节音量 Log.i(TAG + "手势::", "音量"); mGestureOperationType = GestureOperationType.VOLUME; } } } return DealWithScrollGestureOperation(e1, e2, distanceX, distanceY); } });
要加上这一段:
/** * surfaceView的Touch监听 * * @param v * @param event * @return */ @Override public boolean onTouch(View v, MotionEvent event) { gestureDetector.onTouchEvent(event); int action = event.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mGestureOperationType = GestureOperationType.NONE; } return true; }
5.缓存:这个播放器不支持缓存,但它基于的 ijkplayer 是提供的。我用的是这个
https://github.com/ksvc/ksyhttpcache_android
更多相关文章
- 引导界面微场景交互设计与技术实现V1.0
- Android使用PowerImageView实现播放强大的ImageView动画效果
- Android(安卓)开启个人热点时 获取连接人数以及连接上的设备信息
- Android中利用ContentResolver获取本地音乐和相片
- Android(安卓)利用阿里UTD库 获取手机唯一标识
- Android(安卓)图片下载本地内存的缓存方式
- 在Android中播放音频和视频
- Android跳转系统界面_大全集
- Android(安卓)UI总结 Android(安卓)和H5 字体大小适配