Windows Phone 7 – Background Audio Playerlist Application
在Windows Phone 7.0的時候,如果有下載過KKBOX的用戶一定覺得…為什麼明明在執行KKBOX,只要按下
Start鍵或Screen Locked之後,音樂就沒了就要重新在開啟程式,為了讓音樂可以正常的播放,有時也要開成
不自動關閉螢幕,非常的不方便。到了WP7.1之後,在Mango的500個API中針對這個問題提供了解決的方案,
也是此篇要學習的主要內容。不多說,往下來看組合背景音樂程式必要的元素與架構:
〉Background Audio Architecture:
在整個Background運作架構裡,WP7提供了一個Background Agent的概念,提供開發人員可以用簡易方式來控制背景任務,
完成背景播放音樂、下載、設定提醒等API提供的背景功能。然而,在Windows Phone 7播放音樂/影片均需一個多媒體模組:
Zune Media Queue來完成播放(例如,開啟Zune來開啟音樂、Radio、Video等),因此,在Background Audio Application裡面
將需要向Zune Media Queue下達操作的指令進一步控制需要播送的多媒體檔案或串流。
要實作一個Background Audio Application來與Zune Media互動之前有幾個最重要的控制元件需要先了解一下,如下:
主要透過該類別BackgroundAudioPlayer.Instance實例化之後,與Zune Media Queue進行互動。因此,可以透過
BackgroundAudioPlayer.Instance取得目前Zune播放的多媒體資訊、播放狀態等。另外要注意的是:
當使用該類別後會影響目前手機正在播放的程式。例如:如果正開啟Zune在播放音樂,再開啟自己的App將會關掉Zune的程式。
b. Universal Volume Control (UVC):
它是個多媒體控制項目的集合,當在螢幕鎖定(Screen Lock)時,用戶操作硬體音量鍵盤時或播放目前歌曲資訊時,
類別出現Zune原版的控制面板(好處是Windows Phone 7的開發者不用自己實作這一塊),如下圖:
c. AudioPlayerAgent:
它為控制實際播放的元件,接受與UVC事件所傳達的任務(如:播放下一首、暫停等),它與BackgroundAudioPlayer.Instance不一樣,
BackgroundAudioPlayer.Instace是用於與Zune Media Queue互動,真正負責播放Background Audio Application裡的多媒體與接受用
戶操作需求的是由該類別來負責。除此之外該類別也支援實際播放清單(Playlist logic)。
f. AudioTrack:
該類別用於呈現目前播放音軌的metedata,包括:標題、作者、URI…等資料。如果該Track的URI被設定為null時,系統會自動假設
您指定的音軌為MediaStreamSource,在這個情況下可以透過Tag屬性來從AudioPlayerAgent傳遞資料給AudioStreamingAgent。
g. AudioStreamer:
該物件被實作是經由BackgroundAudioPlayer呼叫OnBeginStreaming(AudioTrack, AudioStreamer)所產生。
有了上述元件組合一個Background Audio Application的概念後,接下來針對Background Audio Application的類型,
可以針對播放的資料來源類型分成二種:Background Audio Playlist Application與Background Audio Streaming Application。
本篇針對Background Audio Playlist Application進行說明。另一個類型可到<>進行閱讀。
(1) Background Audio Playlist Application:
該類型的Application主要實作一個播放清單(Playlist Logic)的應用,播放清單中記錄每一個播放媒體的URI,藉由指定每一個URI載入
要播放的多媒體至Zune Media Queue之中,並且讓用戶操作UVC播放自己指定的音樂清單。然而,該URI可以是Local手機檔案或是遠端
的檔案內容,但在選擇Audio檔案時,需要注意選擇是Zune可以播放的格式內容,詳細內容可參考<Supported Media Codecs for Windows Phone>。
至於要實作Background audio playlist application的話,需要實作二個重點要元素,透過MSDN所繪製的下圖加以說明:
透過上圖的定義,可以看出一個Background Audio Playlist Application的運作方式:
(1) 透過實作Application UI來操作Background Audio Player的Instance進行播放清單、多媒體播放進度的控制或存取多媒體資訊;
(2) Background Audio Player收到指令後,向Zune Media Queue下達對應的操作事件讓Audio進行播放等動作;
(3) 實作AudioPlayerAgent是讓Application啟動播放後,向Windows Phone 7註冊一個Background Agent,讓Agent
可以在背景接收Application UI向BackgroundAudioPlayer下達的指定,完成播放的動作。
往下細部說明:
a. 綠色的部分是需要實作的Code:
a-1. Application控制播放的UI(Application UI):
該Application UI如果是使用Visual Studio建立的Template那MainPage.xaml即是Application UI,在MainPAge.xaml.cs則需要
實作BackgroundAudioPlayer.Instance去控制Zune Media Queue,包括初始化、播放、暫停、取得目前播放資訊等功能。
a-2. AudioPlayerAgent真正控制的類別:
該Application使用的AudioPlayerAgent會經由系統自動實作一個Agent配置到您的Application,該Agent負責處理所以User
在Application UI透過BackgroundAudioPlayer.Instace所下達的任何動作,甚至接收UVC的操作來影響Zune Media Queue的播放。
b. 白色的部分是系統自己實作的部分,但透過實作的部分即可以間接操作播放的程序;
c. AudioPlayerAgent需要實作的重要事件處理:
c-1. OnUserAction(BackgroundAudioPlayer, AudioTrack, UserAction, Object):
當用戶透過Application UI或UVC操作目前播放的Agent時,即會觸發該事件,並且透過識別UserAction(如下表)的類型,
完成控制與處理目前要求的任務。
識別目前接收到用戶下達的動作類型;主要以列舉值呈現,如下:
類型 | 說明 | |
Stop | 停止曲目。 | |
Pause | 暫停曲目。 | |
Play | 播放或繼續曲目。 | |
SikpNext | 跳過下一個曲目。 | |
SkipPrevious | 跳過上一個曲目。 | |
FastFoward | 快進目前播放的曲目。 | |
Rewind | 倒帶目前播放的曲目。 | |
Seek | 在目前曲目中移動播放進度至指定的位置。 |
c-2. OnPlayStateChanged(BackgroundAudioPlayer,AudioTrack, PlayState):
當播放的曲目狀態改變時,即會觸發該事件。
類型 | 說明 | |
Unknown | 目前的狀態是無法定義的。例如:當新的AudioTrack有設定,但playback未被初始化。 | |
Stopped | 目前應用程式沒有播放任何曲目。 | |
Paused | 應用程式暫停播放曲目。 | |
Playing | 應用程式目前正在播放曲目。 | |
BuffingStarted | 現在曲目正在啟動緩衝。 | |
BufferingStoped | 現在曲目正在停止緩衝。 | |
TrackReady | 現在曲目準備好可以進行播放。 | |
TrackEnded | 最後一個曲目結束與應用程式將被觸發轉換曲目。 | |
Rewinding | 目前的曲目被要求倒帶。 | |
FastFowarding | 目前的曲目被要求快進播放。 | |
Shutdown | 應用程式被關閉。 | |
Error | 播放時發生錯誤。 |
c-3. OnError(BackgroundAudioPlayer, AudioTrack, Exception, Boolean):
當播放曲目發生錯誤時,例如:下載曲目發生錯誤、播放音檔有問題…等問題都會觸發該事件,此時,可以透過參數中的
BackgroundAudioPlayer參數進行接下來要播放的控制,並且透過Exception識別錯誤的原因。
由於該事件被呼叫預設會關閉Agent的運作,因此,需要特別對它進行處理,以免讓自己的程式整個被關掉。
c-4. NotifyComplete:
當Agent已經完成了所有的任務透過呼叫該方式,通知系統該Agent完成任務可以回到當前執行的系統流程。如果Agent執行完,
沒有呼叫該方法會怎樣呢?那系統分配給Agent執行用到的資源就不會回收,將會造成多個thread(包括背景)都在爭資源最後自己
的Application可能就會被自動關閉或釋放。所以要記得加。
[範例說明]
了解了Background Audio Playlist Application的概念之後,往下便參考<How to: Play Background Audio for Windows Phone>中的介紹,
我針對多媒體資源的部分加以處理。如下:
a. 建立二個專案:Application UI與AudioPlayerAgent:
a-1. 記得在主專案中加入Agent的專案參考,例如:BackMedia要加入ApAgent;
a-2. 在BackMedia的WMAppManifest.xml中加入指定參考的<Task />與指定的類型:
<Tasks>
<DefaultTask Name="_default" NavigationPage="MainPage.xaml" />
<!-- 加入指定的ExtenedTask 為BackgroundTask,
並且specifier = AudioPlayAgent -->
<ExtendedTask Name="BackgroundTask">
<BackgroundServiceAgent Specifier="AudioPlayerAgent" Name="ApAgent"
Source="ApAgent" Type="ApAgent.AudioPlayer" />
</ExtendedTask>
</Tasks>
b. 先實作Application UI需要操作,包括:播放、暫停、上/下一首曲目、擷取目前曲目資訊:
[注意]
在開發之前,記得在MainPage.xaml.cs檔中引入「using Microsoft.Phone.BackgroundAudio;」這樣才能取得Background Audio Agent的實例。
b-1. 註冊三個按鈕項目:播放、上一首、下一首的處理事件;
1: //處理按下Applicaiton UI中上一首曲目的事件
2: private void btnPrevTrack_Click(object sender, RoutedEventArgs e)
3: {
4: BackgroundAudioPlayer.Instance.SkipPrevious();
5: }
6:
7: //處理按下Applicaiton UI中播放/暫停的按鈕的事件
8: private void btnPlayTrack_Click(object sender, RoutedEventArgs e)
9: {
10: //PlayState需先引用using Microsoft.Phone.BackgroundAudio;才能取得到
11: //識別目前的播放狀態為Playing,則代表要暫停播放
12: if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState)
13: {
14: BackgroundAudioPlayer.Instance.Pause();
15: }
16: else
17: {
18: //識別目前的播放狀態非Playing,則代表要播放
19: BackgroundAudioPlayer.Instance.Play();
20: }
21: }
22:
23: //處理按下Applicaiton UI中下一首的按鈕的事件
24: private void btnNextTrack_Click(object sender, RoutedEventArgs e)
25: {
26: BackgroundAudioPlayer.Instance.SkipNext();
27: }
b-2. 註冊處理BackgroundAudioPlayer.Instance.PlayStateChanged的事件;
透過處理PlayStateChanged的事件來監控目前播放的狀況,並且配合識別目前播放的Track是否為空,來取得播放曲目資訊。
1: public MainPage()
2: {
3: InitializeComponent();
4: //在畫面初始化時,就註冊PlayStateChanged事件
5: BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(Instance_PlayStateChanged);
6: }
7:
8: /// <summary>
9: /// 處理BackgroundAudioPlayer.Instance.PlayerState狀態改變的事件。
10: /// </summary>
11: protected void Instance_PlayStateChanged(object sender, EventArgs e)
12: {
13: switch (BackgroundAudioPlayer.Instance.PlayerState)
14: {
15: case PlayState.Playing:
16: btnPlayTrack.Content = "暫停";
17: break;
18: //Paused與Stopped都識別接下來是要按播放才會繼續
19: case PlayState.Paused:
20: case PlayState.Stopped:
21: playButton.Content = "播放";
22: break;
23: }
24:
25: //識別如果目前播放的Track不為null,則可以取得目前播放的資訊
26: if (BackgroundAudioPlayer.Instance.Track != null)
27: {
28: txtTrackInfo.Text = string.Format("Title: {0}, Artist: {1}",
29: BackgroundAudioPlayer.Instance.Track.Title,
30: BackgroundAudioPlayer.Instance.Track.Artist);
31: }
32: }
c. 實作AudioPlayerAgent:
當透過Visual Studio的Template建立AudioPlayerAgent專案時,它會自動建立一份繼承AudioPlayerAgent的.cs檔,其中也註冊了一些事件:
c-1. 事件介紹:
c-1-1. Application.Current.UnhandledException (object, ApplicationUnhandledExceptionEventArgs):
註冊該事件處理當BackgroundAudioAgent執行時遇到不可預期的Exception時,可以透過該事件關掉Agent。
c-1-2. OnPlayStateChanged(BackgroundAudioPlayer, AudioTrack, PlayState):
註冊該事件處理當播放狀態改變時需要處理的事件。它可以透過參數的BackgroundAudioPlayer取得目前正在操作的Instace、
正在播放的Track與指定的PlayState狀態,接著依照指定的PlayState處理對應的工作,最後呼叫「NotifyComplete()」結束任務。
c-1-3. OnUserAction(BackgroundAudioPlayer, AudioTrack, UserAction, object):
註冊該事件處理使用者操作BackgroundAudioPlayer.Instance或是UVC的動作。該事件的重點在於:UserAction的參數,
透過該參數識別要對播放的Instance進行何種動作,最後記得呼叫「NotifyComplete()」結束這個任務。
c-1-4. OnError(BackgroundAudioPlayer, AudioTrack, Exception, bool):
當BackgroundAudioPlayer.Instace執行時遇到Exception即會進入該事件,最常發生的情境在於指定的AudioTrack無法找到URI
或是下載失敗等問題。
c-1-5. OnCancel():
該事件發生於當系統通知Agent進行取消任務時,Agent需要釋放所有的資源等任務,最後在配合NotifyComplete或Abort告訴系統
已完成任務。該事件常用於當取消Agent使用時,例如:ScheduleTaskAgent取消或不需要在播放背景音樂時。
c-2. 實作要播放的AudioTrack清單:
1: private static List<AudioTrack> gPlayList = new List<AudioTrack>()
2: {
3: //此處的範例是使用MMS為例
4: new AudioTrack(new Uri("mms://bcr.media.hinet.net/RA000040", UriKind.Absolute),"大眾廣播","KissRadio","",null),
5: new AudioTrack(new Uri("mms://bcr.media.hinet.net/RA000036", UriKind.Absolute), "HitFM聯播網", "HitFM","北部",null)
6: };
c-3. 實作在OnPlayStateChanged事件觸發時,針對PlayState.TrackReady狀態進行播放的動作:
1: protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
2: {
3: switch (playState)
4: {
5: case PlayState.TrackEnded:
6: break;
7: case PlayState.TrackReady:
8: //針對Track連線成功後,進行播放動作
9: player.Play();
10: break;
11: case PlayState.Shutdown:
12: // TODO: Handle the shutdown state here (e.g. save state)
13: break;
14: case PlayState.Unknown:
15: break;
16: case PlayState.Stopped:
17: break;
18: case PlayState.Paused:
19: break;
20: case PlayState.Playing:
21: break;
22: case PlayState.BufferingStarted:
23: break;
24: case PlayState.BufferingStopped:
25: break;
26: case PlayState.Rewinding:
27: break;
28: case PlayState.FastForwarding:
29: break;
30: }
31:
32: NotifyComplete();
33: }
c-4. 實作在OnUserAction事件觸發時,針對Play、Pause、SkipNext與SkipPrevious進行處理:
1: //定義一個static的變數記錄目前播放的Index
2: private static int gCurrentTrack = 0;
3:
4: protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
5: {
6: switch (action)
7: {
8: case UserAction.Play:
9: if (player.PlayerState != PlayState.Playing)
10: {
11: player.Track = gPlayList[gCurrentTrack];
12: player.Play();
13: }
14: break;
15: case UserAction.Stop:
16: player.Stop();
17: break;
18: case UserAction.Pause:
19: player.Pause();
20: break;
21: case UserAction.FastForward:
22: player.FastForward();
23: break;
24: case UserAction.Rewind:
25: player.Rewind();
26: break;
27: case UserAction.Seek:
28: player.Position = (TimeSpan)param;
29: break;
30: case UserAction.SkipNext:
31: if (++gCurrentTrack >= gPlayList.Count)
32: gCurrentTrack = 0;
33: player.Track = gPlayList[gCurrentTrack];
34: break;
35: case UserAction.SkipPrevious:
36: if (--gCurrentTrack < 0)
37: {
38: gCurrentTrack = gPlayList.Count - 1;
39: }
40: player.Track = gPlayList[gCurrentTrack];
41: break;
42: }
43:
44: NotifyComplete();
45: }
d. 實作結果:
[範例程式]
上述實作Application UI的部分可以看出只需要實作操作BackgroundAudioPlayer.Instance與PlayerState改變對應的事件,
就可以完成透過UI來控制背景播放的功能。至於UVC的部分呢,不用擔心,它會自動由系統產生並擷取Track的資訊。另外,
在實作AudioPlayerAgent時就可以清楚分出,UI就像我們的看電視的搖控器,透過搖控器告訴電視要播放什麼節目台,接著
AudioPlayerAgent就相似電視機幫我們轉到指定的電視進行實際的播放。
[注意事項]
a. Application UI與Background Agent二個是屬於不同的程式,因此,IsolatedStorageSettings是不能共用的;
b. Application UI與Background Agent需要透過IsolatedStorage中的資源來進行參數傳遞;
c. 控制放清單時,UVC的操作會直接影響BackgroundAudioPlayer,
如果Application UI在前景,將會觸發PlayStateChanged事件;如果在背景執行會直接先觸發OnUserAction事件,
因此,Application UI與Background Agent都會影響播放索引值,要特別注意管理它的變化才會二邊都一致。
d. 撰寫播放音樂/影片的App時,需要在播放時加上判斷目前是否有其他程式正在播放音樂,如下程式碼:
1: using Microsoft.Xna.Framework;
2:
3: //確認是否目前有多媒體正在播放
4: public static void CheckAvailablePlay()
5: {
6: //using Microsoft.Xna.Framework.Media.MediaPlayer進行識別
7: if (MediaPlayer.State == MediaState.Playing)
8: {
9: MessageBoxResult tChoice = MessageBox.Show("目前有播放的程式,是否取代?",
10: "Information", MessageBoxButton.OKCancel);
11: if (tChoice == MessageBoxResult.OK)
12: {
13: //記得加上Update的方法,可以通知Zune要進行狀態更新
14: FrameworkDispatcher.Update();
15: MediaPlayer.Stop();
16: BackgroundAudioPlayer.Instance.Play();
17: }
18: }
19: }
f. 使用Background Agent由於與Application是同的控制單位,所以沒辦法用MessageBox,如果需要彈出訊息,
請改用ShellToast。
======
以上的內容從< Background Audio Overview for Windows Phone >取得較多,並加上一些開發時遇到的觀念問題。
WP 7.1 SDK裡針對Background Agent的部分提供了許多好用的Agent,讓開發者可以專注於開發Agent對應Service
要完成的任務,放較少心思去管理執行緒之間的關係,這也是開發其他App中我覺得相對而言比較容易的部分,但是
不代表執行緒的管理就不重要,因為一個App用的Background Agent是有限,主要是避免同一個App用了過多的Agent,
造成效能不佳或管理上的問題。接下來還會有幾篇針對Background Agent的說明與筆記。謝謝。
References:
‧Multitasking for Windows Phone (原理,必讀)
‧Background Audio Overview for Windows Phone (重要)
‧How to: Play Background Audio for Windows Phone (必讀)
‧Scheduled Actions Overview for Windows Phone
‧Microsoft.Phone.Scheduler Namespace
‧Windows Phone Mango–What’s New? (“Background Agents” - Part 5 of 8) (必讀)
‧How to: Create Reminders for Windows Phone
‧Scheduled Tasks Overview for Windows Phone
‧Windows Phone 7.1 Overview – App Connect (必讀)
‧Windows Phone 7.1 Overview (2) – Background Agents (必讀)
‧Background agents will be limited to 10% CPU and 5 MB memory (重點要注意的部分)
‧CaptureSource.CaptureImageAsync Method & VideoBrush.SetSource Method
‧Supported Media Codecs for Windows Phone
‧PlayState Enumeration & AudioPlayerAgent.OnUserAction Method