OC 微信语音、网易云音乐、APP音频播放互放session
2021-05-06 本文已影响0人
喵喵粉
AVAudioPlayer
、AVAudioSession
功能点:
- 后台播放音频
- 音频信息能显示在锁屏界面、控制中心界面
app
在后台播放音频
微信播放语音时app
暂停播放音频,
微信播放语音完成,app
继续播放音频,
微信播放语音未完成,app
收不到AVAudioSessionInterruptionNotification
通知,AVAudioPlayer
不能继续播放- 网易云后台播放音乐
app
播放音频时会暂停网易云后台播放音乐
app
播放音频完成,网易云继续播放音乐
-
后台播放音频
.plist
文件UIBackgroundModes
下添加audio
设置session
的category
,能在锁屏下可以播放的AVAudioSessionCategoryPlayback
、AVAudioSessionCategoryPlayAndRecord
-
音频信息显示在锁屏界面、控制中心界面
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);
}
-
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]];
- 网易云后台播放音乐
-
app
播放音频
- (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];
}
-
app
播放音频完成
#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__);
}
-
app
停止播放后,继续恢复后台其他APP
背景音乐的播放(取消激活当前应用的audio session
)
- (void)resignSession {
[[AVAudioSession sharedInstance] setActive:NO
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:nil];
}
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