MPMediaLibraryAuthorizationStatusチェックについて

iOS10からは、アプリがiTunesメディアファイルにアクセスする際に、ユーザの許可が必要になりました。

そのアラート画面で許可された後、画面などのUIを更新する場合は、そのままではバックグラウンドで処理されてクラッシュの原因になってしまいます。(毎回クラッシュする訳ではないから厄介)

ブロック内でperformSelectorOnMainThreadかGCDを使ってメインスレッドからUIを更新します。

[MPMediaLibrary requestAuthorization:^(MPMediaLibraryAuthorizationStatus status) {
    if (status == MPMediaLibraryAuthorizationStatusAuthorized) {
        // This will work in the background
        NSLog(@"isMainThread: %d", NSThread.isMainThread);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
        });
    }
}];

或いは、UIとは分離させて通知を投げる様にしておくと良いと思います。むしろそうゆう使い方が前提かも。

AVAudioEngineでiPodライブラリの曲を再生しよう

iOS8から導入されたAVAudioEngineを使えば、Core Audioよりも簡単!と、聞いていたので、iPodの曲を再生させてみました。追加できる音響効果はEQ、リバーブ、ピッチシフト、タイムストレッチなどのAudio Unit由来のものが使えるようです。今回は簡単なクラスAEAudioPlayerControllerを作ってリバーブをかけて再生してみます。

AEAudioPlayerController.h

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

@interface AEPlayerController : NSObject

+ (instancetype)sharedController;
- (void)play;

@end

AEAudioPlayerController.m

@property (nonatomic) AVAudioEngine *engine;
@property (nonatomic) AVAudioPlayerNode *playerNode;
@property (nonatomic) AVAudioUnitReverb *reverbNode;
@property (nonatomic) AVAudioFile *audioFile;
@property (nonatomic) MPMediaItem *nowPlayingItem;

AVAudioPlayerNodeとはプレイヤーの様なものですが、AVAudioPlayerとは全くの他人
さらにリバーブがAVAudioUnitReverbとなっていてNodeが付いていない

+ (instancetype)sharedController {
    static id sharedController;
    static dispatch_once_t queue = 0;
    dispatch_once(&queue, ^ {
        sharedController = [self new];
    });
    return sharedController;
}
- (instancetype)init {
    self = [super init];
    if (self) {
        self.engine = [AVAudioEngine new];
        self.playerNode = [AVAudioPlayerNode new];
        self.reverbNode = [AVAudioUnitReverb new];
        self.reverbNode.wetDryMix = 40.0f;
        // attach to engine
        [self.engine attachNode:self.playerNode];
        [self.engine attachNode:self.reverbNode];
        // player -> reverb -> mixer
        [self.engine connect:self.playerNode to:self.reverbNode format:self.audioFile.processingFormat];
        [self.engine connect:self.reverbNode to:self.engine.mainMixerNode format:self.audioFile.processingFormat];
        // 適当に再生できる曲をセット
        MPMediaQuery *query = [MPMediaQuery songsQuery];
        [query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:@NO forProperty:MPMediaItemPropertyIsCloudItem]];
        self.nowPlayingItem = query.items[0];
    }
    return self;
}
- (void)play {
    NSURL *url = [self.nowPlayingItem valueForProperty:MPMediaItemPropertyAssetURL];
    self.audioFile = [[AVAudioFile alloc] initForReading:url error:nil];
    [self.engine startAndReturnError:nil];
    [self loadBuffer];
    [self.playerNode play];
}
- (void)loadBuffer {
    uint32_t capacity = 32768;
    AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.audioFile.processingFormat frameCapacity:capacity];
    if (buffer != nil) {
        [self.audioFile readIntoBuffer:buffer error:nil];
        [self scheduleBuffer:buffer];
    }
}
- (void)scheduleBuffer:(AVAudioPCMBuffer *)buffer {
    [self.playerNode scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
        [self loadBuffer];
    }];
}

適当な場所で[[AEAudioPlayerController sharedController] play];すると再生開始。loadBufferすると繰り返しloadBufferが呼ばれて少しづつバッファが溜まる仕組みです。iPodの曲はアプリにバンドルされたファイルを再生するのと違って、こうしておかないと再生されないみたい。これなら、ストリーミングにも使えそうでいいかも。ただ、プレイヤーとして完成させるには、まだまだ機能不足。それでも再生だけならCore Audioより楽かも。

EQも挟むならこんな感じになります。

// player -> eq -> reverb -> mixer
[self.engine connect:self.playerNode to:self.eqNode format:self.audioFile.processingFormat];
[self.engine connect:self.eqNode to:self.reverbNode format:self.audioFile.processingFormat];
[self.engine connect:self.reverbNode to:self.engine.mainMixerNode format:self.audioFile.processingFormat];

iOS8.1