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!