×
Namespaces

Variants
Actions

WP7中的MP3流播放器

From Nokia Developer Wiki
Jump to: navigation, search
WP Metro Icon Multimedia.png
SignpostIcon XAML 40.png
SignpostIcon WP7 70px.png
Article Metadata

代码示例
兼容于
文章
翻译:
最后由 hamishwillee 在 19 Jul 2013 编辑

本文介绍在Windows Phone 7如何创建一个MP3流音乐播放器。

Contents

简介

本文演示在Windows Phone 7如何创建MP3流音乐播放器。所有的音乐文件存储在服务器,并传输到Windows Phone。应用程序通过JSON数据首先从服务器加载所有的专辑的数据。在服务器端PHP脚本来加载所有的MP3文件的ID3标签,并生成JSON字符串,返回到Windows Phone。在应用程序反序列化JSON字符串到动态对象并将专辑数据显示在全景页(图3)上之后,在新的歌曲页(图片5),将显示选定的专辑中的歌曲。

用户可以从最新专辑中添加喜爱的歌曲到收藏列表(图片2)。当用户从歌曲列表选择歌曲,所选的歌曲在播放页(图片6)播放。所有所选歌曲将被添加到最近播放的歌曲中(图片1)。在播放页面, 用户可以看到专辑封面,专辑名称,歌曲标题和歌曲的播放时间。在选择列表,用户可以跳到当前播放音乐的前一首或下一首歌曲以及搜索该歌曲的位置。在全景页的设置区(图片4),所有播放列表可以重置。

全景页:

Latest Favorites Albums Settings

单个页面:

Album songs Player Page

所有专辑数据保存到独立存储器,当应用程序再次启动时并从那里加载动。当然,当在服务器端添加或删除新专辑,新专辑的数据可以从服务器端加载。当新专辑中的数据从服务器重新获取 – 最新和最喜爱的歌曲列表会被检查,一些不可用的歌曲将会从列表中删除。此应用程序使用Windows Phone背景音频代理来播放MP3音乐,所以在Windows Phone上音乐可以播放的像在原始音乐播放器播放一样。

注意:这仅仅是一个代码示例,它不包括任何MP3或专辑封面文件,所以你必须使用自己的文件和PHP服务器。

这里是一个小的演示视频,这展示应用程序是如何工作的: The media player is loading...

应用程序的后端

文件结构

这个例子PHP服务器作为这个程序的后端。创建一个的目录StreamingDemo到你的服务器并创建名字为“music”的文件夹,上传你自己的MP3文件和JPG专辑封面文件。在你的专辑文件夹中存储专辑封面。专辑封面名应该为cover.jpg,所有其他的文件名将会被忽略。此示例代码用getID3()从MP3文件中读取ID3标签。在这里下载最新版本的getID3()并复制getid3文件夹到你的StreamingDemo文件夹中。你的文件夹结构应该是这样的:

StreamingDemo/getid3
StreamingDemo/Music/Album1
StreamingDemo/Music/Album1/Some nice music.mp3
StreamingDemo/Music/Album1/Some other nice music.mp3
StreamingDemo/Music/Album1/cover.jpg
StreamingDemo/Music/Album2/
StreamingDemo/Music/Album2/Another music file here too.mp3
...

从服务器取得专辑数据

PHP脚本(命名为getAlbums.php)将被Windows Phone应用调用。第一个应用程序,检查是否存在从应用程序传递来的“secred code”,如果没有,PHP脚本将返回空的JSON字符串。 (注:你可以在windows phone应用程序中更改这些secred 代码成你想的任何代码,在这里是PHP脚本代码。)在所有的音乐文件夹专辑目录被提取后。音乐文件按字母顺序排列并且所需的信息被成解析JSON字符串:专辑名称和目录,MP3文件名,艺术家,标题,专辑名称,专辑封面网址和歌曲时间。

<?php
// check request info
if ($_POST['request'] != "secretRequest") {
echo "{}";
return;
}
 
// include getID3() library
require_once('getid3/getid3.php');
 
// initialize getID3 engine
$getID3 = new getID3;
 
// get all album directories
$albumDirectories = array_filter(glob("Music/*"), "is_dir");
 
// start JSON string
echo '{"Albums":[';
$albums = 0;
 
// go through all the albums
foreach ($albumDirectories as &$DirectoryToScan) {
// scan directory files to get music in alphabetical order
$dir = scandir($DirectoryToScan);
// no songs yet, used to generate JSON data
$songs = 0;
// is there cover.jpg in this album directory
$filename = $DirectoryToScan."/cover.jpg";
$cover = "";
if (is_file($filename)) $cover = $filename;
// go through all files in directory (in album)
foreach ($dir as $file) {
// get full path to music file
$FullFileName = realpath($DirectoryToScan.'/'.$file);
// get file extension
$ext = substr($file, strrpos($file, '.') + 1);
if ($ext == "jpg") continue;
// if file is music file
if ((substr($FullFileName, 0, 1) != '.') && is_file($FullFileName) && ($ext == "mp3" || $ext == "MP3")) {
// if there are albums all ready listed to JSON, add comma
if ($albums > 0 && $songs == 0) echo ',';
// time limit to get id3 tags
set_time_limit(30);
// analyze file
$ThisFileInfo = $getID3->analyze($FullFileName);
getid3_lib::CopyTagsToComments($ThisFileInfo);
// set JSON data
if ($songs == 0) {
echo '{"albumname":"'.implode('',$ThisFileInfo['comments_html']['album']).'",';
echo '"albumdirectory":"'.$DirectoryToScan.'/",';
echo '"songs":[';
} else {
echo ",";
}
echo '{';
echo '"albumdirectory":"'.$DirectoryToScan.'/",';
echo '"filename":"'.utf8_encode(html_entity_decode($ThisFileInfo['filename'])).'",';
echo '"artist":"'.utf8_encode(html_entity_decode(implode('', $ThisFileInfo['comments_html']['artist']))).'",';
echo '"title":"'.utf8_encode(html_entity_decode(implode('',$ThisFileInfo['comments_html']['title']))).'",';
echo '"album":"'.utf8_encode(html_entity_decode(implode('',$ThisFileInfo['comments_html']['album']))).'",';
echo '"albumart":"'.utf8_encode(html_entity_decode($cover)).'",';
echo '"playtime":"'.$ThisFileInfo['playtime_string'].'"';
echo '}';
// one song added
$songs++;
}
}
// add JSON data, one album added
echo ']}';
$albums++;
}
// end JSON string (albums)
echo ']}';
?>

