我的需求是播放rtsp视频流,最开始用了Android原生播放器MediaPlayer,但是延迟太大了,感觉要将近10秒,而且不能修改缓冲大小,因为底层写死的(根据Android版本设置了固定的缓冲大小),最终用了开源的vlc
目前项目中使用vlc播放了rtsp视频流,并且有拍照,录制的功能
使用vlc播放,vlc使用的是3.0.0的版本
截图和录制需要在aar中加入Android可调用的方法
需要将aar放到libs文件下,在gradle中配置
libvlcaar 配置aar然后添加依赖
compile(name:'libvlc-release-3',ext:'aar')
具体代码实现如下
final ArrayList args =new ArrayList<>();//VLC参数
args.add("--rtsp-tcp");//强制rtsp-tcp,加快加载视频速度
args.add("--aout=opensles");
args.add("--audio-time-stretch");
args.add("--sub-source=marq{marquee=\"%Y-%m-%d,%H:%M:%S\",position=10,color=0xFF0000,size=40}");//这行是可以再vlc窗口右下角添加当前时间的
args.add("-vvv");
mLibVLC =new LibVLC(this, args);
mMediaPlayer =new MediaPlayer(mLibVLC);
Rect surfaceFrame =textureView.getHolder().getSurfaceFrame();
//设置vlc视频铺满布局
mMediaPlayer.getVLCVout().setWindowSize(layout_video.getWidth(), layout_video.getHeight());//宽,高 播放窗口的大小
mMediaPlayer.setAspectRatio(layout_video.getWidth()+":"+layout_video.getHeight());//宽,高 画面大小
mMediaPlayer.setScale(0);//这行必须加,为了让视图填满布局
//添加视图
IVLCVout vout =mMediaPlayer.getVLCVout();
vout.setVideoView(textureView);
vout.attachViews();
Uri uri = Uri.parse(Path.RTSP_URL);//rtsp流地址或其他流地址//"https://media.w3.org/2010/05/sintel/trailer.mp4"
final Media media =new Media(mLibVLC, uri);
int cache =10;
media.addOption(":network-caching=" + cache);
media.addOption(":file-caching=" + cache);
media.addOption(":live-cacheing=" + cache);
media.addOption(":sout-mux-caching=" + cache);
media.addOption(":codec=mediacodec,iomx,all");
mMediaPlayer.setMedia(media);//
media.setHWDecoderEnabled(false, false);//设置后才可以录制和截屏,这行必须放在mMediaPlayer.setMedia(media)后面,因为setMedia会设置setHWDecoderEnabled为true
mMediaPlayer.play();
这样就可以使用vlc播放rtsp视频了
vlc截图
String path1 = Environment.getExternalStorageDirectory().getAbsolutePath()+"/" +"wy";//路径最后不用/,vlc会自己补充
boolean snapshot =mMediaPlayer.getSnapshot(path1);//这个是vlc arr里面添加的方法
//通知系統相冊刷新保持的錄屏,拍照后的图片存放在本地相册
Intent intent =new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(new File(path1)));
sendBroadcast(intent);//不添加的话,相册不会更新,手机图库看不到
vlc 录制
boolean onvideo =false;//onvideo是否开始录制false是未录制
String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/" +"wy";
if (!onvideo){
Thread myThread =new Thread(new Runnable() {
@Override
public void run() {
Log.d("record_video",Thread.currentThread().getName());
if (!isStopThread) {
onvideo =true;
boolean record =mMediaPlayer.record(path);//此方法是vlc 源码添加的,开始录制
Log.d("record1",record+"");
}
}
});
myThread.start();
tv_recordvideo.setText(R.string.textvideo);
}else {
onvideo =false;
boolean record =mMediaPlayer.record(null);//结束录制,路径传null
Log.d("record2",record+"");
tv_recordvideo.setText("");
//通知系統相冊刷新保持的錄屏
Intent intent1 =new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent1.setData(Uri.fromFile(new File(path)));
sendBroadcast(intent1);
}
oppo手机出现不更新画面
E/libEGL: eglCreateWindowSurfaceTmpl:729 error 3003 (EGL_BAD_ALLOC)
E/VLC: [c637e3b0/4338] libvlc gl: cannot create EGL window surface
E/VLC: [c5a38530/4338] libvlc window: request 1 not implemented
E/VLC: [bf07af30/3829] libvlc video output: video output creation failed
E/VLC: [c645ee30/3829] libvlc decoder: failed to create video output
E/VLC: [c637e430/4339] libvlc window: request 1 not implemented
E/BufferQueueProducer: [SurfaceView - com.example.activity.MainActivity#0] connect: already connected (cur=1 req=1)
E/libEGL: eglCreateWindowSurface: native_window_api_connect (win=0xc5a8e808) failed (0xffffffea) (already connected to another API?)
解决方案:
android:hardwareAccelerated="true"//在清单文件中添加了这行,开启了硬件加速,不报上面的错了
然而我用华为手机测试没有这个问题。。。。。
2021.1.25
使用vlc播放器播放smb远程共享文件中的mp4视频
Uri uri = Uri.parse(直接传入smb视频地址);
获取视频长度:
mMediaPlayer.setEventListener,设置监听
IMedia media1 =mMediaPlayer.getMedia();
media1.parseAsync();//这里同步方法拿到的是0,或者-1,我用了异步解决问题
chapter = (int)mMediaPlayer.getLength();
mediaplaye.play();//核心代码,在播放前获取视频长度
mMediaPlayer.setEventListener(new MediaPlayer.EventListener() {
@Override
public void onEvent(MediaPlayer.Event event) {
// Log.d("mMediaPlayer","event.type=="+ event.type);
switch (event.type){
case Opening:
Log.d("mMediaPlayer","Opening=="+Opening);
break;
case MediaChanged:
Log.d("mMediaPlayer","MediaChanged=="+MediaChanged);
break;
case Buffering:
Log.d("mMediaPlayer","Buffering=="+Buffering);
break;
case Playing:
Log.d("mMediaPlayer","Playing=="+Playing);
break;
case Paused:
Log.d("mMediaPlayer","Paused=="+Paused);
break;
case Stopped:
Log.d("mMediaPlayer","Stopped=="+Stopped);
break;
case EndReached:
// textureView.setVisibility(View.INVISIBLE);
// network_layout.setVisibility(View.VISIBLE);
// tv_network.setText("网络连接断开,请重试");
Log.d("mMediaPlayer","EndReached=="+EndReached);
break;
case EncounteredError:
Log.d("mMediaPlayer","EncounteredError=="+EncounteredError);
break;
case TimeChanged:
Log.d("mMediaPlayer","TimeChanged=="+TimeChanged);
break;
case PositionChanged:
if (isPositionChanged){
handler.sendEmptyMessageDelayed(2,0);
isPositionChanged =false;
}
Log.d("mMediaPlayer","PositionChanged=="+PositionChanged);
break;
case SeekableChanged:
IMedia media1 =mMediaPlayer.getMedia();
media1.parseAsync();
chapter = (int)mMediaPlayer.getLength();
int time =chapter /1000;
int miao = time /60;
int i1 = time - (miao *60);
if (miao<10){
if (i1<10){
String i ="0" + miao +":" +"0" + i1;
pb_seek.setMax(chapter /1000);
tv_pro_right.setText(i);
}else if (i1<60){
String i ="0" + miao +":" + i1;
pb_seek.setMax(chapter /1000);
tv_pro_right.setText(i);
}
}else if (miao<60){
if (i1<10){
String i = miao +":" +"0" + i1;
pb_seek.setMax(chapter /1000);
tv_pro_right.setText(i);
}else if (i1<60){
String i = miao +":" + i1;
pb_seek.setMax(chapter /1000);
tv_pro_right.setText(i);
}
}
Log.d("time",time+"");
// Message message = new Message();
// Bundle bundle = new Bundle();
// bundle.putInt("time",chapter);
//
// message.setData(bundle);
// handler.sendMessageDelayed(message,0);
Log.d("chapter", chapter +"==="+ media1.getDuration()+"==="+mMediaPlayer.getChapter()+"==="+mMediaPlayer.getTime()+"==="+mMediaPlayer.getLength()+"==="+mMediaPlayer.getPosition()+"==="+mMediaPlayer.getTitle());
Log.d("mMediaPlayer","SeekableChanged=="+SeekableChanged);
break;
case PausableChanged:
Log.d("mMediaPlayer","PausableChanged=="+PausableChanged);
break;
case LengthChanged:
Log.d("mMediaPlayer","LengthChanged=="+LengthChanged);
break;
case Vout:
Log.d("mMediaPlayer","Vout=="+Vout);
break;
case ESAdded:
Log.d("mMediaPlayer","ESAdded=="+ESAdded);
break;
case ESDeleted:
Log.d("mMediaPlayer","ESDeleted=="+ESDeleted);
break;
case ESSelected :
Log.d("mMediaPlayer","ESSelected=="+ESSelected);
break;
case RecordChanged:
Log.d("mMediaPlayer","RecordChanged=="+RecordChanged);
break;
}
}
});
mMediaPlayer.play();
}catch (Exception e){
}
handler中拿到消息设置进度条
private void setTimer(){
timer =new Timer();
timerTask =new TimerTask() {
@Override
public void run() {
if (mMediaPlayer !=null) {
if (mMediaPlayer.isPlaying()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d("totalTime",totalTime+"");
pb_seek.setProgress(totalTime);
int i =totalTime /60;
if (totalTime <=chapter /1000) {
int miao =totalTime - (i *60);
if (i <10) {
if (miao <10) {
tv_pro_left.setText("0"+i+":0" + miao +"");
}else if (miao <60) {
tv_pro_left.setText("0"+i+":" + miao);
}
}else if (i <60) {
if (miao <10) {
tv_pro_left.setText(i+":0" + miao +"");
}else if (miao <60) {
tv_pro_left.setText(i+":" + miao);
}
}
}else {
return;
}
totalTime =totalTime +1;
}
});
}
}
}
};
timer.schedule(timerTask, 0, 1000);
}
我的progress布局
<androidx.constraintlayout.widget.ConstraintLayout
app:layout_constraintHorizontal_chainStyle="spread_inside"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="MissingConstraints">
app:layout_constraintHorizontal_weight="1"
android:layout_marginLeft="@dimen/dp_3"
android:id="@+id/tv_pro_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
android:text="00:00"/>
style="?android:attr/progressBarStyleHorizontal"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
app:layout_constraintHorizontal_weight="4"
android:id="@+id/pb_seek"
app:layout_constraintRight_toLeftOf="@id/tv_pro_right"
app:layout_constraintLeft_toRightOf="@id/tv_pro_left"
android:layout_width="@dimen/dp_300"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
app:layout_constraintHorizontal_weight="1"
android:layout_marginRight="@dimen/dp_3"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/tv_pro_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"/>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图:
效果图在activity中关闭资源,避免oom
private void cancelTimer(){
if (timer !=null) {
timer.cancel();
timer =null;
}
if (timerTask !=null) {
timerTask.cancel();
timerTask =null;
}
}
public void releasePlayer() {
// mMediaPlayer.setVideoCallback(null, null);
if (mMediaPlayer!=null){
mMediaPlayer.stop();
mMediaPlayer.setEventListener(null);
}
if (vout!=null){
vout.detachViews();
}
if (mLibVLC!=null){
mLibVLC.release();
mLibVLC =null;
}
}
网友评论