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查看其视频编码是[email  protected]。后来连接手机调试,使用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 = https://www.songbingjia.com/android/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)】 
 



























































































































































































































































































































































































































































    推荐阅读