OC 微信语音、网易云音乐、APP音频播放互放session

2021-05-06  本文已影响0人  喵喵粉

AVAudioPlayerAVAudioSession

功能点:

  1. 后台播放音频
  2. 音频信息能显示在锁屏界面、控制中心界面
  3. app在后台播放音频
    微信播放语音时app暂停播放音频,
    微信播放语音完成,app继续播放音频,
    微信播放语音未完成,app收不到AVAudioSessionInterruptionNotification通知,AVAudioPlayer不能继续播放
  4. 网易云后台播放音乐
    app播放音频时会暂停网易云后台播放音乐
    app播放音频完成,网易云继续播放音乐
  1. 后台播放音频
    .plist文件UIBackgroundModes下添加audio
    设置sessioncategory,能在锁屏下可以播放的AVAudioSessionCategoryPlaybackAVAudioSessionCategoryPlayAndRecord

  2. 音频信息显示在锁屏界面、控制中心界面

AppDelegate.m

#import <MediaPlayer/MediaPlayer.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions {
    
    //https://www.jishudog.com/3906/html
    MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:CGSizeMake(50, 50) requestHandler:^UIImage * _Nonnull(CGSize size) {
        return [[UIImage imageNamed:@"icon"] originImageScaleToSize:size];
    }];

    //锁屏界面显示的图文
    [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:@{
        MPMediaItemPropertyTitle:@"ciluo",
        MPMediaItemPropertyAlbumTitle:@"好音乐网",
        MPMediaItemPropertyArtwork:artwork
    }];
    
    ...
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

///接收远端控制事件
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    
    [self resignFirstResponder];
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    
    [self becomeFirstResponder];
}

响应远端控制事件:控制中心、锁屏界面的播放暂停
ViewController.m

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    if (UIEventTypeRemoteControl == event.type) {
        switch (event.subtype) {
            case UIEventSubtypeRemoteControlPlay:
                [_player play];
                break;
            case UIEventSubtypeRemoteControlPause:
                [_player pause];
                break;
            case UIEventSubtypeRemoteControlStop:
                break;
            case UIEventSubtypeRemoteControlPreviousTrack:
                break;
            case UIEventSubtypeRemoteControlNextTrack:
                break;
            case UIEventSubtypeRemoteControlBeginSeekingBackward:
                break;
            case UIEventSubtypeRemoteControlEndSeekingBackward:
                break;
            case UIEventSubtypeRemoteControlBeginSeekingForward:
                break;
            case UIEventSubtypeRemoteControlEndSeekingForward:
                break;

            default:
                break;
        }
    }
    NSLog(@"%s %@", __func__, event);
}
  1. app后台播放音频时,微信播放语音
    会收到中断通知
//3.添加session的通知:中断、线路切换
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interupAction:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChangeAction:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(otherAppHintAction:) name:AVAudioSessionSilenceSecondaryAudioHintNotification object:[AVAudioSession sharedInstance]];
  1. 网易云后台播放音乐
- (void)playAmr {
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback
             withOptions:AVAudioSessionCategoryOptionAllowBluetooth
                   error:nil];
    [session setActive:YES error:nil];
    
    //初始化播放器
    self.player = [[AVAudioPlayer alloc] initWithData:_musicData error:nil];
    self.player.delegate = self;
    [self.player prepareToPlay];
    [self.player play];
}
#pragma mark - AVAudioPlayerDelegate

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
    NSLog(@"%s", __func__);
    
    [self resignSession];
}

/* if an error occurs while decoding it will be reported to the delegate. */
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error {
    NSLog(@"%s", __func__);
}
- (void)resignSession {
    [[AVAudioSession sharedInstance] setActive:NO
                                   withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
                                         error:nil];
}
  1. vc.m
@interface ViewController ()<AVAudioPlayerDelegate>
@property (nonatomic, strong) AVAudioPlayer *player;

@property (weak, nonatomic) IBOutlet UILabel *lbTip;

@property (nonatomic, strong) NSData *musicData;
@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [_player pause];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self readyData];
    [self audioAuth];
}

- (void)readyData {
    NSArray *amrs = @[@"ciluo", @"listen"];
    NSString *path = [[NSBundle mainBundle] pathForResource:amrs.firstObject ofType:@"amr"];
    NSData * data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:path]];
    
    NSString *tmpAmrPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AudioTempFile"];
    NSString *tmpWavPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AudioTempConvertFile"];
    
    //把data写入文件中,取名AudioTempFile
    [data writeToFile:tmpAmrPath atomically:YES];
    //将amr格式的数据转成wav
    [EMVoiceConverter amrToWav:tmpAmrPath wavSavePath:tmpWavPath];
    //读取新的wav格式音频文件
    self.musicData = [NSData dataWithContentsOfFile:tmpWavPath];
    
    //第二种转换data
    NSData *wavData = DecodeAMRToWAVE(data);
    NSLog(@"%ld, %ld", self.musicData.length, wavData.length);
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    if (UIEventTypeRemoteControl == event.type) {
        switch (event.subtype) {
            case UIEventSubtypeRemoteControlPlay:
                [_player play];
                break;
            case UIEventSubtypeRemoteControlPause:
                [_player pause];
                break;
            case UIEventSubtypeRemoteControlStop:
                break;
            case UIEventSubtypeRemoteControlPreviousTrack:
                break;
            case UIEventSubtypeRemoteControlNextTrack:
                break;
            case UIEventSubtypeRemoteControlBeginSeekingBackward:
                break;
            case UIEventSubtypeRemoteControlEndSeekingBackward:
                break;
            case UIEventSubtypeRemoteControlBeginSeekingForward:
                break;
            case UIEventSubtypeRemoteControlEndSeekingForward:
                break;

            default:
                break;
        }
    }
    NSLog(@"%s %@", __func__, event);
}