Windows Phone7.1SDK

在windows phone7设备上开发应用,你需要安装Windows Phone7.1SDK。你可以从这里下载最新的windows phone sdk

Windows Phone 应用程序

开始创建一个新的Windows Phone应用程序,要启动Microsoft Visual Studio然后创建一个新项目,并选择Windows Phone应用程序模板。全景项将用代码创建。

Create a new project

这个示例用c#作为后台代码语言

音频播放代理

此示例代码在Windows Phone上使用音频播放代理播放MP3音乐。你必须添加音频播放代理到你的解决方案。从你的项目里选择的解决方案并添加一个新项目到你的解决方案,并命名为AudioPlaybackAgent。这样添加了音频播放代理到了你的实例项目,这是由操作系统来处理这个应用程序请求。

Add Audio Playback Agent to the solution

你可以从AudioPlaybackAgent项目中打开AudioPlayer.cs类查看生成的处理音乐播放状态的代码。此代码示例中的代码后面将会被修改。阅读更多有关windows phone7的背景音频信息:Background Audio Overview for Windows Phone.

音频播放代理参考应当被添加到Windows Phone项目里。选择你的项目并添加一个新引用到你的AudioPlaybackAgent。

Add reference to Audio Playback Agent to your project

项目扩展

微软Visual Studio扩展可以以许多不同的方式安装。本文使用安装更容易的Nuget。查找并安装Silverlight工具包和简单的JSON扩展。有关这些扩展和安装指令,请看看Picasa Image Gallery with JSON in WP7文章。

只为你主项目安装Silverlight工具包和两个项目安装simple Json。

解决方案后台的类:Album和Song

专辑和歌曲数据将被保存在Album.cs和Song.cs类中。

Album.cs

专辑类保存专辑名字、艺术家和字符串值的目录数据。所有的专辑曲目保存在歌曲列表集里。 Song.cs 歌曲类保存基于字符串的歌曲数据值。

namespace NetMP3Player
{
public class Album
{
public string Name { get; set; }
public string Artist { get; set; }
public string Directory { get; set; }
public List<Song> Songs { get; set; }
}
}

在不同页面处理专辑数据

此示例代码有一些不同的页面:主全景页(不同的列表),歌曲页(显示一个专辑中的歌曲)和播放页。在Windows Phone上这些页面之间的数据共享的最简单的方法之一是,存储所有数据到App.xaml.cs类。当你不得不处理Windows Phone的墓碑模式,保存所有数据到同一个地方的是它的另一个优势。

App.xaml.cs 类

在App.xaml.cs类中所有专辑数据存储到不同的列表集。所有的专辑都存储到专辑列,所有最新歌曲存储到LatestSongs列和所有选定的喜爱的歌曲存储到FavoriteSongs列。当最新歌曲被选中时(最新的,最喜欢的或专辑),还有1个字符串变量ToPlayer用于描述该列表。

// List of Albums
public List<Album> Albums = new List<Album>();
// List of Latest Songs
public List<Song> LatestSongs = new List<Song>();
// List of Favorite Songs
public List<Song> FavoriteSongs = new List<Song>();
// To Player (from which Page and list : Main - Latest, Favorite, Album - to Player Page)
public string ToPlayer = "";

当程序启动时,应用程序从独立存储器加载所有的列表数据。在Windows Phone 上程序启动时Application_Launching方法将被执行

private void Application_Launching(object sender, LaunchingEventArgs e)
{
// connect to isolated storage
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
 
// Albums.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("AlbumsData.dat", FileMode.OpenOrCreate, isoStore))
{
if (stream.Length > 0)
{
try {
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Album>));
Albums = serializer.ReadObject(stream) as List<Album>;
} catch (SerializationException) {
MessageBox.Show("Error loading Albums data from Isolated Storage!");
}
}
}
// Latest.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("LatestData.dat", FileMode.OpenOrCreate, isoStore))
{
if (stream.Length > 0)
{
try {
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Song>));
LatestSongs = serializer.ReadObject(stream) as List<Song>;
} catch (SerializationException) {
MessageBox.Show("Error loading Latest data from Isolated Storage!");
}
 
}
}
// PlayList.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("FavoriteData.dat", FileMode.OpenOrCreate, isoStore))
{
if (stream.Length > 0)
{
try
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Song>));
FavoriteSongs = serializer.ReadObject(stream) as List<Song>;
} catch (SerializationException) {
MessageBox.Show("Error loading Favorite data from Isolated Storage!");
}
}
}
}

在应用程序中当列表数据被改变时程序将保存列表数据到独立存储器。这些保存方法在描述在App.xaml.cs类中。

保存最喜爱的到独立存储器(这里被应用程序页调用)

public void SaveFavoritesToIsolatedStorage()
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
// FavoriteList.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("FavoriteData.dat", FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Song>));
serializer.WriteObject(stream, FavoriteSongs);
}
}

保存专辑到独立存储器(这里被应用程序页调用)

public void SaveAlbumsToIsolatedStorage()
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
// Albums.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("AlbumsData.dat", FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Album>));
serializer.WriteObject(stream, Albums);
}
}

保存最新到独立存储器(这里被应用程序页调用)

public void SaveLatestToIsolatedStorage()
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
// Latest.dat
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("LatestData.dat", FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Song>));
serializer.WriteObject(stream, LatestSongs);
}
}

在App.xaml.cs类处理墓碑模式。因此,所有的列表与PhoneApplicationServices目前的状态对象被保存。用同样的方式处理加载当应用程序中墓碑模式被检测为激活或停用。

当应用程序被激活(进入到前台)Application_Activated方法将被执行。如果墓碑模式被检测到,这里所有的数据将从PhoneApplicationService状态加载。

private void Application_Activated(object sender, ActivatedEventArgs e)
{
if (e.IsApplicationInstancePreserved)
{
// DORMANT
//System.Diagnostics.Debug.WriteLine("EVENT: Application Activated");
}
else
{
// THOMBSTONED - mode, read Lists from State
// get Albums
if (PhoneApplicationService.Current.State.ContainsKey("Albums"))
{
Albums = (List<Album>)PhoneApplicationService.Current.State["Albums"];
PhoneApplicationService.Current.State.Remove("Albums");
}
// get LatestSongs
if (PhoneApplicationService.Current.State.ContainsKey("LatestSongs"))
{
LatestSongs = (List<Song>)PhoneApplicationService.Current.State["LatestSongs"];
PhoneApplicationService.Current.State.Remove("LatestSongs");
}
// get FavoriteSongs
if (PhoneApplicationService.Current.State.ContainsKey("FavoriteSongs"))
{
FavoriteSongs = (List<Song>)PhoneApplicationService.Current.State["FavoriteSongs"];
PhoneApplicationService.Current.State.Remove("FavoriteSongs");
}
// get ToPlayer
if (PhoneApplicationService.Current.State.ContainsKey("ToPlayer"))
{
ToPlayer = (string)PhoneApplicationService.Current.State["ToPlayer"];
PhoneApplicationService.Current.State.Remove("ToPlayer");
}
}
}

