Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 視頻直播

視頻直播

編輯:關於Android編程


<framelayout android:id="@+id/videoLayout" android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

    
    

</framelayout>

 

接著就是Activity類文件的定義:

package com.example.videodemo;

import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class VideoPlayActivity extends Activity implements
		SurfaceHolder.Callback {
	/** Called when the activity is first created. */
	MediaPlayer player;
	SurfaceView surface;
	SurfaceHolder surfaceHolder;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_video_play);

		initView();
	}

	private void initView() {
		surface = (SurfaceView) findViewById(R.id.surface);
		surfaceHolder = surface.getHolder(); // SurfaceHolder是SurfaceView的控制接口
		surfaceHolder.addCallback(this); // 因為這個類實現了SurfaceHolder.Callback接口,所以回調參數直接this
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder arg0) {
		// 必須在surface創建後才能初始化MediaPlayer,否則不會顯示圖像
		player = new MediaPlayer();
		player.setAudioStreamType(AudioManager.STREAM_MUSIC);
		player.setDisplay(surfaceHolder);
		// 設置顯示視頻顯示在SurfaceView上
		try {
			player.setDataSource("你要播放的視頻的url");
			player.prepare();
			player.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		if (player.isPlaying()) {
			player.stop();
		}
		player.release();
		// Activity銷毀時停止播放,釋放資源。不做這個操作,即使退出還是能聽到視頻播放的聲音
	}
}

由此你可以看到,這種實現方式有幾點值得注意的地方是:

1、你需要一個媒體播放器對象"MediaPlayer",該對象會負責播放你指定的視頻。

2、如果說MediaPlayer負責播放視頻,那麼我們剛剛定義的SurfaceView則用於在屏幕中顯示播放視頻。

(所以又可以理解為,如果MediaPlayer是一副畫,而SurfaceView則是讓這幅畫呈現在人們眼前的畫紙)

3、MediaPlayer類的成員方法設置用於顯示媒體視頻的SurfaceHolder,正如上面所說,就如同你擇不同的畫紙來呈現你的畫。

4、MediaPlayer類的成員方法setDataSource用於指定你要播放的視頻數據源。

5、僅僅是設置完數據源是不足夠的,設置完數據源和顯示的Surface後,你需要調用prepare()或prepareAsync()來讓你的視頻數據源stand by..

6、所以你也可能已經發現,對於一段視頻的播放,MediaPlayer是關鍵,關於該類的更多使用,這篇博客裡有更詳細的說明:Android - MediaPlayer類的使用說明


由此我們已經基本掌握了,在android端簡單的播放視頻的方法。一切看上去十分美好。

但做開發就是有這麼蛋疼,maybe有很多時候為了加快video與server端之間上傳於下載的速率,有時候會對視頻做分段處理。

正如同做web開發時,上傳和下載文件時,如果文件過大,很多時候我們會選擇對文件做“切割處理一樣”。

 

那麼這個時候,就出現了一種情況,就是可能你要播放的一段視頻,

事實上是由幾小段視頻組合而成的。所以就涉及到了連續播放。

 

可能當面對到這樣的需求時,我們首先最容易想到的就是:

對每段視頻進行監聽,當監聽到它播放結束時,立刻做Refresh切換到下一段視頻分段的播放。

而MediaPlayer的確也提供了這樣的監聽事件,正是:MediaPlayer.OnCompletionListener()。

我在網上查閱相關實現的功能時,也只看到類似的說法,也就是說在該監聽內做實現:

當一段數據源播放完畢後,執行player.reset()釋放數據源,然後再設置新的資源進行播放。

但這樣做有很大的一個弊端就是,reset掉舊的數據源之後,新的數據源會有一段“加載時間”。

也就是說,在這段時間內,用戶看到的播放界面就處於一個停頓狀態。

 

那麼,為了最大化的避免這個所謂的“停頓時間”,又應該怎麼去做呢?

首先考慮到的便是,在一段視頻開始播放的同時,便開始做第二段視頻播放的“准備工作”。

但是通過前面的例子我們以前看到了,基於MediaPlayer本身的特性和限制。

如果我們想要實現這樣的方式,那麼單一的MediaPlayer是滿足不了我們的需求的。

 

