首页 > 代码库 > android播放器:mediaplayer

android播放器:mediaplayer

近几天的主要问题在于播放器。原来采用的方案最终发现存在问题无法实施,只好临时替换。想起最开始就曾经想用的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(); 
  
       出现的问题是: 
       one rror(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 时, 代表无法找到连接本地视频文件或者连接目标服务器。接着会 
    one rror 里会打印出 INFO/System.out(5514): what is 1 extra -4,然后继续打印出 
    one rror 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 = http://www.mamicode.com/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 ); 
    } 
这样子每次调整完这个方法保存到系统就可以了,或者可以在程序里一个变量初始化时读取系统亮度值,然后更改窗体亮度完也对这个变量修改,这种是不会对系统的亮度造成任何影响。 

 

 

android播放器:mediaplayer