后台音頻
后台播放音頻的應用程序,顧名思義,即使用戶已通過按"返回"按鍵或"開始"按鍵離開應用程序也可以繼續播放音頻。
后台音頻體系結構
后台音頻應用程序利用后台代理(Windows Phone OS 7.1 中的新增內容)。Windows Phone上的所有媒體都是通過Zune媒體隊列播放的。后台音頻應用程序向Zune媒體隊列發指令控制當前堆棧、播放音樂、暫停音樂,以及快進和后退操作。那么這些是如何實現的呢?其實是調用BackgroundAudioPlayer類中方法來完成控制操作,Instance對象與Zune媒體隊列通信以操作音頻的播放。
有兩種類型的后台音頻應用程序。一種類型實現簡單的播放列表並將一個包含媒體文件地址的URI傳遞給Zune媒體隊列以設置當前曲目。URI可以是手機的本地或遠程地址,在任何一種情況下,URI指向的音頻必須是Windows Phone支持的類型才能播放。另一種類型的后台音頻應用程序使用MediaStreamSource實現音頻流的播放,此音頻流的格式則可以是任何格式。這是因為由MediaStreamSource派生的類實現了對音頻流的處理和解碼。
1) 后台音頻播放列表應用程序
若要創建后台音頻播放列表需實現兩個部分:提供用於控制播放的用戶界面和實現從AudioPlayerAgent 派生的類,即下圖中以綠色顯示的部分。
圖后台音頻播放列表應用程序
應用程序UI提供控制音頻播放的用戶界面。通過UI的交互調用Instance對象設置Zune媒體隊列中的當前曲目。AudioPlayerAgent由操作系統進行實例化,並應用程序的用戶界面或UVC處理交互操作。
AudioPlayerAgent在后台運行並調用BackgroundAudioPlayer的實例,該實例調用 Zune 媒體隊列以實際播放音頻。
您可使用Visual Studio中的"Windows Phone Playback Agent"模板創建新的AudioPlayerAgent項目並將該項目添加到您的解決方案中。
圖創建新的AudioPlayerAgent項目
從主應用程序項目中添加對新AudioPlayerAgent的引用。即右鍵單擊應用程序項目中的"引用"節點,然后選擇"添加引用...",如下圖所示。
圖 添加引用
2) 音頻流代理的應用程序
對於音頻流的應用程序,需實現從AudioStreamingAgent派生的類和MediaStreamSource派生的類。您即實現下圖中以綠色顯示的部分。音頻流代理AudioStreamingAgent 負責創建MediaStreamSource並將Zune媒體隊列指向該實例。
圖 后台音頻流應用程序
使用Visual Studio中的"Windows Phone Audio Streaming Agent"模板創建新的AudioStreamingAgent項目並將該項目添加到解決方案中。
圖 創建新的AudioStreamingAgent項目
3) 后台音頻生命周期
AudioPlayerAgent和AudioStreamingAgent雖然都是由BackgroundAudioPlayer創建,但是其各自的生命周期卻有所不同。
AudioPlayerAgent是由BackgroundAudioPlayer在需要時創建的,用於處理來自應用程序的用戶界面或通用音量控制(UVC)的用戶操作請求。
AudioStreamingAgent是由BackgroundAudioPlayer在需要新的音頻流時創建的。當新曲目由於要開始播放而需要音頻流時,BackgroundAudioPlayer在AudioStreamingAgent 中調用OnBeginStreaming方法。
當后台代理調用 Abort()()()()或NotifyComplete()()()()時釋放后台代理。
4) 實現音頻播放的類
BackgroundAudioPlayer
BackgroundAudioPlayer類與Zune媒體隊列通信,通過Instance控制正在播放的音頻。調用BackgroundAudioPlayer類的Instance屬性返回BackgroundAudioPlayer的實例。如果此應用程序已分配后台音頻播放資源,則返回的BackgroundAudioPlayer將包含對這些資源的引用。
注意:
每個應用程序只能有一個BackgroundAudioPlayer處於活動狀態。
AudioTrack
AudioTrack類表示有關某個曲目的元數據,包括標題、藝術家、專輯以及曲目的URI。如果URI設置為null,則系統則默認曲目設置為MediaStreamSource。在這種情況下,可使用Tag屬性將AudioPlayerAgent中的信息傳遞給AudioStreamingAgent。
AudioStreamer
AudioStreamer的實例傳遞給OnBeginStreaming(AudioTrack, AudioStreamer)。在OnBeginStreaming的實現中,調用SetSource(MediaStreamSource)指向從MediaStreamSource派生的類。
后台音頻的程序的認證要求
后台音頻的程序屬於特定應用程序類型,除了一般應用程序的要求需滿足外,還需符合如下的要求。
1) 該應用程序不得啟動后台音頻播放,除非用戶激活該應用程序提供的可發現 UI 元素。
驗證方法:
- 啟動應用程序;
- 驗證該應用程序中是否存在可發現的UI元素,並允許后台音頻播放;
- 激活UI元素以開始后台音頻播放;
- 驗證后台音頻播放是否開始。
2) 當該應用程序位於前台中時,它必須向用戶提供可發現的UI元素,允許用戶停止音頻播放。
驗證方法:
- 啟動應用程序;
- 開始音頻播放;
- 驗證應用程序內是否可發現停止音頻播放的UI元素;
- 激活UI元素以停止音頻播放;
- 驗證音頻播放是否停止。
3) 當用戶點按統一音量控制上的"停止"按鍵時,應用程序必須停止后台中播放的音頻。如果播放服務支持暫停操作,則通過統一音量控制暫停后台音頻必須根據用戶的操作暫停或重新啟動音頻。
驗證方法:
- 啟動應用程序;
- 開始音頻播放;
- 關閉該應用程序;
- 驗證該音頻是否繼續在后台中播放;
- 查看統一音量控制;
- 如果播放服務支持暫停命令,則可以通過統一音量控制暫停音頻,驗證播放是否暫停;通過統一音量控制重新啟動音頻,驗證播放是否重新啟動;
- 通過統一音量控制停止音頻播放;
- 驗證播放是否停止;
4) 當從應用程序中播放后台音頻時,該應用程序發送的要顯示在統一音量控制中的元數據必須對該音頻進行描述(例如歌曲、曲目、藝術家、播放狀態或應用程序名稱)。應用程序錯誤代碼不得顯示在統一音量控制中。禁止在統一音量控制中插入廣告和其他不相關的內容。
驗證方法:
- 啟動應用程序;
- 開始音頻播放;
- 關閉該應用程序;
- 驗證該音頻是否繼續在后台中播放;
- 查看統一音量控制;
- 驗證該音頻播放的元數據是否顯示以及是否與音頻內容相關。
5) 使用AudioStreamingAgent API的應用程序只能用於執行與音頻播放及關聯元數據管理相關的任務。
驗證方法:
- 啟動應用程序;
- 關閉該應用程序;
- 驗證后台音頻流代理是否僅用於傳輸指定音頻內容及進行相關的元數據管理。
后台音頻的最佳實踐
1) 調試
在"編碼—調試—編輯代碼—重新啟動調試"的過程中,調試器可能會在您重新啟動調試時將自身連接到后台音頻代理,而不是您的應用程序。若要避免出現這種情況,您需要確保后台代理在應用程序啟動時處於關閉狀態。要實現這一點,有一種簡便的方法是將對Close()()()()的調用放到App類的構造函數中。如果您是通過Visual Studio模板創建的應用,則App類構造函數中會包含一項檢查,以了解該應用程序是否正在調試器下運行。可將對BackgroundAudioPlayer.Instance.Close的調用放在此if語句中。
// Show graphics profiling information while debugging.
if (System.Diagnostics.Debugger.IsAttached)
{
// Close the background audio player in case it
// was running from a previous debugging session.
BackgroundAudioPlayer.Instance.Close();
// ...
}
2) 處理用戶操作
BackgroundAudioPlayer未對用戶操作(UserAction)請求的數量施加任何限制。通過UI(應用界面或通用音量控制即UVC)調用的操作排成在隊列中處理。AudioPlayerAgent沒有確定是否存在連續調用的方法。需要網絡請求的SkipNext、SkipPrevious或Play調用可能需要幾秒鍾的時間。
- 無論調用花費的時間長短,都將按順序處理對SkipNext、SkipPrevious或Play的連續調用。
- 每次調用允許30秒的時間。
- 由於Play和Pause操作未獲得優先權,因此代理可能花費幾秒鍾(甚至幾分鍾)的時間才能響應用戶操作。
因此限制排隊的SkipNext和SkipPrevious請求的數量並給予Play和Pause較高的優先級。后台音頻播放器示例只是在處理請求之前禁用"下一個"和"上一個"按鈕。從 Windows Phone 的代碼示例頁面中獲得后台音頻播放器示例的代碼。
MediaElement和BackgroundAudioPlayer
混合BackgroundAudioPlayer和MediaElement要注意:
- 必須在切換到MediaElement 播放之前調用Close()()()()。
- 只有一個媒體隊列。您的應用程序不能暫停后台音頻,不能使用 MediaElement 播放某些內容,而且不能繼續播放后台音頻流。
內存和運行時約束
- AudioPlayerAgent 的實現必須在30秒內調用NotifyComplete()()()()或Abort()()()()。
- AudioStreamingAgent 的實現則允許無限時運行。
- 兩種類型的后台音頻代理都托管在同一進程中,並且共用最大限制為 15 MB 的內存。
- 在調試器下運行時,Windows Phone 操作系統會忽略內存和運行時約束。
動手實踐——音樂播放器
本節的重點內容是實現音樂播放器的后台音樂播放功能,即在應用程序處於非激活狀態時持續的播放音樂。本節的涉及的技術如何實現使用音頻播放代理播放本地媒體的 Windows Phone 應用程序。
任務1——創建Windows Phone后台音頻應用程序
- 通過選擇"文件|新項目..."菜單命令創建一個新項目。
- 將顯示"新建項目"對話框。展開"Visual C#"模板,然后選擇"Silverlight for Windows Phone"模板。
- 選擇"Windows Phone應用程序"模板。根據需要填寫"MusicPlayer",然后單擊"確定"。
- 在"解決方案資源管理器"中,右鍵單擊"解決方案"節點,然后選擇"添加 | 新項目..."。
- 在"添加新項目"對話框中,單擊"Windows Phone音頻播放代理"。
- 在名稱中填寫"AudioPlaybackAgent",然后單擊"確定"。至此,解決方案應該具有兩個項目,即應用程序項目和后台代理項目。
任務2——添加UI和事件處理函數
在此任務中添加MainPage的UI和后台音頻Microsoft.Phone.BackgroundAudio.BackgroundAudioPlayer的事件處理函數。
打開 MainPage.xaml 文件
在文件首部的phone:PhoneApplicationPage中綁定DataContext的相關資源屬性。
DataContext="{Binding RelativeSource={RelativeSource Self}}"
定位到ContentPanel的標簽,在其中添加HorizontalAlignment屬性,設置其為"Stretch"。
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" HorizontalAlignment="Stretch">
在Grid中增加對於行屬性的定義和TextBlock,TextBlock綁定播放列表的名稱。
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding ActivePlaylist.Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
</Grid>
增加顯示播放曲目的ListBox,代碼如下。
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding ActivePlaylist.Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<ListBox ItemsSource="{Binding ActivePlaylist.Tracks}" Grid.Row="1" x:Name="lstTracks" HorizontalContentAlignment="Stretch" Loaded="lstTracks_Loaded">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Margin="10,0" Source="{Binding Tile, TargetNullValue={StaticResource NoArt}}" Grid.RowSpan="3" Width="60" Stretch="Uniform" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Title}" Grid.Column="1" FontSize="{StaticResource PhoneFontSizeLarge}"/>
<TextBlock Text="{Binding Artist}" Grid.Row="1" Grid.Column="1"/>
<TextBlock Text="{Binding Album}" Grid.Row="2" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
打開MainPage.xaml.cs,添加命名為lstTracks_Loaded的函數。
publicpartialclassMainPage : PhoneApplicationPage
{
...
private void lstTracks_Loaded(object sender, RoutedEventArgs e)
{
}
...
}
編譯並在模擬器中運行的效果如下所示。
圖 運行效果
定義播放列表類Playlist的依賴屬性,將其命名為ActivePlaylist。
publicpartialclassMainPage : PhoneApplicationPage
{
public Playlist ActivePlaylist
{
get { return (Playlist)GetValue(ActivePlaylistProperty); }
set { SetValue(ActivePlaylistProperty, value); }
}
public static readonly DependencyProperty ActivePlaylistProperty =
DependencyProperty.Register("ActivePlaylist", typeof(Playlist), typeof(MainPage), newPropertyMetadata(null));
...
}
重載OnNavigatedTo方法,讀取獨立存儲空間中的播放列表。
publicpartialclassMainPage : PhoneApplicationPage
{
...
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
Stream playlistStream = Application.GetResourceStream(new Uri("Misc/Playlist.xml", UriKind.Relative)).Stream;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Playlist));
ActivePlaylist = (Playlist)serializer.Deserialize(playlistStream);
using ( IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication() )
{
using ( IsolatedStorageFileStream file = isoStorage.OpenFile("playlist.xml", FileMode.OpenOrCreate) )
{
var writer = new StreamWriter(file);
serializer.Serialize(writer, ActivePlaylist);
}
}
base.OnNavigatedTo(e);
}
...
}
在MainPage的構造函數中,添加PlayStateChanged事件訂閱的代碼。
publicpartialclassMainPage : PhoneApplicationPage
{
...
public MainPage()
{
InitializeComponent();
BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(Instance_PlayStateChanged);
}
...
}
添加事件處理函數的響應代碼,實現當后台音頻播放器運行時,更新播放曲目和播放狀態。
publicpartialclassMainPage : PhoneApplicationPage
{
...
privatevoid Instance_PlayStateChanged(object sender, EventArgs e)
{
UpdateSelection();
}
privatevoid UpdateSelection()
{
int activeTrackNumber = GetActiveTrackIndex();
if ( activeTrackNumber != -1 )
{
lstTracks.SelectedIndex = activeTrackNumber;
}
}
privateint GetActiveTrackIndex()
{
int track = -1;
if ( null != BackgroundAudioPlayer.Instance.Track )
{
track = int.Parse(BackgroundAudioPlayer.Instance.Track.Tag);
}
return track;
}
...
}
在應用程序處於不活動狀態時,后台音頻播放器也允許用戶更改播放的曲目。若要實現此功能,需要更新的lstTracks_Loaded方法,在該方法中插入以下代碼。
publicpartialclassMainPage : PhoneApplicationPage
{
...
private void lstTracks_Loaded(object sender, RoutedEventArgs e)
{
UpdateSelection();
}
...
}
編譯並運行此程序,首頁呈現的內容如下。
-
圖 播放列表
任務3——添加控制按鈕
本節中將添加ApplicationBar的按鈕,控制音樂播放器播放、停止、前一個音軌和后一個音軌。
為MainPage.xaml添加PhoneApplicationPage.ApplicationBar,即在LayoutRoot結尾部分添加如下的代碼。
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="False">
<shell:ApplicationBarIconButton IconUri="/Images/prev.png" Text="prev" Click="appbar_prev"/>
<shell:ApplicationBarIconButton IconUri="/Images/play.png" Text="play" Click="appbar_playpause"/>
<shell:ApplicationBarIconButton IconUri="/Images/stop.png" Text="stop" Click="appbar_stop"/>
<shell:ApplicationBarIconButton IconUri="/Images/next.png" Text="next" Click="appbar_next"/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
在MainPage.xaml.cs添加應用程序欄(ApplicationBar)的Click事件處理函數。
public partial class MainPage : PhoneApplicationPage
{
...
#region ApplicationBar Buttons Events
private void appbar_prev(object sender, EventArgs e)
{
BackgroundAudioPlayer.Instance.SkipPrevious();
}
private void appbar_playpause(object sender, EventArgs e)
{
if ( BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing )
BackgroundAudioPlayer.Instance.Pause();
else
BackgroundAudioPlayer.Instance.Play();
}
private void appbar_stop(object sender, EventArgs e)
{
BackgroundAudioPlayer.Instance.Stop();
}
private void appbar_next(object sender, EventArgs e)
{
BackgroundAudioPlayer.Instance.SkipNext();
}
#endregion
...
}
在Instance_PlayStateChanged方法中更新應用程序欄的按鈕狀態,為此調用UpdateAppBarStatus方法。
publicpartialclassMainPage : PhoneApplicationPage
{
...
privatevoid Instance_PlayStateChanged(object sender, EventArgs e)
{
UpdateAppBarStatus();
UpdateSelection();
}
...
}
實現UpdateAppBarStatus 方法。判斷BackgroundAudioPlayer的狀態是處於播放、暫停和停止的狀態,然后分別設置"前一個"、"播放/暫停"、"停止"和"下一個"按鈕的狀態。
publicpartialclassMainPage : PhoneApplicationPage
{
...
privatevoid UpdateAppBarStatus()
{
switch ( BackgroundAudioPlayer.Instance.PlayerState )
{
case PlayState.Playing:
//Prev Button
if ( GetActiveTrackIndex() > 0 )
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = false;
//Play/Pause Button
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IsEnabled = true;
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).Text = "pause";
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IconUri = new Uri("/Images/pause.png", UriKind.Relative);
//Stop Button
( ApplicationBar.Buttons[2] as ApplicationBarIconButton ).IsEnabled = true;
//Next button
if ( GetActiveTrackIndex() < ActivePlaylist.Tracks.Count - 1 )
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = false;
break;
case PlayState.Paused:
//Prev Button
if ( GetActiveTrackIndex() > 0 )
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = false;
//Play/Pause Button
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IsEnabled = true;
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).Text = "play";
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IconUri = new Uri("/Images/play.png", UriKind.Relative);
//Stop Button
( ApplicationBar.Buttons[2] as ApplicationBarIconButton ).IsEnabled = true;
//Next button
if ( GetActiveTrackIndex() < ActivePlaylist.Tracks.Count - 1 )
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = false;
break;
case PlayState.Stopped:
//Prev Button
if ( GetActiveTrackIndex() > 0 )
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = false;
//Play/Pause Button
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IsEnabled = true;
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).Text = "play";
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IconUri = new Uri("/Images/play.png", UriKind.Relative);
//Stop Button
( ApplicationBar.Buttons[2] as ApplicationBarIconButton ).IsEnabled = false;
//Next button
if ( GetActiveTrackIndex() < ActivePlaylist.Tracks.Count - 1 )
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = false;
break;
case PlayState.Unknown:
//Prev Button
( ApplicationBar.Buttons[0] as ApplicationBarIconButton ).IsEnabled = false;
//Play/Pause Button
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IsEnabled = true;
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).Text = "play";
( ApplicationBar.Buttons[1] as ApplicationBarIconButton ).IconUri = new Uri("/Images/play.png", UriKind.Relative);
//Stop Button
( ApplicationBar.Buttons[2] as ApplicationBarIconButton ).IsEnabled = false;
//Next button
if ( GetActiveTrackIndex() < ActivePlaylist.Tracks.Count - 1 )
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = true;
else
( ApplicationBar.Buttons[3] as ApplicationBarIconButton ).IsEnabled = false;
break;
default:
break;
}
}
...
}
編譯和運行應用程序,可以看到播放列表下方新增了應用程序欄。
圖 具有應用程序欄的播放列表
- 任務4——在獨立存儲空間保存音樂和圖像文件
您可能會疑惑為什么要將曲目保存至獨立存儲空間,答案很簡單,就是為了能夠被播放器播放。
在App.xaml.cs的文件頭部添加如下的引用
using System.IO.IsolatedStorage;
using System.Windows.Resources;
-
在App類的構造函數中,添加調用CopyToIsolatedStorage方法的代碼,並實現CopyToIsolatedStorage的代碼:創建IsolatedStorageFile對象引用獨立存儲空間的類,將音頻文件寫入獨立存儲空間的"Music/"文件夾中,將專輯圖片寫入獨立存儲空間的"Shared/Media/"文件夾中。
public App()
{
...
CopyToIsolatedStorage();
}
private void CopyToIsolatedStorage()
{
using ( IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication() )
{
string[] files = new string[] { "file1.mp3", "file2.mp3", "file3.mp3" };
foreach ( var _fileName in files )
{
if ( !storage.FileExists(_fileName) )
{
string _filePath = "Music/" + _fileName;
StreamResourceInfo resource = Application.GetResourceStream(new Uri(_filePath, UriKind.Relative));
using ( IsolatedStorageFileStream file = storage.CreateFile(_fileName) )
{
int chunkSize = 4096;
byte[] bytes = new byte[chunkSize];
int byteCount;
while ( ( byteCount = resource.Stream.Read(bytes, 0, chunkSize) ) > 0 )
{
file.Write(bytes, 0, byteCount);
}
}
}
}
files = new string[] { "Image1.jpg", "Image3.jpg", "no-art.jpg" };
foreach ( var _fileName in files )
{
string _destFilePath = "Shared/Media/" + _fileName;
;
if ( !storage.FileExists(_destFilePath) )
{
string _filePath = "Images/" + _fileName;
StreamResourceInfo resource = Application.GetResourceStream(new Uri(_filePath, UriKind.Relative));
using ( IsolatedStorageFileStream file = storage.CreateFile(_destFilePath) )
{
int chunkSize = 4096;
byte[] bytes = new byte[chunkSize];
int byteCount;
while ( ( byteCount = resource.Stream.Read(bytes, 0, chunkSize) ) > 0 )
{
file.Write(bytes, 0, byteCount);
}
}
}
}
}
}
-
- 任務5——音頻播放的后台代理
后台音頻播放的代理運行在后台,是由操作系統實例化的對象,用來處理用戶的后台運行請求。AudioPlayerAgent在后台運行,調用BackgroundAudioPlayer播放Zune媒體隊列。
在解決方案中添加新的工程,工程模板為Windows Phone Audio Playback Agent。Windows Phone Audio Playback Agent模板位於Silverlight for Windows Phone 類別中,將新工程命名為AudioPlaybackAgent。
在此工程中需要用到model的定義和XML的序列化,所以在工程的引用中添加Models和System.Xml.Serialization的引用。
在AudioPlayer.cs中重構AudioPlayer類。刪除原有的代碼,在文件的頭部添加引用,代碼如下。
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using System.IO;
using Models;
在AudioPlayer類中添加靜態變量currentTrackNumber和playlist。currentTrackNumber保存當前播放的音軌,playlist保存當前的播放列表。
static int currentTrackNumber = 0;
static Playlist playlist;
AudioPlayer的構造函數,從獨立存儲空間讀取XML格式的文件列表。
public AudioPlayer()
: base()
{
//Load from IsoStore & deserialize
using ( IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication() )
{
using ( IsolatedStorageFileStream file = isoStorage.OpenFile("playlist.xml", FileMode.Open) )
{
XmlSerializer serializer = new XmlSerializer(typeof(Playlist));
var reader = new StreamReader(file);
playlist = (Playlist)serializer.Deserialize(reader);
}
}
}
重載OnPlayStateChanged 方法,實現處理播放器的狀態變化。其中調用NotifyComplete方法通知后台音頻播放器狀態的變化。
protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
{
switch ( playState )
{
case PlayState.TrackEnded:
PlayNext(player);
break;
case PlayState.TrackReady:
player.Play();
break;
default:
break;
}
NotifyComplete();
}
添加PlayNext和PlayPrev方法,實現上一個和下一個音軌的播放。
private void PlayNext(BackgroundAudioPlayer player)
{
var songsCount = playlist.Tracks.Count;
if ( ++currentTrackNumber >= songsCount )
{
currentTrackNumber = 0;
}
Play(player);
}
private void PlayPrev(BackgroundAudioPlayer player)
{
var songsCount = playlist.Tracks.Count;
if ( --currentTrackNumber < 0 )
{
currentTrackNumber = songsCount - 1;
}
Play(player);
}
重載OnUserAction方法,處理后台音頻播放器與用戶觸控的交互。
protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
{
switch ( action )
{
case UserAction.FastForward:
player.FastForward();
break;
case UserAction.Pause:
player.Pause();
break;
case UserAction.Play:
if ( player.PlayerState == PlayState.Paused )
{
player.Play();
}
else
{
Play(player);
}
break;
case UserAction.Rewind:
player.Rewind();
break;
case UserAction.Seek:
player.Position = (TimeSpan)param;
break;
case UserAction.SkipNext:
PlayNext(player);
break;
case UserAction.SkipPrevious:
PlayPrev(player);
break;
case UserAction.Stop:
player.Stop();
break;
default:
break;
}
NotifyComplete();
}
實現初始化播放當前音軌的方法:從播放列表中獲取當前音軌的源、標題、藝術家和專輯等信息,以此信息實例化AudioTrack類並播放。
private void Play(BackgroundAudioPlayer player)
{
var currentTrack = playlist.Tracks[currentTrackNumber];
Uri tileUri = ( currentTrack.Tile == null ? new Uri("Shared/Media/no-art.jpg", UriKind.Relative) :
( currentTrack.Tile.IsAbsoluteUri ? new Uri("Shared/Media/no-art.jpg", UriKind.Relative) :
new Uri(currentTrack.TileString.Replace("/Images", "Shared/Media"), UriKind.Relative) ) );
var audioTrack = new AudioTrack(currentTrack.Source,
currentTrack.Title,
currentTrack.Artist,
currentTrack.Album,
tileUri,
currentTrackNumber.ToString(),
EnabledPlayerControls.All);
player.Track = audioTrack;
}
最后一步,在MusicPlayer項目中添加AudioPlaybackAgent項目的引用。
任務6——測試應用程序
運行應用程序,單擊播放按鈕
。確認在鍵盤上按F9鍵可增加模擬器的音量,按F10鍵可降低模擬器的音量。
圖4 在模擬器中運行
按下啟動鍵
,確認音樂可以繼續播放。
圖5 后台音頻播放控制器
在鍵盤上按下F9鍵顯示后台音頻播放器的控制界面,通過觸控按鈕控制當前播放的曲目。