播放iPod Library中的歌曲

現在市面上的音樂播放器都支持iPod Library歌曲(俗稱iPod音樂或者本地音樂)的播放,用戶對於iPod音樂播放的需求也一直十分強烈。這篇要講的是如何來播放iPod Library的歌曲。


概述

根據官方文檔描述Apple從iOS 3.0開始允許開發者訪問用戶的iPod library來獲取用戶放在其中的歌曲等多媒體內容。

爲此Apple提供了多種方法來訪問和播放iPod中的音樂,下面我們來分別列舉一下這些方法。


訪問MediaLibrary

官方文檔訪問iPod Library的方法有兩種,分別是MediaPicker和MediaQuery。

MediaPicker

MediaPicker是一個高度封裝的iPod Library訪問方式,通過使用MPMediaPickerController類來訪問iPod Library。這是一個UI控件,用戶可以根據需要選擇其中的音樂。這個類使用時非常方便,只需要生成一個“的實例,設置一下屬性和delegate後present出來,接下來只要等待回調即可,在回調時需要手動dismiss picker。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];
picker.prompt = @"請選擇需要播放的歌曲";
picker.showsCloudItems = NO;
picker.allowsPickingMultipleItems = YES;
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];


- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker
{
    [mediaPicker dismissViewControllerAnimated:YES completion:nil];
}

- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
    [mediaPicker dismissViewControllerAnimated:YES completion:nil];
    //do something
}

上面的代碼將會得到如下的效果:

通過MediaPicker最終可以得到MPMediaItemCollection,其中存放着所有在Picker中選中的歌曲,每一個歌曲使用一個MPMediaItem對象表示。對於MediaPicker的使用也可以參考官方文檔

MediaQuery

如果你覺得MeidaPicker的功能或者UI不能滿足你的要求那麼可以使用MediaQuery。MediaQuery可以直接訪問iPod Library的DB,並根據需要獲取數據。官方文檔給出了MediaQuery的示意圖。

MediaQuery功能十分強大,它可以根據一個或多個條件查詢滿足需要的MediaItem。

你可以使用MPMediaQuery的類方法來生成一些已經預置了條件的Query

1
2
3
4
5
6
7
8
9
10
11
// Base queries which can be used directly or as the basis for custom queries.
// The groupingType for these queries is preset to the appropriate type for the query.
+ (MPMediaQuery *)albumsQuery;
+ (MPMediaQuery *)artistsQuery;
+ (MPMediaQuery *)songsQuery;
+ (MPMediaQuery *)playlistsQuery;
+ (MPMediaQuery *)podcastsQuery;
+ (MPMediaQuery *)audiobooksQuery;
+ (MPMediaQuery *)compilationsQuery;
+ (MPMediaQuery *)composersQuery;
+ (MPMediaQuery *)genresQuery;

也可以自己生成MPMediaPredicate設置條件,並把它加到Query中,最後通過items和collections訪問查詢到的結果,例如:

1
2
3
4
5
6
7
8
9
10
11
MPMediaPropertyPredicate *artistNamePredicate =
[MPMediaPropertyPredicate predicateWithValue:@"Happy the Clown"
                                 forProperty:MPMediaItemPropertyArtist
                              comparisonType:MPMediaPredicateComparisonEqualTo];

MPMediaQuery *quert = [[MPMediaQuery alloc] init];
[quert addFilterPredicate: artistNamePredicate];
quert.groupingType = MPMediaGroupingArtist;

NSArray *itemsFromArtistQuery = [quert items];
NSArray *collectionsFromArtistQuery = [quert collections];

這一過程可以表示爲(圖來自官方文檔):

這裏對於MediaQuery的用法就不再繼續展開,關於這塊內容並沒有什麼晦澀難懂的地方需要解釋,大家可以通過閱讀官方文檔來詳細瞭解其用法。

MediaCollection

MPMediaCollection是MediaItem的合集,可以通過訪問它的items屬性來訪問所有的MediaItem。

MPMediaPlaylist是一個特殊的MPMediaCollection代表用戶創建的播放列表,它會比MediaCollection包含更多的信息,比如播放列表的名字等等。這些屬性可以通過MPMediaEntity的方法訪問(MPMediaCollection是MPMediaEntity的子類,MPMediaItem也是)。

