Windows Phone 8–操作Microsoft.Xna.Framework.Media
最近比較有在撰寫處理設備裡圖檔與音樂檔案的功能,除了控制自己App中的檔案,有時候也需要存取共用的區域。
剛好看到<Data for Windows Phone>的一個重點,如下圖:
得知MediaLibrary API在Windows Phone 8支援了很多功能,因此,該篇將針對Microsoft.Xna.Framework.Media中重的類別加以說明。
〉Microsoft.Xna.Framework.Media:
該命名空間定義了許多相關於play、view songs、albums、playlist與pictures或是enumerate等。簡單列出如下:
‧類別:
名稱 | 說明 |
Album | 提供media library中一個相關的album。 |
AlbumCollection | 提供meida library中album的集合。 |
Artist | 提供media library指定artist的information。 |
ArtistCollection | 提供media library中所有artists的information。 |
Genre | 提供media library中的genre information(曲風資訊)。 |
GenreCollection | 提供media library中的所有genres information。 |
MediaLibrary | 提供連結media library中的songs、playlists與pictures。 |
MeidaPlayer | 提供方法與屬性去控制play、pause、resume與stop songs。MediaPlayer也支持隨機(shuffle),重複(repeat),音量(volume),播放位置(play position),和可視化功能(visualization)。 |
MediaQueue | 提供方法與屬性連結與控件播放音樂中的queue。 |
MediaSource | 提供方法與屬性取得media將讀取的source或sources資訊。 |
Picture | 提供連結在media library中的圖像。 |
PictureAlbum | 提供連結在media library中的圖像Album。 |
PictureAlbumCollection | 提供連結在media library中的圖像Album集合。 |
PictureCollection | 提供連結在media library中的圖像集合。 |
Playlist | 提供連結在media library中的playlist。 |
PlaylistCollection | 提供連結在media library中的playlist集合。 |
Song | 提供連結在song library中的song。 |
SongCollection | 提供連結在song library中的song集合。 |
Video | 代表一個Video。 |
VideoPlayer | 提供方法與屬性去控制play、pause、resume與stop videos。VideoPlayer支持重複(repeat),音量(volume),播放位置(play position)。 |
VisualizationData | Encapsulates visualization (frequency and sample) data for the currently-playing song. |
‧列舉:
名稱 | 類別 |
MediaSourceType | Type of the media source. |
MediaState | Media playback state (playing, paused, or stopped). |
VideoSoundtrackType | Type of sounds in a video |
以上是擷錄MSDN說明Microsoft.Xna.Framework.Media中重要的類別,其中有關MediaPlayer與相關Songs的操作,如果您有閱讀過
處理Background Audio PlayList的文件相信不陌生,可參考<Windows Phone 7 – Background Audio Playlist Application>,因為裡面用
到了Song、Playlist與MediaPlayer的類別搭配播放音樂。
大致上了解有多少個類別後,往下針對幾個比較特別類別加以說明:
提供連結media library中的songs、playlists與pictures。也代表透過該類別可以取得device中所有多媒體資訊,該類別每一個屬性
回傳的為media collection,所以可以針對所需類型加入至應用程式中畫面的控件項目做Binding。
但需要特別注意,media collection無法直接檢索或實例化來用,需要將指定的項目從collection找出單一物件,例如:song或artist物件。
其用法非常容易,以下便舉例處理pictures、songs的範例:
(1) 取得所有media library中的圖像,並且顯示於List中;
1-1. 在xaml中加一個ListBox,設定ListBox.ItemTemplate顯示預覽的圖像;
<phone:PhoneApplicationPage.Resources>
<MyApp:PreviewPictureConverter x:Key="PreviewPictureConverter" />
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="lstPictures" Width="430" Height="710">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- 使用自行開發的Convert來Binding Source -->
<Image Source="{Binding Converter={StaticResource PreviewPictureConverter}}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
上述的xaml寫法我相信大家不陌生,另外,也可以參考<Windows phone: listbox with images out-of-memory>的說明;
1-2. 由於透過MediaLibrary取得PictureCollection,每一個Picture並無法直接套入Image控件,所以加個Convert;
public class PreviewPictureConverter:System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Picture tValue = value as Picture;
if (tValue == null) return null;
BitmapImage tBitmap = new BitmapImage();
// 利用GetThumbnail()用縮圖,降低發生Out of memory的問題。
tBitmap.SetSource(tValue.GetThumbnail());
return tBitmap;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
在此處主要是使用GetThumbnail()的方式,利用縮圖顯示於ListBox中,降低記憶體爆掉的問題。使用GetImage()的話,
在滑動ListBox時就會出現out of memory的錯誤了。
可改用<PhotoHub - Windows Phone 8 XAML LongListSelector Grid Layout sample>的方法避免;
1-3. 在畫面載入時,取得所有圖像並且加入至ListBox中;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// <Capability Name="ID_CAP_MEDIALIB_PHOTO" />
// 這個要記得加上去。
using (MediaLibrary tLib = new MediaLibrary())
{
PictureCollection tPicSets = tLib.Pictures;
if (tPicSets.Count == 0)
MessageBox.Show("no pictures in the device!");
else
lstPictures.ItemsSource = tPicSets;
}
}
如果上述功能取不到圖像,主要因為capability未加入,記得加入ID_CAP_MEDIALIB_PHOTO。
[補充]
如果你遇到out of memory的話,建議可以將要捉取的圖像分類載入,不要一次把所有設備中的圖像全部加入至畫面,
可改成下列的程式段:
(A) 定義多個PivotItem分別放置不同的圖像來源,並且註冊Loaded事件,如下:
<phone:Pivot Title="MY APPLICATION" x:Name="pvtRoot">
<!--Pivot item one-->
<phone:PivotItem Header="item1" x:Name="pvt1" Loaded="pvt1_Loaded">
<Grid>
<ListBox x:Name="lstType1" Width="430" Height="600">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,5,0,0">
<Image Source="{Binding Converter={StaticResource PvtPictureConvert}}"
Stretch="Fill"
/>
<TextBlock
Text="{Binding Name}"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PivotItem>
<!--Pivot item two-->
<phone:PivotItem Header="item2" x:Name="pvt2" Loaded="pvt2_Loaded">
<Grid>
<ListBox x:Name="lstType2" Width="430" Height="600">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,5,0,0">
<Image Source="{Binding Converter={StaticResource PvtPictureConvert}}"
Stretch="Fill" />
<TextBlock
Text="{Binding Name}"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PivotItem>
<phone:PivotItem Header="item3" x:Name="pvt3" Loaded="pvt3_Loaded">
<Grid>
<ListBox x:Name="lstType3" Width="430" Height="600">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,5,0,0">
<Image Source="{Binding Converter={StaticResource PvtPictureConvert}}"
Stretch="Fill" />
<TextBlock
Text="{Binding Name}"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PivotItem>
<phone:PivotItem Header="item4" x:Name="pvt4" Loaded="pvt4_Loaded">
<Grid>
<ListBox x:Name="lstType4" Width="430" Height="600">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,5,0,0">
<Image Source="{Binding Converter={StaticResource PvtPictureConvert}}"
Stretch="Fill" />
<TextBlock
Text="{Binding Name}"
TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PivotItem>
</phone:Pivot>
採用與上述介紹Convert的作法,但載入資料是放置在PivotItem.Loaded的事件。為何會這樣做,主要就是分散記憶體的載量,
這樣也可以讓產生效果比較好。
(B) 初始化載入所有Pictures的Album.Name:
MediaLibrary gMediaLib = null;
private void Initialization()
{
// 取得所有圖像的Album.Name,並且填入PivotItem.Header
var tCollection = (from Collection in gMediaLib.Pictures
select Collection.Album.Name).Distinct();
int tCount = 0;
foreach (string tName in tCollection)
{
((PivotItem)pvtRoot.Items[tCount]).Header = tName;
tCount += 1;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Initialization();
}
(C) 註冊每一個PivotItem.Loaded,加上讀取的處理邏輯:
private void LoadTypePictures(string pTypeName, ListBox pTarget)
{
if (pTarget.Items.Count == 0)
{
var tPictures = from Collection in gMediaLib.Pictures
where Collection.Album.Name == pTypeName
select Collection;
pTarget.ItemsSource = tPictures;
}
}
private void pvt1_Loaded(object sender, RoutedEventArgs e)
{
LoadTypePictures(pvt1.Header.ToString(), lstType1);
}
private void pvt2_Loaded(object sender, RoutedEventArgs e)
{
LoadTypePictures(pvt2.Header.ToString(), lstType2);
}
private void pvt3_Loaded(object sender, RoutedEventArgs e)
{
LoadTypePictures(pvt3.Header.ToString(), lstType3);
}
private void pvt4_Loaded(object sender, RoutedEventArgs e)
{
LoadTypePictures(pvt4.Header.ToString(), lstType4);
}
詳細的程式內容,可以下載範例程式參考{PivotPage.xaml}。
(2) 取得所有的音樂檔,並且顯示於ListBox中,根據用戶選擇的曲目進行播放;
2-1. 在xaml中加入一個ListBox,設定ListBox.ItemTemplate顯示曲目的名稱與作曲人;
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox
x:Name="lstSongs"
SelectionChanged="SongSelectionChanged" Width="430" Height="700">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17"
Width="432" Height="100">
<TextBlock
Text="{Binding Name}" TextWrapping="Wrap"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock
Text="{Binding Artist.Name}" TextWrapping="Wrap"
Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
2-2. 撰寫在畫面載入時,透過MediaLibrary取得所有曲目;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
lstSongs.Items.Clear();
using (MediaLibrary tLibrary = new MediaLibrary())
{
if (tLibrary.Songs.Count == 0)
MessageBox.Show("設備中沒有任何曲目!");
else
lstSongs.ItemsSource = tLibrary.Songs;
}
}
2-3. 撰寫用戶選擇ListBox中的Item時進行曲目的播放;
private void SongSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 透過FrameworkDispatcher.Update()來更新目前播放的主導權
FrameworkDispatcher.Update();
if (MediaPlayer.State == MediaState.Playing)
{
MessageBoxResult tResult = MessageBox.Show(
"是否取代目前正在播放的項目?", "提示",
MessageBoxButton.OKCancel);
if (tResult == MessageBoxResult.OK)
{
MediaPlayer.Stop();
}
}
// 播放指定的Song物件
Song tSong = lstSongs.SelectedItem as Song;
MediaPlayer.Play(tSong);
}
為何會需要透過FrameworkDispatcher.Update()?
可以參考<Windows Phone 7 – Background Audio Playlist Application>的補充說明。
2-4. 刪除選擇的曲目;
if (MessageBox.Show("Is deleted the song?", "Tip", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
Song tItem = ((MenuItem)sender).DataContext as Song;
using (MediaLibrary tLibrary = new MediaLibrary())
{
// 利用MediaLibraryExtensions
MediaLibraryExtensions.Delete(tLibrary, tItem);
}
}
[範例程式]
======
以上是說明如何操作MediaLibrary來取得設備中的所有圖像、音樂與影片。希望對大家有所幫助。
References:
‧Basics of Using Local Storage on Windows Phone 8
‧Reading from an SD Card in Windows Phone 8 Applications
‧System.OutOfMemoryException when using GetImage() from MediaLibrary Picture Collection (重要)
‧How to create a base camera app for Windows Phone
‧windows phone 7 中怎样定义和使用资源(Resource)
‧Theme resources for Windows Phone (重要的參考資源)
‧Windows phone: listbox with images out-of-memory
‧31 Days of Mango | Day #28: MediaLibrary
‧Windows Phone - Microsoft Advertising的使用心得
‧Advanced photo capture for Windows Phone 8