課程內容
Ø Playing Video
Ø MediaElement
Subservient Cat是一個“虛擬寵物”的應用程式。與大多數貓不一樣,Subservient Cat非常聽從主人的指令!但是,使用者需要知道哪些命令它是能夠回應的。它又帶點遊戲的成分,因為使用者必須通過自己的摸索來發現這些命令。
該應用程式使用一段黑色貓咪(名字為Boo)的視訊剪輯作為主介面。因此,對於學習怎樣通過MediaElement控制項在應用程式中播放視頻來說,這是一個很好的例子。
Playing Video with MediaElement
如果我們想要使用者可以對視頻進行播放、暫停和其他的控制操作,最好的選擇就是使用Media Player 啟動器。但是,如果我們想要在應用程式的頁面中播放視頻內容,就可以選擇使用MediaElement。MediaElement是一個UI控制項,它可以通過自身的Source屬性來播放視頻檔案。例如:
<MediaElement Source=”cat.wmv”/>
視頻源檔案的路徑可以指向工程中包含的檔案,或者是一個線上的網路視頻。預設情況下,MediaElement在載入時自動播放視頻(對於網路視頻來說,只要緩衝了足夠的視頻流,它就開始播放),但是,我們可以將AutoPlay屬性設定為false,來更改這種設定。在背後代碼中,我們可以使用MediaElement的Play、Pause 和 Stop方法。它還具有Position屬性,用於指示當前的播放位置(用一個時間段的值來標識)。另外,如果視頻支援尋找的話,我們可以設定Position為一個播放的時間點。就和其他的Silverlight 元素一樣,MediaElement支援轉變和剪下操作,並且它還可以與其他元素混合。從介面上來看,使用MediaElement元素很直接。但是,也有很多需要說明的地方。下面例舉了5個需要注意的點:
1. 一個應用程式的frame只能包含一個MediaElement!
在一個frame中使用多個MediaElement的做法是不被支援的,而且程式會返回失敗。注意,這種限制比一個頁面使用一個MediaElement還要嚴格;任何時候,只能有一個MediaElement載入到frame上(無論MediaElement是處於停止、暫停或者是播放狀態)。因此,多個頁面只有不同時出現在navigation堆棧的情況下,才能使每個頁麵包含各自的MediaElement。否則,如果我們需要播放多個視頻,那麼我們需要複用同一個MediaElement,或者將不使用的MediaElement從element tree中移除。
2. 在將視頻包含到應用程式時,確定其Build Action屬性值設定為Content,而非Resource!
這樣做可以提高視頻啟動的效能。在視頻檔案作為資源嵌入時,在其播放前,應用程式會先對其進行加壓縮,然後暫時存放到隔離儲存空間(對於MediaElement使用的音頻檔案來說,也同樣需要注意這個問題)。
3. 在MediaElement開始播放時,任何背景音頻播放(比如Zune播放的音樂)會暫停!
這正是為什麼MediaElement不被用於播放音效的主要原因。另外,即使視頻檔案中沒有包含音頻,這一點也是要注意的。
4. MediaElement在模擬器的light主題下存在Bug!
這聽上去很奇怪,但確實是事實。在模擬器上測試MediaElement,我們必須確保它在dark主題下運行。但是別擔心,這個問題在真機中不存在。
5. MediaElement無法渲染完全不透明的效果!
如果MediaElement中存在其他元素,我們可以通過視頻清楚的看到它們,甚至是MediaElement的Opacity屬性設定為1(該屬性的預設值就是1)。這個是手機的media player(在其內部進行MediaElement的視頻渲染)和Silverlight之間合成的異常。詳見“Windows Phone支援的媒體檔案格式”(連結為http://goo.gl/6NhuD),查看MediaElement支援的視頻格式;以及“推薦的視頻編碼參數設定”(連結為http://goo.gl/ttPkO),查看哪種編碼形式最合適我們的應用。如果我們正在使用Expression Encoder,就可以使用其中專門針對Windows Phone (和 Zune HD)平台已經預設值的參數來進行視頻編碼。
The User Interface
除了簡介頁面以外(這裡不作介紹),Subservient Cat應用程式使用了另一個頁面,也就是首頁面。首頁面的的root grid包含了三個不同的使用者控制項,33.1所示。
1. 包含視頻播放的 MediaElement元素
2. 一個簡單的“intro screen”,介紹貓咪能夠執行的指令,之後應用程式會播放命令相對應的視頻片段。
3. 具有text box的panel,讓使用者猜測新的指令。
圖33.1 首頁面中三個主要的使用者控制項
注意:
➔ 視頻播放時,手機處於橫屏模式,所以它只是一個橫屏模式的頁面。但是,text box元素被用來給使用者猜測新的指令,所以背後的代碼暫時改變頁面的SupportedOrientations屬性值,使得焦點位於text box時,兩種螢幕模式都可以使用。這樣一來,具有硬體鍵盤的手機就可以讓使用者獲得更好的體驗。
➔ 應用程式欄具有三個按鈕:一個用於展示指令輸入面板,一個用於導航到簡介頁面,一個用於指示使用者已經發現的指令數量(在背後代碼中更新)。點擊最後一個按鈕還可以提示我們,是否有更多的指令等待我們去發現,因為對於我們使用者來說,指令的總數,是一個謎。應用程式欄菜單是通過代碼進行動態添加的,它包含了使用者已經發現的指令清單,在使用者點擊其中任何一個指令時,貓咪就會做相應的動作。詳見圖33.2。
圖33.2 應用程式欄菜單提供快速擷取已發現的指令清單,例如“yawn”。
➔ 雖然應用程式可以播放不同的視頻片段,但從效能的角度來看,事實上它使用了單個較長的視頻檔案(cat.wmv)。背後的代碼會負責選擇其中合適的視頻片段進行播放。
➔ 通過CompositeTransform 來實現MediaElement的移動和擴大,因為“cat.wmv”源檔案的頭和尾具有一些我們不想看到的黑條。
➔ MediaElement的Volume屬性(值的範圍為0~1)被設定為1(表示最大音量),因為其預設值是0.85。雖然播放的聲音最大隻能達到使用者佈建的值,但是這就確保了視頻檔案中的細節部分(短暫的“喵”叫聲)也能夠被聽到。
注意:確保給MediaElement元素命名!
如果我們沒有給其命名,有可能marketplace發布審核流程不會發現你使用了MediaElement,因此就不會確保我們的應用程式具有“media library”能力,而這個能力對於本應用程式來說是必須的。
The Code-Behind
➔ 首頁面的建構函式利用possibleCommands方法產生貓咪能夠識別的指令集,該指令集伴隨的聲音用其在“cat.wmv”檔案中的起始和終止時間表示。
➔ 在 OnNavigatedTo 事件處理中,使用之前已經發現的指令來填充應用程式欄菜單。這些指令存放在discoveredCommands中,而discoveredCommands又是作為一個設定儲存起來。
➔ 為了在應用程式欄按鈕表徵圖中展示已經發現的指令數量,該應用程式工程中包含了一些圖片,包括appbar.1.png、appbar.2.png和appbar.3.png等等。至於選用哪一個圖片,就需要根據discoveredCommands集合的數量來確定。
➔ 在頁面載入時,視頻就自動開始播放(因為代碼中的AutoPlay屬性沒有設定為false),但是我們不想播放整個視頻來展示貓咪的所有動作。相反,我們只應該播放視頻的前1.5秒。因此,在MediaElement的MediaOpened事件處理函數中(該事件在媒體檔案載入並準備播放時觸發),我們利用videoTimer在視頻播放1.48秒以後進行暫停。該暫停在videoTimer的Tick事件處理函數“VideoTimer_Tick”中完成。
注意:直到MediaOpened事件觸發,我們才能夠在MediaElement中播放視頻!
在設定MediaElement的源檔案後(在XAML或者背後代碼中都可以完成),我們不能立即與媒體檔案進行互動。相反,我們必須等待MediaOpened事件的觸發。如果由於某些原因,媒體檔案無法載入,那麼MediaFailed事件就會被觸發。Subservient Cat應用程式沒有使用手動調用Play的方法,那是因為它使用了MediaElement的自動播放特性。但如果不使用其自動播放的特性,就必須在MediaElement_MediaOpened事件處理函數中調用Play方法。
注意:為什麼在手機串連到PC機的Zune後,無法播放手機上的視頻?
這個原因其實在前一章中已經解釋過。Zune是一個傳統型應用程式,它會鎖定手機的媒體庫,這就導致了MediaElement無法載入媒體檔案。記住,如果我們需要調試應用程式中視頻播放相關的功能,可以使用Windows Phone Developer Tools 中提供的Windows Phone Connect Tool工具來串連手機,而不是通過Zune來串連。
在Subservient Cat應用程式中,我們可以通過MediaFailed事件來檢測這種情況。當然,我們假設這種情況的出現就是由於Zune的串連,因為對於應用程式來說,該視頻檔案就是本地的檔案。
➔ PlayClip方法可以使視頻暫停,回到beginTime參數指定的起始時間點,重新初始化videoTimer,使得視頻可以在endTime參數制定的終止時間停止播放。但是,由於設定MediaElement的Position會帶來一些不友好的效果,如視頻會快速前進或者快速回退到指定的時間點(而不是即刻的跳轉),應用程式的簡介頁面已經對這種過渡進行了視頻隱藏處理(我們不希望展示哪些有待使用者發掘的視頻片段)。Position的設定在BeginInvoke回呼函數中完成,使得簡介頁面可以進行顯示。如果不是這樣做的話,我們就會看到不想要的情況出現。視頻延時的長度設定為2秒,這段時間可以讓使用者在簡介頁面上瀏覽指令。我們沒有辦法獲知使用者真實消耗的時間,但2秒鐘已經足夠長的了。
注意:在我們設定MediaElement的Position參數後,效果無法即刻顯現!
相反,我們可以看到目標時間點之前或之後的一小段視頻,就像那種看快進或者是快退的效果。因為Subservient Cat應用程式使用的方法是:暫時採用其他的元素來遮蓋視頻。
在當前的Windows Phone版本中,MediaElement元素並不支援標記。使用標記來區分cat.wmv視頻檔案中單獨的視頻片段,這是一個理想的方案,而且還可以大幅度減少背後的處理代碼。但是,使用DispatcherTimer來通知應用程式相關的視頻已經播放完畢,這也是一個可替代的方案。下面是需要注意的兩個事項:
1. 定時器的精度沒有達到“幀”的層級。本應用程式使用的視頻,在每個片段的最後使用了一些緩衝,以防videoTimer的Tick事件觸發滯後。
2. 如果我們想要彈出一個訊息框,視頻檔案會在後台繼續播放,但是定時器的Tick事件處理不能被調用。無論視頻播放多長時間,直到訊息框解除才能恢複Tick事件處理(MessageBox.Show是一個阻塞的操作)。這正是為何在原始碼中,首先使用DiscoveredButton_Click來暫停視頻的播放。
當我開始寫Subservient Cat應用程式的時候,我在OnNavigatedFrom事件中調用了MediaElement的Stop方法,因為在簡介頁面顯示,而首頁面處於堆棧中時,我擔心不必要的視頻播放會引來效能的下降。但是,事實證明這種擔心是多餘的,因為在頁面離開時,MediaElement會暫停所播放的視頻。如果我們不需要這種特性(例如,在其他頁面時,我還想聽到視頻播放的聲音),我們必須將MediaElement附加到某個幀,而不是一個特定的頁面。
MediaElement和隔離儲存空間中的檔案
如果想要播放隔離儲存空間中的檔案(例如,我們的應用程式下載並將它儲存在此處),我們可以調用MediaElement的SetSource方法,該方法以一個流為參數,而不是一個URI。有了它,我們就可以傳入一個合適的IsolatedStorageFileStream。