1
2
3
4
5
6
7
// Returns the value for the given entity property.
// MPMediaItem and MPMediaPlaylist have their own properties
- (id)valueForProperty:(NSString *)property;

// Executes a provided block with the fetched values for the given item properties, or nil if no value is available for a property.
// In some cases, enumerating the values for multiple properties can be more efficient than fetching each individual property with -valueForProperty:.
- (void)enumerateValuesForProperties:(NSSet *)properties usingBlock:(void (^)(NSString *property, id value, BOOL *stop))block NS_AVAILABLE_IOS(4_0);

MediaItem

通過MediaPicker和MediaQuery最終都會得到MPMediaItem,這個item中包含了許多信息。這些信息都可以通過MPMediaEntity的方法訪問,其中參數非常多就不列舉了具體可以參照MPMediaItem.h。


使用MPMusicPlayerController

拿到iPod Library中的歌曲後就可以開始播放了。播放的方式有很多種,先介紹一下MediaPlayer framework中的MPMusicPlayerController類。

通過MPMusicPlayerController的類方法可以生成兩種播放器,生成方法如下:

1
2
3
4
5
// Playing media items with the applicationMusicPlayer will restore the user's iPod state after the application quits.
+ (MPMusicPlayerController *)applicationMusicPlayer;

// Playing media items with the iPodMusicPlayer will replace the user's current iPod state.
+ (MPMusicPlayerController *)iPodMusicPlayer;

這兩個方法看似生成了一樣的對象,但它們的行爲卻有很大不同。從Apple寫的註釋上我們可以很清楚的發現它們的區別。+applicationMusicPlayer不會繼承來自iOS系統自帶的iPod應用中的播放狀態,同時也不會覆蓋iPod的播放狀態。而+iPodMusicPlayer完全繼承iPod應用的播放狀態(甚至是播放時間),對其實例的任何操作也會覆蓋到iPod應用。對+iPodMusicPlayer方法command+點擊後可以看到更詳細的註釋。

1
2
3
4
5
6
7
The iPod music player employs the iPod app on your behalf. On instantiation, it takes on the current iPod app state and controls that state as your app runs. Specifically, the shared state includes the following:
Repeat mode (see Repeat Modes)
Shuffle mode (see Shuffle Modes
Now-playing item (see nowPlayingItem)
Playback state (see playbackState)

Other aspects of iPod state, such as the on-the-go playlist, are not shared. Music that is playing continues to play when your app moves to the background.

說白了,當在使用iPodMusicPlayerv其實並不是你的程序在播放音頻,而是你的程序在操縱iPod應用播放音頻,即使你的程序crash了或者被kill了,音樂也不會因此停止。

而對於+applicationMusicPlayer通過command+點擊可以看到:

1
2
The application music player plays music locally within your app. It does not affect the iPod state.
When your app moves to the background, the music player stops if it was playing.

從註釋中可以知道這個方法返回的對象雖然不是調用iPod應用播放的也不會影響到iPod應用,但它有個很大的缺點:無法後臺播放,即使你在active了audioSession並且在app的設置中設置了Background Audio同樣不會奏效。

綜上所述,一般在開發音樂軟件時很少用到這兩個接口來進行iPod Library的播放,大部分開發者都是用這個類中的volme來調整系統音量的(這個屬性在SDK 7中也被deprecate掉了)。如果你想用到這個類進行播放的話,這裏需要提個醒,給MPMusicPlayerController設置需要播放的音樂時要使用下面兩個方法:

1
2
3
// Call -play to begin playback after setting an item queue source. Setting a query will implicitly use MPMediaGroupingTitle.
- (void)setQueueWithQuery:(MPMediaQuery *)query;
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection;

而不是這個屬性:

1
2
3
// Returns the currently playing media item, or nil if none is playing.
// Setting the nowPlayingItem to an item in the current queue will begin playback at that item.
@property(nonatomic, copy) MPMediaItem *nowPlayingItem;

光看名字很容易被nowPlayingItem這個屬性迷惑,它的意思其實是說在設置了MediaQuery或者MediaCollection之後再設置這個nowPlayingItem可以讓播放器從這個item開始播放,前提是這個item需要在MediaQuery或者MediaCollection的.items集合內。


使用AVAudioPlayer和AVPlayer

除了使用MediaPlayer中的類還有很多其他方法來進行iPod播放,其中做的比較出色的是AVFoundation中的AVAudioPlayerAVPlayer

這兩個類的都有通過NSURL生成實例的初始化方法:

1
2
3
4
5
6
//AVAudioPlayer
- (id)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;

//AVPlayer
+ (id)playerWithURL:(NSURL *)URL;
- (id)initWithURL:(NSURL *)URL;

其中的NSURL正是來自於MPMediaItemMPMediaItemPropertyAssetURL屬性。

1
2
3
4
//A URL pointing to the media item,
//from which an AVAsset object (or other URL-based AV Foundation object) can be created, with any options as desired. 
//Value is an NSURL object.
MP_EXTERN NSString *const MPMediaItemPropertyAssetURL;

上面講到MPMediaItem時已經提到了它是MPMediaEntity子類,可以通過-valueForProperty:方法訪問其中的屬性。通過傳入MPMediaItemPropertyAssetURL就可以得到當前MediaItem對應的URL(ipod-library://xxxxx),生成Player進行播放。大致代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface MyClass : NSObject
{
  AVAudioPlayer *_player;
  //AVPlayer *_player;
}

//設置AudioSession
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

//play
NSError *error = nil;
MPMediaItem *item = ...;
NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
//_player = [AVPlayer playerWithURL:url];
[_player play];

注意:這裏我需要更正一下,之前我在第二篇講到AudioSession時寫了這樣一段話在使用AVAudioPlayer/AVPlayer時可以不用關心AudioSession的相關問題,Apple已經把AudioSession的處理過程封裝了...。這段話不對,我把AVFoundation和Mediaplayer混淆了,在寫的時候也沒注意,應該是在使用MPMusicPlayerController播放時不需要關心AudioSession的問題。


讀取和導出數據

前面說到使用MPMediaItemMPMediaItemPropertyAssetURL屬性可以得到一個表示當前MediaItem的NSURL,有了這個NSURL我們使用AVFoundation中的類進行播放。播放只是最基本的需求,有了這個URL我們可以做更多更有趣的事情。

在AVFoundation中還有兩個有趣的類:AVAssetReaderAVAssetExportSession。它們可以把iPod Library中的指定歌曲以指定的音頻格式導出到內存中或者硬盤中,這個指定的格式包括PCM。這是一個激動人心的特性,有了PCM數據我們就可以做很多很多其他的事情了。

這部分如果要展開的話還會有相當多的內容,國外的先輩們早在2010年就已經發掘了這兩個類的用法,詳細參見這裏這裏。這兩篇講的比較詳細並且附有Sample(其中還涉及了一些Extended Audio File Services的內容),如果裏面Sample無法下載可以從點擊MediaLibraryExportThrowaway1.zipVTM_AViPodReader.zip下載。

需要注意的是在使用AVAssetReader的過程中如果訪問系統的相機或者照片可能會使AVAssetReader產生AVErrorOperationInterrupted錯誤,此時需要重新生成Reader後調用-startReading纔可以繼續讀取數據。


小結

本篇介紹了一些與iPod Library相關的內容,小結一下:

  • Apple提供兩種方法來訪問iPod Library,它們分別是MPMediaPickerControllerMPMediaQuery

  • MPMediaPickerControllerMPMediaQuery最後輸出給開發者的對象是MPMediaItemMPMediaItem的屬性需要通過-valueForProperty:方法獲取了;

  • MPMusicPlayerController可以用來播放MPMediaItem,但有很多侷限性,使用時需要根據不同的使用場景來決定用哪個類方法生成實例;

  • AVAudioPlayerAVPlayer也可以用來播放MPMediaItem,這兩個類的功能比較完善,推薦使用,在使用之前別忘記設置AudioSession;

  • MPMediaItem可以得到對應的URL,這個URL可以用來做很多事情,例如用AVAssetReaderAVAssetExportSession可以導出其中的數據;


發佈了25 篇原創文章 · 獲贊 5 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章