iphone_dev_jp勉強会で発表した「連続再生アプリをつくろう」のサンプルソースupしました
フナミタカオです。
先週末(7/21)に、開催された、第3回iphone_dev_jp 東京iPhone/Mac勉強会で、プレゼンさせていただいた、「AVQueuePlayerのTips - 連続再生アプリをつくろう - Inside of Attaca」のサンプルソースをgithubにあげました。
サンプルソース
git hub : https://github.com/funami/MusicStoreLite
MTLよりリリースした、iTunesMusicStoreの連続試聴アプリ「Attacca」から、エッセンスを取り出した、ソースとなっています。(ただし、通信エラー等のハンドリング部分は、複雑なので、サンプルソースには、取り込んでいません)
Attacca
↑こちらのアプリの音楽連続再生エンジンから、取り出しました
アプリとしても動作でき、世界のITunesストアで、音楽を販売している国ごとのランキングを表示し、連続再生できるようになっています。
音楽を連続再生するための、いくつかのポイントを取り込んだソースになっており
- 1.バックグラウンドでの再生
- 2.電話、アラームの割り込むからの復帰
- 3.リモートコントローラーへの対応
- 4.ロック画面への音楽のアートワークの表示
- 5.ストリーミングによる、連続再生
に対応しています。
FNMusicPlayManagerというシングルトンのインスタンスが、再生リストを保持して、連続再生や、リモートコントローラー等の接続をおこなっています。
サンプルアプリの概要
国一覧から、
ランキング表示して、連続再生
海外のランキングを眺めているのは、結構、新鮮。
個人的なお気に入りは、「香港」と「台湾」!
たとえば、Kary Ng とか、いかがでしょう?まったく、しらなかったけど、いい感じ(日本のストアでは買えないです)
1.バックグラウンドでの再生
・MusicStoreLite-Info.plist
<key>UIBackgroundModes</key> <array> <string>audio</string> </array>
・FNMusicPlayManager
// バックグラウンドでも、再生を続けるために、AVAudioSessionをAVAudioSessionCategoryPlaybackに AVAudioSession *session = [AVAudioSession sharedInstance]; session.delegate = sharedMusicManager; // 電話等から、復帰時に、再生を再開できるように、delegate接続 NSError *error; [session setCategory:AVAudioSessionCategoryPlayback error:&error]; [session setActive:YES error:&error];
2.電話、アラームの割り込むからの復帰
・FNMusicPlayManager
AVAudioSessionのDelegateを仕掛けておく
AVAudioSession *session = [AVAudioSession sharedInstance]; session.delegate = sharedMusicManager; // 電話等から、復帰時に、再生を再開できるように、delegate接続Delegateで、電話等の割り込み直前(beginInterruption)と直後のコールバック(endInterruptionWithFlags)で、復帰の実装をおこなう
#pragma mark -
#pragma mark Interruption event handling
- (void)beginInterruption
{
//NSLog(@"beginInterruption:%d %d",self.playingMusic,_shouldResume);
self.playingMusic = NO;
}
- (void)endInterruptionWithFlags:(NSUInteger)flags
{
//NSLog(@"endInterruptionWithFlags:%d %d %d",self.playingMusic,_shouldResume,flags);
if (flags == AVAudioSessionInterruptionFlags_ShouldResume){
[[AVAudioSession sharedInstance] setActive: YES error: nil];
if (_shouldResume){
[self play];
}
}
}
- (void)inputIsAvailableChanged
{
// TODO:イヤホンから、スピーカにかわったとき or その逆の挙動をここで実装する
}
3.リモートコントローラーへの対応
・FNMusicPlayManager
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; で、リモートコントローラからのイベント受け取りを開始
[[UIApplication sharedApplication] endReceivingRemoteControlEvents]; ちなみに、終わるときは、こちら
・FNMusicStoreAppDelegate
レスポンダーにイベントとして、コールバックされるので、このアプリではFNMusicPlayManagerにわたす
#pragma mark -
#pragma mark Remote-control event handling
// Respond to remote control events
// リモコンからのイベントをここで、キャッチして、FNMusicPlayManagerにおくります。
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
[[FNMusicPlayManager sharedManager] remoteControlReceivedWithEvent:receivedEvent];
}
4.ロック画面への音楽のアートワークの表示
・FNMusicPlayManager
- (void)updatePlayingInfo{
Class playingInfoCenter = NSClassFromString(@"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSDictionary *item = [self.playList objectAtIndex:self.currentIndex];
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
[songInfo setObject:[[item objectForKey:@"im:name"] objectForKey:@"label"] forKey:MPMediaItemPropertyTitle];
[songInfo setObject:[[item objectForKey:@"im:artist"] objectForKey:@"label"] forKey:MPMediaItemPropertyArtist];
[songInfo setObject:[[[item objectForKey:@"im:collection"] objectForKey:@"im:name"] objectForKey:@"label"] forKey:MPMediaItemPropertyAlbumTitle];
NSURL *imageURL = nil;
NSArray *imgs = [item objectForKey:@"im:image"];
for (NSDictionary *img in imgs){
NSDictionary *attributes = [img objectForKey:@"attributes"];
if (attributes != nil){
if ([[attributes objectForKey:@"height"] isEqualToString:@"170"]){
NSString *urlString = [img objectForKey:@"label"];
imageURL = [NSURL URLWithString:urlString];
}
}
}
MPMediaItemArtwork *artwork = [self artwork:imageURL.absoluteString];
if (artwork){
[songInfo setObject:artwork forKey:MPMediaItemPropertyArtwork];
}else{
[songInfo setObject:[[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"no_image.png"]] forKey:MPMediaItemPropertyArtwork];
}
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
//NSLog(@"songInfo:%@",songInfo);
}
}
setNowPlayingInfoメッソドで表示したい、文字、画像を渡せば、表示してくれます。5.ストリーミングによる、連続再生
・FNMusicPlayManager
- (void)playAtIndex:(NSInteger)index
.......
NSMutableArray *palyerItems = [NSMutableArray array];
int musicCount = [_playList count];
// palyerItems に AVPlayerItemを追加
for (int i = index ; i < musicCount ; i++){
NSURL *url = [_palyURLs objectAtIndex:i];
if (url != nil){
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
[palyerItems addObject:playerItem];
}
}
// AVQueuePlayerのインスタンスつくる
_player = [AVQueuePlayer queuePlayerWithItems:palyerItems];
.......
のあたりAVPlayerItem の配列から、AVQueuePlayerを作る。再生準備ができたらplayメソッドで再生開始曲の変更は
#pragma mark KVO
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
.......
で監視AVQueuePlayerは連続再生と、次の曲への移動は簡単だが、曲飛ばしや、戻るのは不得意。
サンプルでは、次の曲への移行以外は、playAtIndexを呼び直して、palyerItemsの配列から、全曲分の作り直している、これはかなり効率が悪い。
解決方法は、Exploring AV Foundation等参照して工夫してみてください。また、通信状態がわるいとAVQueuePlayerは停止してしまうのですが、停止したことをハンドリングしていません。
本来は、停止を監視して、必要に応じて、通信状態がよくなったら再生復帰するとか、ユーザーにフィードバックする等が必要です。
参考:AV Foundation Programming Guide
また、音楽ファイルの一覧は、iTunes Store RSS フィードジェネレータ のランキングレスポンスを利用しています。 Affiliate Search APIを使うと、ランキングだけでなく、フリーワード検索等もできるようになります。ただ、レスポンス構造が異なるので、今回のサンプルだと、改造が必要ですね。 以上さっくりですが、ぜひ、ソース眺めてみてください。
関連資料
スライド
動画
おまけ
7/27日現在の、香港のランキングを、サンプルアプリで表示するとこんな感じHappy Dev Music Player Apps!