当应用程序被停用(进入到后台),同样的方式Application_Deactivated方法将被执行。这里所有的数据将保存到PhoneApplicationService状态。

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
// Store lists to States (and read in Application_Activated if Thobstoned)
if (!PhoneApplicationService.Current.State.ContainsKey("Albums")) PhoneApplicationService.Current.State.Add("Albums", Albums);
if (!PhoneApplicationService.Current.State.ContainsKey("LatestSongs")) PhoneApplicationService.Current.State.Add("LatestSongs", LatestSongs);
if (!PhoneApplicationService.Current.State.ContainsKey("FavoriteSongs")) PhoneApplicationService.Current.State.Add("FavoriteSongs", FavoriteSongs);
if (!PhoneApplicationService.Current.State.ContainsKey("ToPlayer")) PhoneApplicationService.Current.State.Add("ToPlayer", ToPlayer);
}

这些全在程序的类里

全景-应用程序的主页面

本应用程序使用全景作为主页面。在全景页用户可以从最新、喜爱和专辑列表中选择歌曲。所有的列表数据可以在全景页的设置处被移除。

设计(MainPage.xaml)

本全景页用3个不同的列表作为PanoramaItems。这些列表的数据绑定到专辑和歌曲类。

最新列表在LatestListBox以一行显示最新的歌曲标题和艺术家。最近播放的歌曲也可以通过用手指按住来添加到喜爱歌曲里。SilverLight 工具箱的contextMenu也可被添加进去,例如被添加到列表项。当列表项被选中并且播放页显示播放一首新歌时,LatestListBox_SelectionChanged方法将被调用。列表项里的TiltEffect也被启用,因此当被按下时它会向下移动一点。

Latest

<controls:Panorama Title="Net MP3 Player">
 
<controls:PanoramaItem Header="Latest">
<ListBox x:Name="LatestListBox"
SelectionChanged="LatestListBox_SelectionChanged"
toolkit:TiltEffect.IsTiltEnabled="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<StackPanel Grid.Column="0" Margin="5" Width="450">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="Add to PlayList" Click="AddToFavoritesMenuItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="{Binding Title}" FontSize="28" Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="{Binding Artist}" FontSize="24" FontStyle="Italic" Foreground="White"/>
</StackPanel>
</Grid>
<Rectangle Fill="DarkSlateGray" Height="1" HorizontalAlignment="Stretch" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>

记住添加引用到Microsoft.Phone.Controls中来使用全景和以下命名空间来使用全景控件和main.xaml工具箱。

xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"

喜爱列表在FavoritetListBox以一行显示喜爱歌曲标题和艺术家。和最近播放几乎是一样的,最新歌曲列表除了喜爱列表也可以通过用手指按住选择项来移除。这将调用RemoveFromMenuItem_Click来从列表中移除歌曲,当列表项被选中并且播放页显示播放一首新歌时,FavoriteListBox_SelectionChanged方法将被调用。

Favorites

<controls:PanoramaItem Header="Favorites">
<ListBox x:Name="FavoriteListBox"
SelectionChanged="FavoriteListBox_SelectionChanged"
toolkit:TiltEffect.IsTiltEnabled="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<StackPanel Grid.Column="0" Margin="5" Width="450">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="Remove from Favorites" Click="RemoveFromMenuItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="{Binding Title}" FontSize="28" Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="{Binding Artist}" FontSize="24" FontStyle="Italic" Foreground="White"/>
</StackPanel>
</Grid>
<Rectangle Fill="DarkSlateGray" Height="1" HorizontalAlignment="Stretch" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>

专辑列表在AlbumsListBox以一行显示专辑歌曲名字和艺术家。当列表项被选中和歌曲页被打开显示专辑的歌曲时,AlbumsListBox_SelectionChanged方法将被调用。

Albums

<controls:PanoramaItem Header="Albums">
<ListBox x:Name="AlbumsListBox"
SelectionChanged="AlbumsListBox_SelectionChanged"
toolkit:TiltEffect.IsTiltEnabled="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<StackPanel Grid.Column="0" Margin="5" Width="450">
<TextBlock Text="{Binding Name}" FontSize="28" Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="{Binding Artist}" FontSize="24" FontStyle="Italic" Foreground="White"/>
</StackPanel>
</Grid>
<Rectangle Fill="DarkSlateGray" Height="1" HorizontalAlignment="Stretch" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</controls:PanoramaItem>

最后在全景页PanoramaItems是设置。这一部分只显示一些文本信息和一些用于重置本应用程序数据列表的按钮

Settings

<controls:PanoramaItem Header="Settings">
<Grid>
<Button Content="Reset" Height="72" HorizontalAlignment="Left" Margin="190,56,0,0"
Name="ResetFavoriteListButton" VerticalAlignment="Top" Width="210"
Click="ResetFavoriteListButton_Click" />
<Button Content="Reset" Height="72" HorizontalAlignment="Left" Margin="190,179,0,0"
Name="ResetPlayListButton" VerticalAlignment="Top" Width="210"
Click="ResetPlayListButton_Click" />
<Button Content="Fetch!" Height="72" Margin="190,371,7,0"
Name="FetchAlbumsButton" VerticalAlignment="Top" Width="210"
Click="FetchAlbumsButton_Click" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="28,16,0,0" Name="textBlock1"
Text="Reset all the songs from the Latest List" VerticalAlignment="Top" Width="372" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="26,140,0,0" Name="textBlock2"
Text="Reset all the songs from the Play List" VerticalAlignment="Top" Width="352" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="25,262,0,0" Name="textBlock3"
Text="Get Albums from Server" VerticalAlignment="Top" />
<TextBlock Height="77" HorizontalAlignment="Left" Margin="59,301,0,0" x:Name="InfoTextBlock" FontSize="16"
Text="This also removes songs from the&#13;Latest and the Play Lists (if song is not&#13;found in new Albums)." VerticalAlignment="Top" Width="349" />
</Grid>
</controls:PanoramaItem>
 
