本文为自定义的视频播放器,可进行屏幕切换(由于换屏时大小变化,电脑截图就分开截图了),效果如下图:
--------------播放视频概括:
SurfaceView+MediaPlayer以及 VideoView 2种方式
SurfaceVIew中有个SurfaceHolder,通过surfaceView.getHolder( )方法获取,如果需要兼容2.3系统,还要再加上setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);否则只有声音没有图像。有3个生命周期 surfaceCreated、surfaceChanged、surfaceDestroyed。
读取raw目录下的2个方法
Uri.parse("android.resource://"+ getPackageName()+ "/" + R.raw.video));
AssetFileDescriptorafd = getResources().openRawResourceFd(
R.raw.por);
mp_test.setDataSource(afd.getFileDescriptor(),
afd.getStartOffset(), afd.getLength());
afd.close();
设置app主题无title
@android:style/Theme.Light.NoTitleBar
屏蔽变化引起的activity重启
android:configChanges="keyboard|orientation|screenSize"
设置屏幕常亮
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
设置activity方向为纵向
android:screenOrientation="portrait"
获得当前屏幕是横屏还是竖屏
getResources().getConfiguration().orientation
手动切换横竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
监听横竖屏切换的动作
onConfigurationChanged
代码隐藏title
requestWindowFeature(Window.FEATURE_NO_TITLE);(必须在setcontent之前)
代码请求全屏模式
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
代码清除全屏模式
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
适配不同的video尺寸达到不拉伸视频的效果
setVideoParms 方法,根据视频的比例和显示区域的比例计算得来
在一套布局里写2套控件,适配横竖屏
适当利用LinearLayout的weight属性做适配
使用audioManager获取,调节系统系统音量
seekBar的初始化,改变时的监听
系统音量改变时会发出broadcast
android.media.VOLUME_CHANGED_ACTION监听系统音量变化
seekBar监听时注意是否fromUser,mediaPlayer注意try
当前activity add的flag,当activity离开时就会clear掉
-------------项目代码VideoActivity类:
/** * 播放视频页面 * * @author hasee * */ public class VideoActivity extends Activity implements OnPreparedListener, OnSeekBarChangeListener { private MediaPlayer mediaPlayer; private SurfaceView sv_video; private RelativeLayout rl_top; private Button bt_start_or_pause;// 播放或暂停按钮 private SeekBar sb_progress;// 视频进度条 ImageView mChangeSceen; ImageView mCenterPause; LinearLayout mllmenu; LinearLayout mlltitle; private SeekBar sb_vol;// 音量进度条 private AudioManager am; private int currentPosition;// 记录当前进度值 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// 双缓冲 setContentView(R.layout.activity_video); initView(); initData(); } private void initView() { sv_video = (SurfaceView) findViewById(R.id.sv_video); bt_start_or_pause = (Button) findViewById(R.id.bt_start_or_pause); rl_top = (RelativeLayout) findViewById(R.id.rl_top); sb_progress = (SeekBar) findViewById(R.id.sb_progress); sb_vol = (SeekBar) findViewById(R.id.sb_vol); mChangeSceen= (ImageView) findViewById(R.id.bt_change); mCenterPause= (ImageView) findViewById(R.id.video_center_pause); mllmenu= (LinearLayout) findViewById(R.id.video_menu); mlltitle= (LinearLayout) findViewById(R.id.video_menu_title); // sv_video.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);// // 兼容2.3及以下版本,否则只有声音没有画面 sv_video.getHolder().addCallback(new Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { // 当页面可见时候 playVideo(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // 当页面不可见时候 stopVideo(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } }); } private void initData() { registerReceiver(volReceiver, new IntentFilter( "android.media.VOLUME_CHANGED_ACTION")); am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); // 保持屏幕常亮 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); sb_vol.setMax(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)); sb_vol.setProgress(am.getStreamVolume(AudioManager.STREAM_MUSIC)); sb_progress.setOnSeekBarChangeListener(this); sb_vol.setOnSeekBarChangeListener(this); } private void playVideo() { mediaPlayer = new MediaPlayer(); try { // 获取RAW目录下的文件 // AssetFileDescriptor afd = getResources().openRawResourceFd( // R.raw.por); // mediaPlayer.setDataSource(afd.getFileDescriptor(), 0, // afd.getLength()); // 获取RAW目录下的文件 mediaPlayer.setDataSource( this, Uri.parse("android.resource://" + getPackageName() + "/"+ R.raw.test)); mediaPlayer.setLooping(true); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setDisplay(sv_video.getHolder()); mediaPlayer.prepareAsync(); } catch (Exception e) { e.printStackTrace(); } } private void stopVideo() { canProgress = false; if (mediaPlayer != null && mediaPlayer.isPlaying()) { try { currentPosition = mediaPlayer.getCurrentPosition(); mediaPlayer.pause(); mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } catch (Exception e) { } } } @Override public void onPrepared(MediaPlayer mp) { // 说明mediaPlayer准备好了 try { sb_progress.setMax(mp.getDuration()); setParam(mp, isLand()); mp.start(); if (currentPosition > 0) { mp.seekTo(currentPosition); currentPosition = 0; } startProgress(); } catch (Exception e) { } } /** * 判断当前是否横屏 * * @return */ private boolean isLand() { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { return false; } else { return true; } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { Log.e("pid", "竖屏了"); setParam(mediaPlayer, false); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { Log.e("pid", "横屏了"); // 去除状态栏 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); setParam(mediaPlayer, true); } } /** * 设置视频尺寸,达到不被拉伸的效果 * * @param mp * @param isLand */ private void setParam(MediaPlayer mp, boolean isLand) { float screenWidth = getWindowManager().getDefaultDisplay().getWidth(); float screenHeight = screenWidth / 16f * 9f; if (isLand) { screenHeight = getWindowManager().getDefaultDisplay().getHeight(); } float videoWdith = mp.getVideoWidth(); float videoHeight = mp.getVideoHeight(); float screenPor = screenWidth / screenHeight;// 16:9 float videoPor = videoWdith / videoHeight;// 9:16 ViewGroup.LayoutParams pa = sv_video.getLayoutParams(); if (videoPor <= screenPor) { pa.height = (int) screenHeight; pa.width = (int) (screenHeight * videoPor); } else { pa.width = (int) screenWidth; pa.height = (int) (screenWidth / videoPor); } ViewGroup.LayoutParams rl_pa = rl_top.getLayoutParams(); rl_pa.width = pa.width; rl_pa.height = pa.height; rl_top.setLayoutParams(rl_pa); sv_video.setLayoutParams(pa); } private boolean canProgress = true; private void startProgress() { canProgress = true; new Thread() { public void run() { while (canProgress) { try { sleep(500); sb_progress.setProgress(mediaPlayer .getCurrentPosition()); } catch (Exception e) { } } }; }.start(); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(volReceiver); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (!fromUser) return; switch (seekBar.getId()) { case R.id.sb_progress: try { if (mediaPlayer != null) { mediaPlayer.seekTo(progress); } } catch (Exception e) { } break; case R.id.sb_vol: am.setStreamVolume(AudioManager.STREAM_MUSIC, progress, AudioManager.FLAG_SHOW_UI); break; } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } /** * 系统音量改变时的广播接收者 */ private BroadcastReceiver volReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { sb_vol.setProgress(am.getStreamVolume(AudioManager.STREAM_MUSIC)); } }; public void onClick(View v) { switch (v.getId()) { case R.id.bt_start_or_pause: try { if (bt_start_or_pause.getText().equals("播放")) { // 当前是播放,那么暂停 mediaPlayer.pause(); // canProgress = false; bt_start_or_pause.setText("暂停"); mCenterPause.setVisibility(View.VISIBLE); } else if (bt_start_or_pause.getText().equals("暂停")) { // 当前是暂停,那么播放 mediaPlayer.start(); // canProgress = true; bt_start_or_pause.setText("播放"); mCenterPause.setVisibility(View.GONE); } } catch (Exception e) { } break; case R.id.bt_change: // 点击了横竖屏切换,拿到当前屏幕方向 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { // 当前是竖屏,切换成横屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { // 当前是横屏,切换成竖屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } break; case R.id.video_center_pause: try { if (bt_start_or_pause.getText().equals("播放")) { // 当前是播放,那么暂停 mediaPlayer.pause(); // canProgress = false; bt_start_or_pause.setText("暂停"); } else if (bt_start_or_pause.getText().equals("暂停")) { // 当前是暂停,那么播放 mediaPlayer.start(); // canProgress = true; bt_start_or_pause.setText("播放"); } } catch (Exception e) { } mCenterPause.setVisibility(View.GONE); break; } } /** 定时隐藏 */ private Handler mDismissHandler = new Handler() { @Override public void handleMessage(Message msg) { mllmenu.setVisibility(View.GONE); mlltitle.setVisibility(View.GONE); //标题栏的自动隐藏 } }; @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction()==MotionEvent.ACTION_DOWN){ mllmenu.setVisibility(View.VISIBLE); mlltitle.setVisibility(View.VISIBLE); //标题栏的显示 } mDismissHandler.sendEmptyMessageDelayed(0,3000); return super.onTouchEvent(event); } }
-----------------------activity_video.xml布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff" > <RelativeLayout android:id="@+id/rl_top" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000" > <LinearLayout android:id="@+id/video_menu_title" android:layout_width="match_parent" android:layout_height="50dp" android:visibility="gone" android:background="#000"> <TextView android:textColor="#fff" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="视频标题"/> </LinearLayout> <SurfaceView android:id="@+id/sv_video" android:layout_below="@+id/video_menu_title" android:layout_width="match_parent" android:layout_height="300dip" android:layout_centerInParent="true" /> <ImageView android:id="@+id/video_center_pause" android:onClick="onClick" android:layout_width="100dp" android:layout_height="100dp" android:layout_centerInParent="true" android:visibility="gone" android:src="@drawable/video_pause"/> <LinearLayout android:id="@+id/video_menu" android:layout_width="match_parent" android:layout_height="50dp" android:background="#000" android:visibility="gone" android:orientation="horizontal" android:layout_alignParentBottom="true"> <Button android:id="@+id/bt_start_or_pause" android:layout_width="60dp" android:layout_height="50dp" android:text="播放" android:onClick="onClick" /> <SeekBar android:id="@+id/sb_progress" android:layout_width="0dip" android:layout_height="50dip" android:layout_weight="3" android:indeterminate="false"/> <SeekBar android:id="@+id/sb_vol" android:layout_width="0dip" android:layout_height="50dip" android:layout_weight="1" /> <ImageView android:id="@+id/bt_change" android:layout_width="40dp" android:layout_height="40dp" android:onClick="onClick" android:src="@drawable/changescreen"/> </LinearLayout> </RelativeLayout> </RelativeLayout>
注意在androidmanifest清单文件对activity中添加:
<span style="font-size:18px;"> android:configChanges="keyboard|orientation|screenSize"</span>
在res文件中建立raw文件夹,然后粘贴进去test.MP4文件播放.
由于谷歌已经停止对eclipse的版本更新,我使用Android studio软件开发,项目注意点图片:
Mainactivity类并没有什么代码,主要提供开发者一个自己项目页面,然后在里面用Intent跳转到VideoActivity.class就可以了,想要项目给我留言.
总结:
- 视频播放的注意点:
- 子线程里去更改视频进度条
- 注意try Catch对MediaPlayer进行操作的地方(包括自定义相机中的Camera也是)
- 记得SurFaceView的三个生命周期方法,每次最小化以后,回来再播放,都已经不是同一个Holder了
- 获取视频宽高比例和屏幕宽高比例进行比较,随后就可以根据屏幕的宽或者高来确定怎么拉伸视频不会导致变形
- MediaPlayer的释放:
- 先暂停
- 再停止
- 再释放
- 再置空
- 这样最安全
- 涉及到屏幕翻转的生命周期问题
- 要在AndroidManifest.xml中对视频的Activity做相应配置
- 使用广播来监听对应的音量变化,因为有时候我们是用音量键或者在设置里更改音量
- AssetFileDescriptor可以用来读取raw下文件的类
2,mediaPlayer的native机制:
a. 因为mediaPlayer中的很多方法如isPlaying()方法都是JNI机制写的,因此它的GC回收机制和平常的方法不一样。因此,很容易会出现非法堆栈异常等情况,因此最好加上一个tryCatch进行处理。
b. 内部都是native方法,这种native方法,在不一定的情况下,GC会进行回收,因此不知道什么时候调用就会跳出来一个IllegalStateException异常