所以我們要做的工作便是:當我們進入視頻播放界面,第一段視頻准備完畢,開始播放後,

便開始著手初始化另一個新的MediaPlayer,這個新的MediaPlayer的數據源當然是接下來要播放的下一段視頻的url!

當這個MediaPlayer對象的准備工作都搞定後,剩下的工作就是:

我們需要“一顆釘子”,來將兩個分段的視頻段連接起來。

而這個釘子就是Android r16後添加的一個方法:setNextMediaPlayer()方法。

 

關於這個方法的使用,我找了又找,終於在一篇文章裡,看到了一個這樣簡短的說明:

在第一個MediaPlayer類執行結束前的任何時間調用setNextMediaPlayer(MediaPlayernext)這個方法,

該方法的參數是第二個文件創建的MediaPlayer實例。然後Android系統將會在您第一個停止的時候緊接著播放第二個文件。

 

但我認為,在這個說明裡,你應該注意到的關鍵點是:第一個MediaPlayer類執行結束前的任何時間調用這個方法。

也就是說,你必須在前一個MediaPlayer對象播放完畢之前使用該方法。

例如我後來發現,如果理想的在我們前面提到的OnCompletionListener監聽中使用該方法,是無效的。

 

並且,似乎並不如該說明而言的“Android系統將會在您第一個停止的時候緊接著播放第二個文件”。

也就是說,這個切換播放的動作不是自動的,還需要我們手動的做一個小的控制,馬上接下來就會說到。

 

到了這裡,我們要實現的思路已經很明確了:在一段視頻播放的同時,做下一段視頻的player的初始化准備工作。

而此時另一個格外需要記住的就是:不要再在UI線程去開啟新的MediaPlayer的賦值工作.

原理很簡單,其實也是Android開發所必須記住的,即是永遠不要在UI線程裡去做耗時的操作。

這樣做的後果基本有幾種,一種是報告“在主線程做了太多操作”的異常,而另外也可能出現,屏幕響應遲緩,

也就是說,例如你的視頻播放界面可能還存在一些按鈕和響應事件之類,這個響應會出現延遲。最後,當然也很可能出現ANR。

所以,我們還需要做的工作就是,將其它負責後續播放的MediaPlayer對象的初始化與賦值工作放在新的線程裡去執行。

 

而最後我們需要做的,則是在OnCompletionListener裡進行監聽,當一段視頻播放完畢後,

馬上執行mp.setDisplay(null),然後調用負責下一個視頻分段播放的MediaPlayer執行setDisplay(surfaceHolder)。

 

說了這麼多,還是通過代碼說話吧:

@SuppressLint("NewApi")
public class MainActivity extends Activity implements SurfaceHolder.Callback {
	//用於播放視頻的mediaPlayer對象
	private MediaPlayer firstPlayer,     //負責播放進入視頻播放界面後的第一段視頻
	                    nextMediaPlayer, //負責一段視頻播放結束後,播放下一段視頻
	                    cachePlayer,     //負責setNextMediaPlayer的player緩存對象
			            currentPlayer;   //負責當前播放視頻段落的player對象
	//負責配合mediaPlayer顯示視頻圖像播放的surfaceView
	private SurfaceView surface;
	private SurfaceHolder surfaceHolder;
	//底部聊天欄
	private LinearLayout bottom_bar_layout;
	private FrameLayout video_layout;
	
	//================================================================
	
