zoukankan      html  css  js  c++  java
  • 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(); 
      
           出现的问题是: 
           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 ); 
        } 
    这样子每次调整完这个方法保存到系统就可以了,或者可以在程序里一个变量初始化时读取系统亮度值,然后更改窗体亮度完也对这个变量修改,这种是不会对系统的亮度造成任何影响。 
     
  • 相关阅读:
    Java多线程模式(二)
    HDU 1232 畅通工程
    跨平台实现wchar_t转成char
    小学生玩ACM----优先队列
    JSTL标签急速秒杀jsp页面中的java代码(一)---Core标签库
    项目沟通技术和技巧
    自己写一个strcmp函数(C++)
    Linux Makefile文件编写详细步骤与实践
    JAVA之数组查询binarySearch()方法详解
    系统调用与标准库调用的区别
  • 原文地址:https://www.cnblogs.com/Dionexin/p/6169307.html
Copyright © 2011-2022 走看看