</controls:Panorama>

编程(MainPage.xaml.cs)

在主页描述了一些变量。ServerrURL保存服务器的URL,你的StreamingDemo文件夹保存音乐和其他必须文件。App是保存数据列表的应用程序类的引用。专辑数据可以被重置和重新加载,albumsLoadAgain就是用于此目的。

private string ServerURL = "http://YOUR OWN SERVER HERE/StreamingDemo/";
private App app = App.Current as App;
private bool albumsLoadedAgain = false;

所有的数据加载发生在MainPage类中。如果专辑量是零,OnNavigatedTo方法,数据就会从PHP服务器中加载。主页也是本应用程序的开始点,所以如果音乐已经在播放,播放器页面就会加载显示。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
 
// Back Key is pressed - do nothing
if (e.NavigationMode == System.Windows.Navigation.NavigationMode.Back) return;
 
// If music is playing, navigate to PlayerPage
if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
{
this.NavigationService.Navigate(new Uri("/PlayerPage.xaml?main=true", UriKind.Relative));
return;
}
 
// If there are no albums, try load from Server
if (app.Albums.Count() == 0)
{
// load albums data from Server
LoadJSONData();
}
 
}

数据将以JSON字符串从PHP服务器中加载

private void LoadJSONData()
{
// albums data are now loading from net
WebClient webClient = new WebClient();
// server url and php file
Uri uri = new Uri(ServerURL + "getAlbums.php";
webClient.UploadStringCompleted += new UploadStringCompletedEventHandler(JSONDataDownloaded);
// send a secret requst string to php
webClient.UploadStringAsync(uri, "request=secretRequest");
}

当数据从PHP服务器返回JSONDataDownloaded方法将被调用。这里所有JSON数据被反序列化成动态对象,专辑数据保存在专辑和歌曲对象中。

private void JSONDataDownloaded(object sender, UploadStringCompletedEventArgs e)
{
if (e.Result == null || e.Error != null)
{
MessageBox.Show("Cannot get albums data from server!");
return;
}
// clear Albums List, new data is coming
app.Albums.Clear();
try
{
// Deserialize JSON string to dynamic object
IDictionary<string, object> json = (IDictionary<string, object>)SimpleJson.DeserializeObject(e.Result);
// Albums List
IList albumsData = (IList)json["Albums"];
// Find album details
for (int i = 0; i < albumsData.Count; i++)
{
// Create a new Album
Album album = new Album();
album.Songs = new List<Song>();
// Album object
IDictionary<string, object> albumData = (IDictionary<string, object>)albumsData[i];
// Get albumname
album.Name = (string)albumData["albumname"];
// album artist (artist or Various Artists)
album.Artist = "";
// Get album directory
album.Directory = (string)albumData["albumdirectory"];
// Get songs
IList songsData = (IList)albumData["songs"];
// Get song data from JSON
for (int k = 0; k < songsData.Count; k++)
{
// Create a new Song
Song song = new Song();
// Get Song object
IDictionary<string, object> songData = (IDictionary<string, object>)songsData[k];
// Get directory
song.Filename = (string)songData["filename"];
// Get filename
song.Directory = (string)songData["albumdirectory"];
// Set cover
song.AlbumArt = (string)songData["albumart"];
// Get artists
song.Artist = (string)songData["artist"];
if (k == 0) album.Artist = (string)songData["artist"];
else if (album.Artist != song.Artist) album.Artist = "Various Artists";
// Get title
song.Title = (string)songData["title"];
song.Title = HttpUtility.HtmlEncode(song.Title);
// Get album
song.Album = (string)songData["album"];
// Get playtime
song.Playtime = (string)songData["playtime"];
// Add song to album
album.Songs.Add(song);
}
// Add album to Albums (in app)
app.Albums.Add(album);
}
// save albums data to Isolated Storage
app.SaveAlbumsToIsolatedStorage();
}
catch (SerializationException)
{
MessageBox.Show("Cannot load Albums data from Server - JSON serialization exception happened!");
}
catch (WebException)
{
MessageBox.Show("Cannot get albums data from server!");
}
catch (KeyNotFoundException)
{
MessageBox.Show("Cannot load Albums data from Server - JSON parsing error happened!");
}
// sort albums by Album.Artist
app.Albums.Sort(SortByAlphabet);
// show albums data in list
ShowDataInAlbumList();
// albums are fetched again, check Latest and PlayList (remove songs that arent in albums anymore)
if (albumsLoadedAgain) CheckLists();
}

专辑数据保存在独立存储器中并以专辑艺术家的名字排序。排序以自己的排序方法完成:

// Sort albums by album name
private int SortByAlphabet(Album a1, Album a2)
{
return a1.Artist.CompareTo(a2.Artist);
}

如果一个新的相册从服务器中取出,然后CheckLists()方法将被调用。如果有最新和最喜欢的的歌曲不是在新专辑数据中,那么这些歌曲将被从列表中删除。从源代码了解更多这方面的信息。这是基本的工作比较在几个不同的列表中的对象。

专辑数据将用下面的方法绑定到用户界面。在本应用程序同样的方法用于最新和喜爱列表。第一个列表的ItemSource将被发送至空,然后绑定一个新的数据到列表,最后在列表中没有项被选中。

private void ShowDataInAlbumList()
{
AlbumsListBox.ItemsSource = null;
AlbumsListBox.ItemsSource = app.Albums;
AlbumsListBox.SelectedIndex = -1;
}

用户可以从专辑列表中选择一个专辑显示专辑的曲目。当一个新专辑从AlbumsListBox选中,一个新歌曲页将显示。选中的专辑的序号将发送到歌曲页面。

private void AlbumsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (AlbumsListBox.SelectedIndex == -1) return;
int selectedIndex = (sender as ListBox).SelectedIndex;
this.NavigationService.Navigate(new Uri("/SongsPage.xaml?selectedIndex=" + selectedIndex, UriKind.Relative));
}

当用户从最新或喜爱列表选中一首新歌,播放器页将显示。选中的歌曲序号将发送到播放器页面。

private void LatestListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (LatestListBox.SelectedIndex == -1) return;
int selectedIndex = (sender as ListBox).SelectedIndex;
this.NavigationService.Navigate(new Uri("/PlayerPage.xaml?selectedIndex=" + selectedIndex + "&latest=true", UriKind.Relative));
}
 
private void FavoriteListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (FavoriteListBox.SelectedIndex == -1) return;
int selectedIndex = (sender as ListBox).SelectedIndex;
this.NavigationService.Navigate(new Uri("/PlayerPage.xaml?selectedIndex=" + selectedIndex + "&favorite=true", UriKind.Relative));
}

在主页用户可以从喜爱列表中移除歌曲。当用户在列表项中手指按下,Contextmenu将弹出。这是从FavoriteListBox中找到歌曲序号的小技巧。首先选中项被取到,然后它在FavoriteListBox中的位置,最后歌曲将从喜爱列表中移除。一个新数据将显示在用户界面中,修改过多的列表将保存到独立存储器中。 Remove song from Favorites

private void RemoveFromMenuItem_Click(object sender, RoutedEventArgs e)
{
// Get selected ListBoxItem
ListBoxItem selectedListBoxItem = FavoriteListBox.ItemContainerGenerator.ContainerFromItem((sender as MenuItem).DataContext) as ListBoxItem;
 
// Get selected ListBoxItem index in FavoriteListBox
ListBox view = ItemsControl.ItemsControlFromItemContainer(selectedListBoxItem) as ListBox;
int index = view.ItemContainerGenerator.IndexFromContainer(selectedListBoxItem);
 
// remove song from Favorite songs
app.FavoriteSongs.RemoveAt(index);
 
// show data in Favorite List
ShowDataInFavoriteList();
 
// save Favorite data to Isolated Storage
app.SaveFavoritesToIsolatedStorage();
}

在主页用户同样也可以从最新列表中添加歌曲到喜爱列表。添加的歌曲序号将以以上移除方法一样取得。一个新数据将显示在用户界面中并保存到独立存储器中。

Add song to Favorites

private void AddToFavoritesMenuItem_Click(object sender, RoutedEventArgs e)
{
// get selected ListBoxItem
ListBoxItem selectedListBoxItem = LatestListBox.ItemContainerGenerator.ContainerFromItem((sender as MenuItem).DataContext) as ListBoxItem;
 
// get selected ListBoxItem index in LatestListBox
ListBox view = ItemsControl.ItemsControlFromItemContainer(selectedListBoxItem) as ListBox;
int index = view.ItemContainerGenerator.IndexFromContainer(selectedListBoxItem);
 
// add song to Favorites Songs
app.FavoriteSongs.Add(app.LatestSongs[index]);
 
// save Favorites List data to Isolated Storage
app.SaveFavoritesToIsolatedStorage();
}

专辑数据在全景页设置处被重置。当FetchAlbumsButton被点击,一个新专辑数据被获取。这将置空专辑列表,数据从PHP服务器中重载。

// Get new albums data from Server
private void FetchAlbumsButton_Click(object sender, RoutedEventArgs e)
{
// remove albums data
ResetAlbumsList();
// albums are loding again
albumsLoadedAgain = true;
InfoTextBlock.Text = "Loading data from server..";
// start loading data from Server
LoadJSONData();
}

从设置中置空最新和喜爱列表这也是可能的 。置空数据将显示和保存到独立存储器中。

private void ResetLatestListButton_Click(object sender, RoutedEventArgs e)
{
// Remove all
app.LatestSongs.Clear();
// Show data in Latest List
ShowDataInLatestList();
// Save Latest data to Isolated Storage
app.SaveLatestToIsolatedStorage();
}
 
private void ResetPlayListButton_Click(object sender, RoutedEventArgs e)
{
// Remove all
app.FavoriteSongs.Clear();
// Show data in Favorite List
ShowDataInFavoriteList();
// Save Favorites data to Isolated Storage
app.SaveFavoritesToIsolatedStorage();
}

歌曲页面

本页显示列表中选中的专辑歌曲。

设计(SongsPage.xaml)

本页标题显示选中歌曲专辑名字,在一个列表中以列表项显示歌曲数据的标题和艺术家。选中的歌曲能被添加到喜爱歌曲中(如前面在主页中叙述的同样方法),当从选择是从SongList选得(一首新歌播放),SongListBox_SelectionChanged方法将被调用。

Album songs

<ListBox x:Name="SongsListBox" 
SelectionChanged="songsListBox_SelectionChanged"
toolkit:TiltEffect.IsTiltEnabled="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<StackPanel Grid.Column="0" Margin="5" Width="450">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="Add to Favorites" Click="AddToFavoritesMenuItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="{Binding Title}" FontSize="28" Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="{Binding Artist}" FontSize="24" FontStyle="Italic" Foreground="White"/>
</StackPanel>
</Grid>
<Rectangle Fill="DarkSlateGray" Height="1" HorizontalAlignment="Stretch" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

编码(SongsPage.xaml)

歌曲页面很容易编程。它只有几项工作要做:从应用程序类加载选中的专辑数据并显示到用户界面,打开一首新歌(导航到播放器页面)或添加一首歌曲到喜爱列表

当本页面显示时OnNavigatedTo方法将被调用。第一个选中的专辑序号是从前一个页面读取来的并显示专辑数据在SongListBox中显示。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
 
// nothing is now selected in SongsListBox
SongsListBox.SelectedIndex = -1;
 
// read data from previous Page (selected song index in album data)
IDictionary<string, string> parameters = this.NavigationContext.QueryString;
if (parameters.ContainsKey("selectedIndex"))
{
// album index in albums
selectedAlbumIndex = Int32.Parse(parameters["selectedIndex"]);
// set application and page titles
ApplicationTitle.Text = app.Albums[selectedAlbumIndex].Name;
PageTitle.Text = app.Albums[selectedAlbumIndex].Artist;
// display songs in list
SongsListBox.ItemsSource = app.Albums[selectedAlbumIndex].Songs;
}
}

当一首新歌从歌曲列表中选中,播放器页面将被调用。选中的专辑和专辑歌曲序号被发送到播放页。

private void songsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// get selected song index
if (SongsListBox.SelectedIndex == -1) return;
int selectedIndex = (sender as ListBox).SelectedIndex;
// navigate to Player Page
this.NavigationService.Navigate(new Uri("/PlayerPage.xaml?selectedAlbumIndex=" + selectedAlbumIndex + "&selectedSongIndex=" + selectedIndex, UriKind.Relative));
}

用户可以以主页中一样方法添加歌曲到喜爱。唯一的不同是要处理SongsListBox和从专辑数据中选择歌曲。

private void AddToFavoritesMenuItem_Click(object sender, RoutedEventArgs e)
{
// get selected ListBoxItem
ListBoxItem selectedListBoxItem = SongsListBox.ItemContainerGenerator.ContainerFromItem((sender as MenuItem).DataContext) as ListBoxItem;
 
// get selected ListBoxItem index in SongsListBox
ListBox view = ItemsControl.ItemsControlFromItemContainer(selectedListBoxItem) as ListBox;
int index = view.ItemContainerGenerator.IndexFromContainer(selectedListBoxItem);
 
// add song to Favorites Songs
app.FavoriteSongs.Add(app.Albums[selectedAlbumIndex].Songs[index]);
// save Favorites List data to Isolated Storage
app.SaveFavoritesToIsolatedStorage();
}

播放器页面

这是在示例代码中描述的最后一个页面。用户可以播放和暂停播放的歌曲,跳到下一首或前一首。如果php服务器可用,本页将显示专辑封面以及播放的歌曲的标题,艺术家(在存放专辑音乐的文件中它必须命名为cover.jpg)

设计(PlayerPage.xaml)

本页由不同的控件基本设计的。网格包含:Button,TextBlocks,Slider和Image controls.应用程序的标题和页面的名字绑定到选中的歌曲数据。当点击按钮,播放,下一曲,上一曲功能将被调用,当用户移动滑块,SoundSlider_ManipulationCompleted 将被调用。

Player Page

<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="NetMP3Player" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
 
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Button Content="Play" Height="72" HorizontalAlignment="Left" Margin="148,428,0,0" x:Name="PlayButton" VerticalAlignment="Top" Width="160" Click="PlayButton_Click" />
<Button Content="Next" Height="72" HorizontalAlignment="Left" Margin="290,428,0,0" x:Name="NextButton" VerticalAlignment="Top" Width="160" Click="NextButton_Click" />
<Button Content="Prev" Height="72" HorizontalAlignment="Left" Margin="0,428,0,0" x:Name="PrevButton" VerticalAlignment="Top" Width="160" Click="PrevButton_Click" />
<TextBlock Height="51" HorizontalAlignment="Left" Margin="27,334,0,0" x:Name="TitleTextBlock" Text="Title:" VerticalAlignment="Top" FontSize="28" Foreground="{StaticResource PhoneAccentBrush}" Width="400" />
<TextBlock Height="39" HorizontalAlignment="Left" Margin="27,383,0,0" x:Name="ArtistTextBlock" Text="Artist:" VerticalAlignment="Top" FontSize="24" FontStyle="Italic" Foreground="White" Width="400" />
<Slider Height="84" HorizontalAlignment="Left" Margin="27,507,0,0" x:Name="SongSlider" VerticalAlignment="Top" Width="400" ManipulationCompleted="SoundSlider_ManipulationCompleted" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="37,559,0,0" x:Name="StartTextBlock" Text="00:00:00" VerticalAlignment="Top" Width="100" />
<TextBlock Height="30" HorizontalAlignment="Left" TextAlignment="Right" Margin="327,559,0,0" x:Name="EndTextBlock" Text="00:00:00" VerticalAlignment="Top" Width="88" />
<Image Height="300" HorizontalAlignment="Left" Margin="75,8,0,0" x:Name="AlbumArtImage" Stretch="Fill" VerticalAlignment="Top" Width="300" />
</Grid>

编码(PlayerPage.xaml)

在播放器页面有很多编程工作,一些变量用于取得程序类引用,当歌曲在播放,处理声音,添加最新歌曲、更新滑块。

private App app = App.Current as App;
private const int LATESTS_MAX = 20;
// check Songs playing time -> update TextBlocks and Slider
private DispatcherTimer dispatcherTimer = new DispatcherTimer();

AudioPlayback代理在构造器控制处理Playstate。所以每次当一个新的东西在AudioPlayback代理中发生,它就会用Instance_PlayStateChanged方法 通知在播放器页面的用户界面

public PlayerPage()
{
// Initialize
InitializeComponent();
// handle PlayState from AudioPlayback Agent
BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(Instance_PlayStateChanged);
}

当本页显示时OnNavigatedTo方法将被调用。这些将在这里分块描述。

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
...

首先检查播放状态、处理暂停和播放。如果返回键被按下(因此应用程序再一次激活),重启定时器,移动滑块,并显示专辑数据

// Playing - set Pause Button text
if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Playing)
{
PlayButton.Content = "Pause";
}
// Paused - set Play Button text
else if (BackgroundAudioPlayer.Instance.PlayerState == PlayState.Paused)
{
PlayButton.Content = "Play";
}
 
// Back Key is pressed - don't load songs - only update UI
if (e.NavigationMode == System.Windows.Navigation.NavigationMode.Back)
{
// start timer to check position of the song
startTimer();
// set song info
TitleTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Title;
ArtistTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Artist;
// set Page title
PageTitle.Text = app.ToPlayer;
// load album art
LoadAlbumArt();
return;
}

如果本页从主页导航过来的并且特别是从专辑列表导航过来的,然后开始播放一首新歌曲。首先从之前的页检测到选中专辑和歌曲序号并添加歌曲到最新歌曲。GeneratePlayListAndPlay 方法将被调用以生成歌曲列表到音频播放代理。本方法将在本文后面描述。

// We are coming from Main Page (Album -> Songs List) - start playing music (always)
IDictionary<string, string> parameters = this.NavigationContext.QueryString;
if (parameters.ContainsKey("selectedAlbumIndex") && parameters.ContainsKey("selectedAlbumIndex"))
{
// album index in albums
int selectedAlbumIndex = Int32.Parse(parameters["selectedAlbumIndex"]);
// album music index
int selectedSongIndex = Int32.Parse(parameters["selectedSongIndex"]);
// add Song to Latest Songs
AddToLatestSongs(app.Albums[selectedAlbumIndex].Songs[selectedSongIndex]);
// generate play list
GeneratePlayListAndPlay(selectedSongIndex, app.Albums[selectedAlbumIndex].Songs);
// where we got here
PageTitle.Text = app.ToPlayer = "Album songs";
}

如果本页是从主页导航过来的并且特别是从最新歌曲导航过来的,如果相同的歌曲已经在播放就不要再打开音乐了。如果没有音乐在播放就从最新歌曲生成一个播放列表。

// We are coming from MainPage and (Latest Songs)
if (parameters.ContainsKey("selectedIndex") && parameters.ContainsKey("latest"))
{
// get select Song title and artist from LatestSongs
int selectedIndex = Int32.Parse(parameters["selectedIndex"]);
var title = app.LatestSongs[selectedIndex].Title;
var artist = app.LatestSongs[selectedIndex].Artist;
 
// are we playing this song already and started from Latest Songs List (Main Page)
if (app.ToPlayer == "LatestSongs" &&
title == BackgroundAudioPlayer.Instance.Track.Title &&
artist == BackgroundAudioPlayer.Instance.Track.Artist)
{
// do nothing - this song is already playing
System.Diagnostics.Debug.WriteLine("This song is already playing!");
}
else
{
// generate play list
GeneratePlayListAndPlay(selectedIndex, app.LatestSongs);
}
// where we got here
PageTitle.Text = app.ToPlayer = "Latest songs";
}

如果本页是从主页并且特别是从喜爱歌曲导航过来的,那就用喜爱歌曲生成播放列表

// We are coming from Main Page (Favorite Songs)
if (parameters.ContainsKey("selectedIndex") && parameters.ContainsKey("favorite"))
{
// get select Song title and artist from Favorite Songs
int selectedIndex = Int32.Parse(parameters["selectedIndex"]);
var title = app.FavoriteSongs[selectedIndex].Title;
var artist = app.FavoriteSongs[selectedIndex].Artist;
 
// add Song to Latest Songs
AddToLatestSongs(app.FavoriteSongs[selectedIndex]);
// generate play list
GeneratePlayListAndPlay(selectedIndex, app.FavoriteSongs);
// where we got here
PageTitle.Text = app.ToPlayer = "Favorite songs";
}

最后,如果本页是从主页并且特别是没从其他任何列表导航过来的(然后程序再启动一次),就仅仅在用户界面中显示播放专辑数据。StartTimer方法将启动更新滑块。这个将在后文介绍。

// We are coming from main (song is playing)
if (parameters.ContainsKey("main"))
{
// display album art
LoadAlbumArt();
// show soung info
TitleTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Title;
ArtistTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Artist;
}
// start timer to check position of the song
startTimer();

OnNavigatedTo()方法结尾

}

当用户从播放器页面离开,OnNavigatedTo方法将被调用。这里停止计时器 检查滑块的位置并移除处理播放状态更新的事件。

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
dispatcherTimer.Stop();
dispatcherTimer.Tick -= new EventHandler(dispatcherTimer_Tick);
BackgroundAudioPlayer.Instance.PlayStateChanged -= new EventHandler(Instance_PlayStateChanged);
}

上面OnNavigatedTo方法调用AddToLatestSongs来添加开始的歌曲到最新歌曲列表。在本示例代码中,最新歌曲列表限制在20首以内,首先,新歌将和已经在最新列表中歌曲进行比较,如果存在这个歌曲,它将被从列表移除。最后一首新歌被添加到最新歌曲列表的顶上并且最新歌曲列表将被保存到独立存储器中。

private void AddToLatestSongs(Song song)
{
// add selected song to LatestSongs
int index = 0;
// remove if already in Latest and set it to first
foreach (Song latestSong in app.LatestSongs) {
if (song.Artist == latestSong.Artist && song.Title == latestSong.Title)
{
app.LatestSongs.RemoveAt(index);
break;
}
index++;
}
app.LatestSongs.Insert(0, song);
if (app.LatestSongs.Count() > LATESTS_MAX) app.LatestSongs.RemoveAt(app.LatestSongs.Count() - 1);
// Save Latest data to Isolated Storage
app.SaveLatestToIsolatedStorage();
}

上面OnNavigatedTo方法也会调用GeneratePlayListAndPlay方法来生成被发送在音频播放代理播放的播放列表。在这个示例中歌曲数据序列化成JSON对象并被保存到独立存储器中。同一个对象将在音频播放代理中加载和反序列化,最后将为音频播放代理生成音频音轨。这是我们自己的用户界面和音频播放代理进行通信的一种方法。

音频播
private void GeneratePlayListAndPlay(int selectedSongIndex, List<Song> list)
{
// JSON string - start playing index and songs data
string jsonString = "{\"index\":" + selectedSongIndex + ",\"Songs\":" + SimpleJson.SerializeObject(list) + "}";
 
// store list to isolated storage
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
isoStore.DeleteFile("playlist.dat");
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("playlist.dat", FileMode.Create, isoStore))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(string));
serializer.WriteObject(stream, jsonString);
}
// Stop playing if there are song playing
if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState ||
PlayState.Paused == BackgroundAudioPlayer.Instance.PlayerState) BackgroundAudioPlayer.Instance.Stop();
// start playing a song (Call Play-method from AudioPlayback Agent)
BackgroundAudioPlayer.Instance.Play();
PlayButton.Content = "Pause";
}

播放代理通过Instance_PlayStateChanged 方法来通知播放器页面。如果有歌曲在播放,就在屏幕上更新数据(文本,专辑封面和滑块)

void Instance_PlayStateChanged(object sender, EventArgs e)
{
// if something is playing (a new song)
if (BackgroundAudioPlayer.Instance.Track != null)
{
// show soung info
TitleTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Title;
ArtistTextBlock.Text = BackgroundAudioPlayer.Instance.Track.Artist;
// handle slider and texts
SongSlider.Minimum = 0;
SongSlider.Maximum = BackgroundAudioPlayer.Instance.Track.Duration.TotalMilliseconds;
string text = BackgroundAudioPlayer.Instance.Track.Duration.ToString();
EndTextBlock.Text = text.Substring(0, 8);
// album art
LoadAlbumArt();
}
}

专辑封面加载到BitmapImage并设置到AlbumArtImage源。专辑封面URL存储到播放音轨,如果是空就用默认专辑封面(存储在项目内容中)

private void LoadAlbumArt()
{
// get album art Uri from Audio Playback Agent
Uri albumArtURL = BackgroundAudioPlayer.Instance.Track.AlbumArt;
// load album art from net
if (albumArtURL != null && latestAlbumArtPath != albumArtURL.AbsolutePath)
{
latestAlbumArtPath = albumArtURL.AbsolutePath;
AlbumArtImage.Source = new BitmapImage(albumArtURL);
}
// there is no album art in net, load album art from project resources
else if (albumArtURL == null)
{
Uri uri = new Uri("/Images/NoCoverAvailable.jpg", UriKind.Relative);
BitmapImage bitmapImage = new BitmapImage(uri);
AlbumArtImage.Source = bitmapImage;
}
else
{
System.Diagnostics.Debug.WriteLine("Don't load same album art again");
}
}

当音乐在播放,滑块由DispatcherTimer 类来控制。StartTimer 方法初始化间隔时间和计时器开始

private void startTimer()
{
// start timer to check position of the song
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 500);
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Start();
}

dispatcherTimer_Tick 方法每半秒被调用一次,当歌曲在播放,滑块值和歌曲位置文本将被更新。