	//存放所有視頻端的url
	private ArrayList VideoListQueue = new ArrayList();
	//所有player對象的緩存
	private HashMap playersCache = new HashMap();
	//當前播放到的視頻段落數
	private int currentVideoIndex;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//橫屏顯示
		this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        //初始化界面控件
		initView();
	}

	/*
	 * 負責界面銷毀時,release各個mediaplayer
	 * @see android.app.Activity#onDestroy()
	 */
	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (firstPlayer != null) {
			if (firstPlayer.isPlaying()) {
				firstPlayer.stop();
			}
			firstPlayer.release();
		}
		if (nextMediaPlayer != null) {
			if (nextMediaPlayer.isPlaying()) {
				nextMediaPlayer.stop();
			}
			nextMediaPlayer.release();
		}

		if (currentPlayer != null) {
			if (currentPlayer.isPlaying()) {
				currentPlayer.stop();
			}
			currentPlayer.release();
		}
		currentPlayer = null;
	}

	/*
	 * 界面控件的初始化
	 */
	private void initView() {
		surface = (SurfaceView) findViewById(R.id.surface);

		surfaceHolder = surface.getHolder();// SurfaceHolder是SurfaceView的控制接口
		surfaceHolder.addCallback(this); // 因為這個類實現了SurfaceHolder.Callback接口,所以回調參數直接this

		bottom_bar_layout = (LinearLayout) findViewById(R.id.live_buttom_bar);
		
		//點擊屏幕任何地點,控制底部聊天欄的隱藏或顯示
		video_layout = (FrameLayout) findViewById(R.id.videoLayout);
		video_layout.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				if (bottom_bar_layout.getVisibility() == View.VISIBLE) {
					bottom_bar_layout.setVisibility(View.GONE);
				} else {
					bottom_bar_layout.setVisibility(View.VISIBLE);
				}

			}
		});
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
		// TODO 自動生成的方法存根

	}

	@Override
	public void surfaceCreated(SurfaceHolder arg0) {
		//surfaceView創建完畢後,首先獲取該直播間所有視頻分段的url
		getVideoUrls();
		//然後初始化播放手段視頻的player對象
		initFirstPlayer();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder arg0) {
		// TODO 自動生成的方法存根

	}

	/*
	 * 初始化播放首段視頻的player
	 */
	private void initFirstPlayer() {
		firstPlayer = new MediaPlayer();
		firstPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
		firstPlayer.setDisplay(surfaceHolder);

		firstPlayer
				.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
					@Override
					public void onCompletion(MediaPlayer mp) {
						onVideoPlayCompleted(mp);
					}
				});

		//設置cachePlayer為該player對象
		cachePlayer = firstPlayer;
		initNexttPlayer();

		//player對象初始化完成後,開啟播放
		startPlayFirstVideo();
	}

	private void startPlayFirstVideo() {
		try {
			firstPlayer.setDataSource(VideoListQueue.get(currentVideoIndex));
			firstPlayer.prepare();
			firstPlayer.start();
		} catch (IOException e) {
			// TODO 自動生成的 catch 塊
			e.printStackTrace();
		}
	}

	/*
	 * 新開線程負責初始化負責播放剩余視頻分段的player對象,避免UI線程做過多耗時操作
	 */
	private void initNexttPlayer() {
		new Thread(new Runnable() {

			@Override
			public void run() {

				for (int i = 1; i < VideoListQueue.size(); i++) {
					nextMediaPlayer = new MediaPlayer();
					nextMediaPlayer
							.setAudioStreamType(AudioManager.STREAM_MUSIC);

					nextMediaPlayer
							.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
								@Override
								public void onCompletion(MediaPlayer mp) {
									onVideoPlayCompleted(mp);
								}
							});

					try {
						nextMediaPlayer.setDataSource(VideoListQueue.get(i));
						nextMediaPlayer.prepare();
					} catch (IOException e) {
						// TODO 自動生成的 catch 塊
						e.printStackTrace();
					}

					//set next mediaplayer
					cachePlayer.setNextMediaPlayer(nextMediaPlayer);
					//set new cachePlayer
					cachePlayer = nextMediaPlayer;
                    //put nextMediaPlayer in cache
					playersCache.put(String.valueOf(i), nextMediaPlayer);

				}

			}
		}).start();
	}

	/*
	 * 負責處理一段視頻播放過後,切換player播放下一段視頻
	 */
	private void onVideoPlayCompleted(MediaPlayer mp) {
		mp.setDisplay(null);
		//get next player
		currentPlayer = playersCache.get(String.valueOf(++currentVideoIndex));
		if (currentPlayer != null) {
			currentPlayer.setDisplay(surfaceHolder);
		} else {
			Toast.makeText(MainActivity.this, "視頻播放完畢..", Toast.LENGTH_SHORT)
					.show();
		}
	}
	
	private void getVideoUrls() {
		for (int i = 0; i < 5; i++) {
			String url = getURI(i);
			VideoListQueue.add(url);
		}
	}

	private String getURI(int index) {
		return "要播放的第"+index+"段視頻的URI";
	}

}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved