近几天的主要问题在于播放器。原来采用的方案最终发现存在问题无法实施,只好临时替换。想起最开始就曾经想用的android提供的VideoView和MediaPlayer组件,开始替换,然后就是一堆问题。网上查到的大部分资料都没有解决问题,无意中搜到这篇文章,感觉还不错的问题总结,希望能帮我解决问题吧,也希望对正在学习的人能有所帮助。
主要内容来自于(原文网址):http://www.boyunjian.com/do/article/snapshot.do?uid=3453185252600635258,原文标题:视频播放器之遇到问题篇 ,转载请注明。(不知道是否本身就是被转载,已经找不到作者的名字了)
内容如下:
视频播放器之遇到问题篇
---------------------------------------------------------------------------------------------------------------------------
遇到问题1:
当播放公司服务器视频,在模拟器2.1系统下无问题播放,但在模拟器2.2系统下当执行到MediaPlayer.prepaer();这个方法时会抛出IOException异常?
解决思路:
首先去查看了Android里面MediaPlayer.prepaer();的源代码,看看在什么情况在会抛出IO异常,但是它的MediaPlayer.Java里的prepaer()方法是 native的,看MeidaPlayer.cpp也没具体看明白.后来查看Android Supported Media Formats发现其支持视频编码是Baseline Profile (BP),而播放视频通过MediaInfo查看其视频编码是Baseline@3。后来连接手机调试,使用MOTO Defy 2.2无法播放,但放到HTCG7 2.2及平板电脑上可正常播放,据此推断,可能是由于厂家定制底层和内核的时候,根据需求增减了一部分支持的视频编码。
---------------------------------------------------------------------------------------------------------------------------
遇到问题2
正常情况下如果视频处于started状态直接调用MediaPlayer.seekTo()跳转指定时间没有问题,但是如果处于paused状态调用MediaPlayer.seekTo()方法后,此时开始调用start()方法会出现error,,onError(MediaPlayer mp, int what, int extra)中返回的是
07-1916:05:29.499: INFO/System.out(7763): whatis 100 extra 0
100代表的是MEDIA_ERROR_SERVER_DIED
extra 0没搞懂是什么,后来查看自带的播放器也存在这个问题,视频暂停,然后拖动进度条,在开始会发生错误。
解决办法:
尝试方法1:
1在拖动进度条监听器的onStopTrackingTouch()方法中首先检测视频是否正在播放(mMediaPlayer.isPlaying()==true),
2如果拖动前是暂停状态的话,就先start进入started状态,
3然后在调用seekTo()方法,测试后还是会问题,错误如下
07-1916:54:05.569: ERROR/ProfileVideoFrameDrops(8984): PVMediaOutputNodePort 1Frames dropped at 1
07-1916:54:05.960: ERROR/TI_Video_Decoder(8984): 2273 VIDDEC_HandleCommandFlush DSPflushed without processing SPS/PPS. saving first buffer
07-1916:54:05.983: ERROR/MediaPlayer(8959): internal/external state mismatchcorrected
onError(MediaPlayermp, int what, int extra)中返回的是
07-1916:54:05.983: INFO/System.out(8959): what is 100 extra 0
然后进入了OnCompletionListener中的onCompletion方法
然后onError r(MediaPlayer mp, int what, intextra)继续返回
07-1916:54:06.092: INFO/System.out(8959): what is -38 extra 0
说明此方法行不通。
尝试方法2:
1 和第一步一样,看是否在播放中
2 如果处于paused状态,调用reset()方法,
3然后重新调用prepare()方法准备
4开始seekTo();或者先start(),然后seekTo();
出现的问题是:
onError(MediaPlayer mp, int what, intextra)中返回的是
07-19 17:10:17.749:INFO/System.out(9472): what is -38 extra 0
尝试方法3:
1 和第一步一样,看是否在播放中
2如果处于paused状态,调用stop()方法,
3然后重新调用prepare()方法准备
4开始seekTo();
这种方法是我暂时尝试到了能行得通的方法,但是有个小瑕疵,就是seekTo()后自动播放了,这时调用pause()也停不了,或者先start(),然后pause()也不行。不知道是什么原因,查看log发现当跳动时的错误
07-1920:17:20.108: ERROR/TI_LCML(11015): 507 :: Exiting Init_DSPSubSystem
07-1920:17:20.881: ERROR/ProfileVideoFrameDrops(11015): PVMediaOutputNodePort 5Frames dropped at 6
---------------------------------------------------------------------------------------------------------------------------
遇到问题3
获取PopupWindow上的按钮ID 例如ImageButton playButton = (ImageButton)findViewById(R.id.play);一直出现空指针异常。
解决办法:
这时候findViewById(R.id.play)是当前View的,而不是PopupWindow里的,应该修改成为
ImageButtonplayButton = (ImageButton) vPopupWindow.findViewById(R.id.play);
---------------------------------------------------------------------------------------------------------------------------
遇到问题4
在实验PopupWindow时,把seekBar和开始按钮放到PopupWindow上,但是弹出PopupWindow后,点击PopupWindow外部其他控件时,PopupWindow无法消失,焦点一直在PopupWindow上.
解决办法:
网上查看相关资料后。发现是因为没有给PopupWindow设置背景图片,使用下面这个方法
popup.setBackgroundDrawable(getResources().getDrawable(R.drawable.videoplayer_bg));
后问题解决。
---------------------------------------------------------------------------------------------------------------------------
遇到问题5
想在视频播放中使用OnGestureListener识别用户手势,来做到不使用控制台快捷控制一些常用的例如快进倒退调声等,但是实验时无法进入android.view.GestureDetector.OnGestureListener里的public boolean onFling()函数.因此没法检测手势
解决办法:
把默认生成的onDown方法返回值改成 return true ;
public boolean onDown(MotionEvent e)
{
// TODO Auto-generatedmethod stub
return true ;
}
----------------------------------------------------------------------------------
遇到问题6
使用更新时间线程和打开控制台一定时间后自动隐藏控制台线程时,如果处于线程延时时间内横竖屏转换时,转变后会发生错误导致崩溃.错误如下:
07-2017:13:13.780: WARN/dalvikvm(12151): threadid=1: thread exiting with uncaughtexception (group=0x400208b0)
07-2017:13:13.803: ERROR/AndroidRuntime(12151): java.lang.IllegalArgumentException:View not attached to window manager
解决办法
原因估计是在横竖屏转换时Activity重载时没有注销线程里的Runnable
隐藏控制台线程在run()方法结尾执行注销线程里的runnable
dismissHandler.removeCallbacks(dismissRunnable);
在Activity里的onDestroy()方法里
执行
if (updateHandle!= null )
{
updateHandle .removeCallbacks(updateRunnable);
releaseMediaPlayer();
}
if (dismissHandler!= null )
{
dismissHandler.removeCallbacks(dismissRunnable);
}
---------------------------------------------------------------------------------------------------------------------------
遇到问题7
播放视频中,如果中途返回桌面,处理完外部事件在回到播放器,会出现无法继续退出前的时间点继续播放。错误是播放器线程死亡。
解决办法:
首先在activity的onPause()方法里保存时间断点,这里我用一个static int userPauseTime来保存。
protected void onPause()
{
userPauseTime =mMediaPlayer.getCurrentPosition();
mMediaPlayer.pause();
System. out .println("onPause");
super .onPause();
}
当用户处理完其他事件重新进入activity,程序执行流程根据我的跟踪是这样的,
onResume()->surfaceCreated()->initMedia(media初始化)->onPrepared()
如果在onResume()直接seekTo()是不行的,因为这个时候貌似mediaplayer已经挂掉了,所以在surfaceCreated()里需要重新如下面这样new一个 mediaplayer,然后重新初始化
if (mMediaPlayer != null ) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null ;
}
// 对mMedia进行相关准备工作
try {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(path);
mMediaPlayer.setDisplay(holder);
mMediaPlayer.setAudioStreamType(AudioManager. STREAM_MUSIC );
mMediaPlayer.prepareAsync();
}
catch (Exception e) {
// TODO : handleexception
System. out .println("e"+e);
}
initMediaAllListener();
mMediaPlayer.setAudioStreamType(AudioManager. STREAM_MUSIC );
然后在onPrepared()方法中,判断是否用户之前有中断的时间,如果没有的话,应该属于第一次启动本视频,如果有的话 就调用 seekTo()跳转到之前的时间
public void onPrepared(MediaPlayer mediaplayer)
{
Log. d ( TAG , "onPreparedcalled");
mIsVideoReadyToBePlayed = true ;
if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown)
{
// startVideoPlayback();
}
if ( userPauseTime ==0)
{
startVideoPlayback();
System. out .println("startVideoPlayback();");
}
else
{
mMediaPlayer.seekTo( userPauseTime );
mMediaPlayer.start();
startProgressUpdate();
userPauseTime =0;
System. out .println("userPauseTime!=0");
}
}
---------------------------------------------------------------------------------------------------------------------------
遇到问题8
不清楚onInfo和onError里打印出来的 what 和 extra 代表什么意思,因此无法辨别mediaplayer出了什么错误.
解决办法:
总结如下
1.onInfo里 what= 1extra=44 时,代表着可以连接本地视频文件或者连接目标服务器流文件成功。
2.OnInfo里 what= 1extra=26 时, 代表无法找到连接本地视频文件或者连接目标服务器。接着会
onError 里会打印出 INFO/System.out(5514): what is 1 extra -4,然后继续打印出
onError INFO/System.out(5514):what is -38 extra 0
只要mediaplayer出了错误 最后都是onError 打印出 what is -38 extra 0
但是API文档中并未找到详细说明或给出对应的错误列表...
经过研究和网上资料的收集,暂总结如下:
在这个网站下可以查到如下内容
http://android.git.kernel.org/?p=platform/external/opencore.git;a=blob;f=pvmi/pvmf/include/pvmf_return_codes.h;h=ed5a2539ca85ae60425229be41646b6bd7d9389c;hb=HEAD
/*
*DRM clock is not available or cannot be read
*/
const PVMFStatus PVMFErrDrmClockError = (-38);
/*
*Return code for pending completion
*/
const PVMFStatus PVMFPending = 0;
DRM指的是内容 数字版权加密保护技术 。 由于数字化信息的特点决定了必须有另一种独特的技术,来加强保护这些数字化的音视频节目内容的版权。
3播放正在缓冲时,无线网络断开
07-2510:25:16.647: ERROR/MediaPlayer(8789): error (1, -17)既 what is 1, extra is -17
07-25 10:55:17.436: INFO/System.out(12355): whatis 1extra 31
07-27 10:23:42.040: INFO/System.out(14344):onInfo is 1 extra is28
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题9
横竖屏速度慢,而且会重新开始播放视频。如何能加快转换速度,并且不间断播放?
解决办法:
转换速度慢并且重新播放是由于横竖屏转换时调用了activity里的onCreate()方法导致重新加载了,如果不想自动转换时调用onCreate()则在AndroidMaifest.xml中相应的activity下加入如下语句:
<activity android:name= ".MediaPlayerDemo_Video"
android:configChanges= "orientation|keyboardHidden"
></activity>
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题10
调整音量时,会显示出系统自带的音量调整UI ,可能会遮挡住视频,如何才能不显示自带的音量UI?
解决办法:
在调整音量时,是使用下面这条语句:
audioManager.adjustStreamVolume(AudioManager. STREAM_MUSIC ,
AudioManager. ADJUST_LOWER ,AudioManager. FLAG_SHOW_UI );
最后一个参数指的就是需不需要显示出音量UI,如果设置为0 ,就不会显示了。
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题11
在播放网络流媒体时,如果网速太慢,根据缓冲进度来给用户正在缓冲的提示?
解决办法:
第一种,在onBufferingUpdate()里面跟踪视频正在播放时间,如果与上一次时间相同,则弹出缓冲提示如下
int CurrentTime =mMediaPlayer.getCurrentPosition();
if(lastBufferTime==CurrentTime)
{
if(bufferAlertDialog.isShowing()==false)
{
bufferAlertDialog.show();
System.out.println("bufferAlertDialog.show();");
}
}
else
{
if(bufferAlertDialog.isShowing()==true)
{
bufferAlertDialog.dismiss();
bufferAlertDialog=null;
System.out.println("bufferAlertDialog.dismiss();");
}
}
if (mMediaPlayer!= null )
{
if (mMediaPlayer.isPlaying()== true )
{
lastBufferTime=CurrentTime;//正在播放才更新上一次缓冲时间 防止用户暂停时也更新
}
}
但是这样有些瑕疵 ,首先是每次先是画面停顿后2秒-3秒左右后才弹出缓冲中,这是由于onBuffer自己调用的时间是2s左右。
其次是可以播放,但是播放起来很卡,这种方法暂时不能给出任何提示。
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题12
使用ProgressDialog进行缓冲提示时,第一次ProgressDialog上的进度条会旋转,但在播放中进度条不会旋转。
解决办法:
在每次onBuffer里面重新生成 ProgressDialog,
if (bufferAlertDialog== null )
{
initBuffering();
}
然后在每次dismiss后赋值null,
if (bufferAlertDialog.isShowing()== true )
{
bufferAlertDialog.dismiss();
bufferAlertDialog= null ;
}
注意: 这里不能使用 dismiss()不能改成dialog.hide()方法,经过测试,hide方法后 isShowing()返回的一直是 true ,虽然在手机上看不见了,但是依然是 isShowing
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题13
如何在屏幕双击后自动切换播放视频原始尺寸或全屏显示?
解决办法:
可以通过设置SurfaceView的参数来调整显示大小,如下
public void setVideoScale( int width, int height)
{
LayoutParams lp = mPreview.getLayoutParams();
lp.height = height;
lp.width = width;
mPreview.setLayoutParams(lp);
}
width和height 如果是视频默认尺寸的话就是
int videoWidth = mMediaPlayer.getVideoWidth();
int videoHeight = mMediaPlayer.getVideoHeight();
全屏显示时就是
Display display =getWindowManager().getDefaultDisplay();
screenHeight = display.getHeight();
screenWidth = display.getWidth();
最后在双击的监听器中
@Override
public boolean onDoubleTap(MotionEvent e)
{
System. out .println("onDoubleTap");
if (isFullScreen)
{
setVideoScale( SCREEN_DEFAULT );
} else
{
setVideoScale( SCREEN_FULL );
}
isFullScreen = !isFullScreen;
return true ;
}
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题14
横竖屏转换时,竖屏全屏播放转换成横屏时,只能显示在左边而且显示一半.
解决办法:
显示在左边是xml中的布局问题,在其修改成
<SurfaceView android:id= "@+id/surface"
android:layout_width= "fill_parent"
android:layout_height= "fill_parent"
android:layout_centerInParent= "true"
>
显示一半是由于在转换时没有重新获得屏幕的大小,它就按照你原先的大小重新显示,所以必须在屏幕转换配置监听器里
public voidonConfigurationChanged(Configuration newConfig)中,重新获得屏幕大小,然后重新设置为全屏或默认,如下
getScreenSize();
if (newConfig.orientation == Configuration. ORIENTATION_LANDSCAPE )
{
setVideoScale( SCREEN_FULL );
isFullScreen= true ;
else
{ if (isFullScreen== true )
{
setVideoScale( SCREEN_FULL );
isFullScreen= true ;
}
else
{
setVideoScale( SCREEN_DEFAULT );
isFullScreen= false ;
}
}
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题15
在弹出控制台后,如果在控制台自动消失(5s)前,手动消失,然后在触摸,控制台会继续之前5s剩下的秒数后消失,而不是重新开始.
解决办法:
主要缺少手动消失时移除上一次时间runnable;
原来程序里是这么判断的,在触摸事件里
public boolean onSingleTapUp(MotionEvent e)
{
if (popup != null )
{
if (popup.isShowing()== true )
{
pop.dismiss();
} else
{
popup.showAtLocation(findViewById(R.id. surface ),
Gravity. BOTTOM , 0, -50);
mViewFlipper.startFlipping();
startControlBarDismiss(5000);
System. out .println("startControlBarDismiss(5000);");
}
}
}
经过验证,只有在当前最前端窗体是视频窗口时,触摸时在会触发onSingleTapUp,这是因为是把onTouchListener安装在视频窗口的LinearLayout上,所以手动触摸控制台以外时控制台窗体消失时并不是触发触摸里的pop.dismiss();估计是系统自带的前端窗口消失
办法是在每次弹出前就检测上一次是否有时间runnable,线程不为空的话就移除runnable,而不是在消失的时候移除
if (dismissHandler!= null )
{
dismissHandler.removeCallbacks(dismissRunnable);
}
popup.showAtLocation(findViewById(R.id. surface ),
Gravity. BOTTOM , 0, -50);
mViewFlipper.startFlipping();
startControlBarDismiss(5000);
System. out .println("startControlBarDismiss(5000);");
并在run()方法结束的时候移除runnable 和 赋null;
If(dismissHandler!=null)
{
dismissHandler.removeCallbacks(dismissRunnable);
dismissHandler= null ;
}
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题16
在控制台里拖动seekBar,seekBar在拖动中一闪一闪的跳回到播放时间
解决办法
这是由于在拖动时更新进度条线程还在一直更新,办法是在seekBar的onStartTrackingTouch()(开始拖动监听器中)首先先移除updaterunnable()
在拖动停止时,在执行updateHandle.post(updateRunnable);
---------------------------------------------------------------------------------------------------------------------------------------------
遇到问题17
在通过上下手势播放时调节亮度中,只能调节一次,每次修改完读取亮度值还是上次的值。
解决办法:
原来程序中在onFling()中 每次上下手势时读取上次亮度,然后+30
setBrightness(getNowBrihtness()+ 30);
getNowBrihtness();
private void setBrightness( int brightness)
{
WindowManager.LayoutParams lp = this .getWindow().getAttributes();
lp.screenBrightness = Float. valueOf (brightness) * (1f / 255f);
this .getWindow().setAttributes(lp);
}
private int getNowBrihtness()
{
try
{
nowBrightnessValue = android.provider.Settings.System. getInt (
resolver, Settings.System. SCREEN_BRIGHTNESS );
System. out .println("nowBrightnessValue" + nowBrightnessValue);
} catch (Exception e)
{
e.printStackTrace();
}
return nowBrightnessValue;
}
两次打印出来的亮度值是一样的。
经过资料发现,使用setBrightness()只是对当前activity设置亮度值,而不是设置系统的亮度值,当退出activity就使用回系统的亮度值,而getNowBrihtness()每次是获取系统的亮度值。造成了只能调整一次的假象,其实每次都是调整成了一样的值而已。
想要保存到系统的亮度值,首先需要设置一下权限
<uses-permissionandroid:name="android.permission.WRITE_SETTINGS"></uses-permission>
然后使用下面这个方法
private void saveBrightness( int brightness)
{
Uri uri = android.provider.Settings.System
. getUriFor ("screen_brightness");
android.provider.Settings.System. putInt (resolver, "screen_brightness",
brightness);
//resolver.registerContentObserver( uri , true, myContentObserver);
resolver.notifyChange(uri, null );
}
这样子每次调整完这个方法保存到系统就可以了,或者可以在程序里一个变量初始化时读取系统亮度值,然后更改窗体亮度完也对这个变量修改,这种是不会对系统的亮度造成任何影响。