#pragma mark - auth

- (void)audioAuth {
    //Info.plist must contain an NSMicrophoneUsageDescription key with a string value
    [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
        if (granted) {
            NSLog(@"auth ok");
            dispatch_async(dispatch_get_main_queue(), ^{
                [self setup];
            });
        } else {
            NSLog(@"auth not ok");
            dispatch_async(dispatch_get_main_queue(), ^{
                [self notPermissionTip];
            });
        }
    }];
}

- (void)notPermissionTip {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"麦克风访问权限受限" preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
    [alert addAction:[UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
    }]];
    [self presentViewController:alert animated:YES completion:nil];
}

- (void)setup {
    
    //3.添加session的通知:中断、线路切换
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interupAction:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChangeAction:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(otherAppHintAction:) name:AVAudioSessionSilenceSecondaryAudioHintNotification object:[AVAudioSession sharedInstance]];
    
    //4.设置终端的rate、channels、duration等

    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop target:self action:@selector(stopPlay)];
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(playAmr)];
}

#pragma mark - action

- (void)playAmr {
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback
             withOptions:AVAudioSessionCategoryOptionAllowBluetooth
                   error:nil];
    [session setActive:YES error:nil];
    
    //初始化播放器
    self.player = [[AVAudioPlayer alloc] initWithData:_musicData error:nil];
    self.player.delegate = self;
    [self.player prepareToPlay];
    [self.player play];
}

- (void)stopPlay {
    [_player stop];
    
    [self resignSession];
}

///自己APP停止播放后,继续恢复后台其他APP背景音乐的播放(取消激活当前应用的audio session)
- (void)resignSession {
    [[AVAudioSession sharedInstance] setActive:NO
                                   withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
                                         error:nil];
}

///继续播放⏸️的音乐
- (void)resumePlay {
        
    NSError *error = nil;
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback
             withOptions:AVAudioSessionCategoryOptionAllowBluetooth
                   error:&error];

    //Activate audio session in current app
    //Deactivate audio session in others' app
    
    //https://baiduhidevios.github.io/2017/03/26/团队原创之AV%20Foundation-AVAudioPlayer/
    [session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
//    [session setActive:YES error:nil];
    
    if (error) {
        NSLog(@"setActive:YES error:%@", error.description);
    } else {
        NSLog(@"setActive:YES no error");
    }
    
    [self.player play];
}

- (void)audioMethod {
    
}

#pragma mark - NSNotification

- (void)interupAction:(NSNotification *)notification {
    NSLog(@"%s :%@", __func__, notification.userInfo);
    NSDictionary *userInfo = notification.userInfo;
    
    //
    AVAudioSessionInterruptionType type = [userInfo[AVAudioSessionInterruptionTypeKey] integerValue];
    switch (type) {
        case AVAudioSessionInterruptionTypeBegan:
        {
            NSLog(@"开始暂停 ⏸️");
            [self.player pause];
        }
            break;
        case AVAudioSessionInterruptionTypeEnded:
        {
            NSUInteger opts = [userInfo[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
            if (AVAudioSessionInterruptionOptionShouldResume == opts) {
                NSLog(@"app恢复播放");
                
                [self resumePlay];
            } else {
                NSLog(@"app不恢复播放 %lu", opts);
            }
        }
            break;
        default:
            break;
    }
    
    BOOL suspended = userInfo[AVAudioSessionInterruptionWasSuspendedKey];
    if (suspended) {
        NSLog(@"app Suspended");
    } else {
        NSLog(@"app not Suspended");
    }
    
}

- (void)routeChangeAction:(NSNotification *)notification {
    NSLog(@"%s :%@", __func__, notification.userInfo);
    AVAudioSessionRouteChangeReason type = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] integerValue];
    switch (type) {
        case AVAudioSessionRouteChangeReasonUnknown:
            break;
        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
        {
            AVAudioSessionRouteDescription *desc = notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
            NSArray<AVAudioSessionPortDescription *> *inputs = desc.inputs;
        }
            break;
        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
        {
            AVAudioSessionRouteDescription *roteDesc = notification.userInfo[AVAudioSessionRouteChangePreviousRouteKey];
            NSArray<AVAudioSessionPortDescription *> *outputs = roteDesc.outputs;
            
            AVAudioSessionPortDescription *portDesc = outputs.firstObject;
            if ([AVAudioSessionPortHeadphones isEqualToString:portDesc.portType]) {
                
            } else if ([AVAudioSessionPortHeadphones isEqualToString:portDesc.portType]) {
                
            }
            
        }
            break;
        default:
            break;
    }
}

- (void)otherAppHintAction:(NSNotification *)notification {
    NSLog(@"%s :%@", __func__, notification.userInfo);
    AVAudioSessionSilenceSecondaryAudioHintType type = [notification.userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] integerValue];
    switch (type) {
        case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
            break;
        case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
            break;
        default:
            break;
    }
}

#pragma mark - AVAudioPlayerDelegate <NSObject>

/* audioPlayerDidFinishPlaying:successfully: is called when a sound has finished playing. This method is NOT called if the player is stopped due to an interruption. */
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
    NSLog(@"%s", __func__);
    
    [self resignSession];
}

/* if an error occurs while decoding it will be reported to the delegate. */
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error {
    NSLog(@"%s", __func__);
}

@end
上一篇下一篇

猜你喜欢

热点阅读