ios音乐后台播放和远程控制

最近在做音乐播放器,要静音状态下播放,后台播放,远程控制,锁屏显示,上拉菜单控制,来电中断处理等。

开启后台模式:
在TARGETS-Capabilities-Background Modes 勾选Audio,AirPay and Picture in Picture

在AppDelegate中

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //静音状态下播放
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
    //处理电话打进时中断音乐播放
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionNotificationHandler:) name:AVAudioSessionInterruptionNotification object:nil];
    //后台播放
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

    // 在App启动后开启远程控制事件, 接收来自锁屏界面和上拉菜单的控制
    [application beginReceivingRemoteControlEvents];
    // 处理远程控制事件
    [self remoteControlEventHandler];
    
    self.window= [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    [self.window makeKeyAndVisible];
    
    MusicListTableVC * vc = [[MusicListTableViewController alloc] init];
    UINavigationController * nc = [[UINavigationController alloc] initWithRootViewController:vc];
    self.window.rootViewController = nc;
    
    return YES;
}

来电中断处理:

//来电中断处理
- (void)interruptionNotificationHandler:(NSNotification*)notification
{
    NSDictionary *interuptionDict = notification.userInfo;
    NSString *type = [NSString stringWithFormat:@"%@", [interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey]];
    NSUInteger interuptionType = [type integerValue];

    if (interuptionType == AVAudioSessionInterruptionTypeBegan) {
        //获取中断前音乐是否在播放
        _played = [MusicPlayViewController shareMusicPlay].isPlaying;
        NSLog(@"AVAudioSessionInterruptionTypeBegan");
    }else if (interuptionType == AVAudioSessionInterruptionTypeEnded) {
        NSLog(@"AVAudioSessionInterruptionTypeEnded");
    }
    
    if(_played)
    {
        //停止播放的事件
        [[MusicPlayTools shareMusicPlay] musicPause];
        _played=NO;
    }else {
        //继续播放的事件
        [[MusicPlayTools shareMusicPlay] musicPlay];
        _played=YES;
    }
}
- (void)applicationWillResignActive:(UIApplication *)application
{
    NSLog(@"要挂起了。。。");
    //更新锁屏信息
    [[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // 在App要终止前结束接收远程控制事件, 也可以在需要终止时调用该方法终止
    [application endReceivingRemoteControlEvents];
}

远程控制事件处理:

// 在需要处理远程控制事件的具体控制器或其它类中实现
- (void)remoteControlEventHandler
{
    // 直接使用sharedCommandCenter来获取MPRemoteCommandCenter的shared实例
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
    // 启用播放命令 (锁屏界面和上拉快捷功能菜单处的播放按钮触发的命令)
    commandCenter.playCommand.enabled = YES;
    // 为播放命令添加响应事件, 在点击后触发
    [commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        [[MusicPlayTools shareMusicPlay] musicPlay];
        [[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    // 播放, 暂停, 上下曲的命令默认都是启用状态, 即enabled默认为YES
    [commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        //点击了暂停
        [[MusicPlayTools shareMusicPlay] musicPause];
        [[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    [commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        //点击了上一首
        [[MusicPlayViewController shareMusicPlay] lastSongAction];
        [[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    [commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        //点击了下一首
        [[MusicPlayViewController shareMusicPlay] nextSongButtonAction:nil];
        [[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    // 启用耳机的播放/暂停命令 (耳机上的播放按钮触发的命令)
    commandCenter.togglePlayPauseCommand.enabled = YES;
    // 为耳机的按钮操作添加相关的响应事件
    [commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        // 进行播放/暂停的相关操作 (耳机的播放/暂停按钮)
        [[MusicPlayViewController shareMusicPlay] playPauseButtonAction:nil];
        [[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
        return MPRemoteCommandHandlerStatusSuccess;
    }];
}

锁频界面上所显示的歌曲播放信息和图片,控制中心上显示的歌曲播放信息等,这些信息的显示都由MPNowPlayingInfoCenter类来控制。
首先#import <MediaPlayer/MPNowPlayingInfoCenter.h>然后调用MPNowPlayingInfoCenter的单例方法获取实例,再把需要显示的信息组织成Dictionary并赋值给nowPlayingInfo属性就完成了。

其中常用的是MPNowPlayingInfoPropertyElapsedPlaybackTime和MPNowPlayingInfoPropertyPlaybackRate:

  • MPNowPlayingInfoPropertyElapsedPlaybackTime表示已经播放的时间,用这个属性可以让NowPlayingCenter显示播放进度;
  • MPNowPlayingInfoPropertyPlaybackRate表示播放速率。通常情况下播放速率为1.0,即真是时间的1秒对应播放时间中的1秒;

这里需要解释的是,NowPlayingCenter中的进度刷新并不是由app不停的更新nowPlayingInfo来做的,而是根据app传入的ElapsedPlaybackTime和PlaybackRate进行自动刷新。例如传入ElapsedPlaybackTime=120s,PlaybackRate=1.0,那么NowPlayingCenter会显示2:00并且在接下来的时间中每一秒把进度加1秒并刷新显示。如果需要暂停进度,传入PlaybackRate=0.0即可。

所以每次播放暂停和继续都需要更新NowPlayingCenter并正确设置ElapsedPlaybackTime和PlaybackRate否则NowPlayingCenter中的播放进度无法正常显示。

NowPlayingCenter的刷新时机

频繁的刷新NowPlayingCenter并不可取,特别是在有Artwork的情况下。所以需要在合适的时候进行刷新。

依照我自己的经验下面几个情况下刷新NowPlayingCenter比较合适:

  • 当前播放歌曲进度被拖动时
  • 当前播放的歌曲变化时
  • 播放暂停或者恢复时
  • 当前播放歌曲的信息发生变化时(例如Artwork,duration等)

在刷新时可以适当的通过判断app是否active来决定是否必须刷新以减少刷新次数。

/**
 *  设置锁屏信息
 */
-(void)configNowPlayingInfoCenter
{
    Class playingInfoCenter = NSClassFromString(@"MPNowPlayingInfoCenter");
    
    if (playingInfoCenter) {
        MusicInfoModel * model = [MusicPlayTools shareMusicPlay].model;
        NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
        UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld", self.index + 1]];
        MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithBoundsSize:image.size requestHandler:^UIImage * _Nonnull(CGSize size) {
            return image;
        }];
        //歌曲名称
        [songInfo setObject:model.name forKey:MPMediaItemPropertyTitle];
        //演唱者
        [songInfo setObject:model.singer forKey:MPMediaItemPropertyArtist];
        //专辑名
        [songInfo setObject:@"专辑名" forKey:MPMediaItemPropertyAlbumTitle];
        //专辑缩略图
        [songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
        //音乐当前已经播放时间
        NSInteger currentTime = [[MusicPlayTools shareMusicPlay] getCurTime];
        [songInfo setObject:[NSNumber numberWithInteger:currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
        //进度光标的速度 (这个随 自己的播放速率调整,我默认是原速播放)
        [songInfo setObject:[NSNumber numberWithFloat:1.0] forKey:MPNowPlayingInfoPropertyPlaybackRate];
        //歌曲总时间设置
        NSInteger duration = [model.duration integerValue];
        [songInfo setObject:[NSNumber numberWithInteger:duration] forKey:MPMediaItemPropertyPlaybackDuration];
        //设置锁屏状态下屏幕显示音乐信息
        [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容