private void dispatcherTimer_Tick(object sender, EventArgs e)
{
// song is playing
if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState)
{
// handle slider
SongSlider.Minimum = 0;
SongSlider.Value = BackgroundAudioPlayer.Instance.Position.TotalMilliseconds;
SongSlider.Maximum = BackgroundAudioPlayer.Instance.Track.Duration.TotalMilliseconds;
// display text
string text = BackgroundAudioPlayer.Instance.Position.ToString();
StartTextBlock.Text = text.Substring(0,8);
text = BackgroundAudioPlayer.Instance.Track.Duration.ToString();
EndTextBlock.Text = text.Substring(0, 8);
}
}

当用户在用户界面拖拽滑块是,SoundSlider_ManipulationCompleted 方法将被调用,滑块值将首先渲染,一个新的位置的BackgroundAudioPlayer 实例被设置

private void SoundSlider_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
// get slider value
int sliderValue = (int)SongSlider.Value;
// create timespan object with milliseconds (from slider value)
TimeSpan timeSpan = new TimeSpan(0, 0, 0, 0, sliderValue);
// set a new position of the song
BackgroundAudioPlayer.Instance.Position = timeSpan;
}

本播放器页面有一些按钮控件:前一曲,播放/暂停和下一曲 这些将用方法调用到后台音频播放器

private void PrevButton_Click(object sender, RoutedEventArgs e)
{
// skip to previous song
BackgroundAudioPlayer.Instance.SkipPrevious();
// handle text and slider
PlayButton.Content = "Pause";
SongSlider.Value = 0;
}
 
private void NextButton_Click(object sender, RoutedEventArgs e)
{
// skip to next song
BackgroundAudioPlayer.Instance.SkipNext();
// handle text and slider
PlayButton.Content = "Pause";
SongSlider.Value = 0;
}
 
private void PlayButton_Click(object sender, RoutedEventArgs e)
{
// get Play Button state (Play or Pause)
string state = (string)PlayButton.Content;
// if pause, then pause music
if (state == "Pause")
{
BackgroundAudioPlayer.Instance.Pause();
PlayButton.Content = "Play";
}
// if play, then play music
else
{
BackgroundAudioPlayer.Instance.Play();
PlayButton.Content = "Pause";
}
}

音频播放代理

当在解决方案里创建一个音频播放代理项目时,它创建一个音频播放类。该类用于在音频播放代理中播放音乐。在本示例中,歌曲数据通过JSON字符串尤其是歌曲对象数据从用户界面发送到音频播放代理中。

修改音频播放类,包含一下变量来存储播放列表、当前播放序号、加载歌曲和专辑封面的服务器URL路径。

private static List<AudioTrack> playList = new List<AudioTrack>();
private static int currentSongIndex = 0;
private static string ServerURL = "http://YOUR OWN SERVER HERE/StreamingDemo/"

当用户请求使用程序用户界面,音频播放代理 OnUserAction方法(在AudioPlayer.cs类中)将被调用。如果播放器在暂停模式,修改UserAction.play 来开始播放歌曲,如果播放器不是播放模式,就从独立存储器中加载播放列表

case UserAction.Play:
if (player.PlayerState == PlayState.Paused)
{
// start playing again
player.Play();
}
else if (player.PlayerState != PlayState.Playing)
{
// load play list from isolated storage and start playing
LoadPlayListFromIsolatedStorage(player);
}
break;

在LoadPlayListFromIsolatedStorage 清理第一个前播放列表时,一个新的播放列表从保存在独立存储器的JSON数据中生成。数据包含那里开始播放歌曲的一个歌曲和序号的列表。

private void LoadPlayListFromIsolatedStorage(BackgroundAudioPlayer player)
{
// clear previous playlist
playList.Clear();
 
// access to isolated storage
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("playlist.dat", FileMode.OpenOrCreate, isoStore))
{
if (stream.Length > 0)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(string));
string jsonString = serializer.ReadObject(stream) as string;
 
// Deserialize JSON string to dynamic object
IDictionary<string, object> json = (IDictionary<string, object>)SimpleJson.DeserializeObject(jsonString);
// start Index
int index = Int32.Parse(json["index"].ToString());
currentSongIndex = index;
 
// Songs List
IList songsData = (IList)json["Songs"];
// Find songs details
for (int i = 0; i < songsData.Count; i++)
{
// Get Song object
IDictionary<string, object> songData = (IDictionary<string, object>)songsData[i];
// Get directory
string directory = (string)songData["Directory"];
// Get filename
string filename = (string)songData["Filename"];
// Get artists
string artist = (string)songData["Artist"];
// Get title
string title = (string)songData["Title"];
// Get album
string album = (string)songData["Album"];
// Get album art
string albumart = (string)songData["AlbumArt"];
Uri albumArtURL = null;
if (albumart != "") albumArtURL = new Uri(ServerURL+albumart,UriKind.Absolute);
// Get playtime
string playtime = (string)songData["Playtime"];
// Create a new AudioTrack object
AudioTrack audioTrack = new AudioTrack(
new Uri(ServerURL + directory + filename, UriKind.Absolute), // URL
title, // MP3 Music Title
artist, // MP3 Music Artist
album, // MP3 Music Album name
albumArtURL // MP3 Music Artwork URL
);
// add song to PlayList
playList.Add(audioTrack);
}
}
else
{
// no AudioTracks from Isolated Storage
}
// start playing
player.Play();
}
}

音频播放器类有从用户界面调用(在本例中 从播放器页面调用)GetNextTrack 和GetPreviousTrack 方法,修改这些方法来控制当前在音频播后台播放器播放歌曲。在本例中,如果下一首被选中并且最后一首歌曲已经播放就播放第一首歌曲(相反同理)

private AudioTrack GetNextTrack()
{
AudioTrack track = null;
currentSongIndex++;
if (currentSongIndex > playList.Count-1) currentSongIndex = 0;
track = playList[currentSongIndex];
return track;
}
private AudioTrack GetPreviousTrack()
{
AudioTrack track = null;
currentSongIndex--;
if (currentSongIndex < 0) currentSongIndex = playList.Count - 1;
track = playList[currentSongIndex];
return track;
}

总结

本示例代码演示如何从服务器传输音频到Windows phone 应用程序并且用音频播放代理进行播放。希望本文对你在wp7上传输音频有帮助

你可以从这里下载源代码 File:PTMNetMP3Player.zip

This page was last modified on 19 July 2013, at 06:52.
137 page views in the last 30 days.