1. 程式人生 > >Android本地視訊播放器mediaplay版

Android本地視訊播放器mediaplay版

本文為自定義的視訊播放器,可進行螢幕切換(由於換屏時大小變化,電腦截圖就分開截圖了),效果如下圖:



--------------播放視訊概括:

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就可以了,想要專案給我留言.

總結:

  1. 視訊播放的注意點:
    1. 子執行緒裡去更改視訊進度條
    2. 注意try Catch對MediaPlayer進行操作的地方(包括自定義相機中的Camera也是)
    3. 記得SurFaceView的三個生命週期方法,每次最小化以後,回來再播放,都已經不是同一個Holder了
    4. 獲取視訊寬高比例和螢幕寬高比例進行比較,隨後就可以根據螢幕的寬或者高來確定怎麼拉伸視訊不會導致變形
    5. MediaPlayer的釋放:
      1. 先暫停
      2. 再停止
      3. 再釋放
      4. 再置空
      5. 這樣最安全
    1. 涉及到螢幕翻轉的生命週期問題
      1. 要在AndroidManifest.xml中對視訊的Activity做相應配置
    1. 使用廣播來監聽對應的音量變化,因為有時候我們是用音量鍵或者在設定裡更改音量
    2. AssetFileDescriptor可以用來讀取raw下檔案的類

2,mediaPlayer的native機制:

a.   因為mediaPlayer中的很多方法如isPlaying()方法都是JNI機制寫的,因此它的GC回收機制和平常的方法不一樣。因此,很容易會出現非法堆疊異常等情況,因此最好加上一個tryCatch進行處理。

b.   內部都是native方法,這種native方法,在不一定的情況下,GC會進行回收,因此不知道什麼時候呼叫就會跳出來一個IllegalStateException異常