iOS15 推送动态语音播报解决方案

简介: iOS15之后,推送多条语音会产生多条横幅,对于动态金额语音,多条横幅是不可取的

问题

iOS15之后,推送多条语音会产生多条横幅,对于动态金额语音,多条横幅是不可取的

解决方案

  1. 做版本管理,iOS15以上,用新的解决方案实现,iOS15以下还是沿用旧的推送方案
/// !!!!: 推送语音播报总控制逻辑
/// @param sourceURLsArr mp3源文件数组
/// @param bestAttemptContent
/// @param completed
-(void)pushVoiceNotificationWithWithSourceURLs:(NSArray *)sourceURLsArr
                           bestAttemptContent:(UNMutableNotificationContent *)bestAttemptContent
                                    completed:(XSNotificationPushCompleted)completed
{
    if (@available(iOS 15.0, *)) {
        // 合并音频文件生成新的音频
        [self mergeAVAssetWithSourceURLs:sourceURLsArr completed:^(NSString *soundName, NSURL *soundsFileURL) {
            if (!soundName) {
                NSLog(@"声音生成失败!");
                completed();
                return;
            }
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
            if (@available(iOS 15.0, *)) {
                bestAttemptContent.interruptionLevel = UNNotificationInterruptionLevelTimeSensitive;
            }
#endif
            UNNotificationSound * sound = [UNNotificationSound soundNamed:soundName];
            bestAttemptContent.sound = sound;
            completed();
        }];
    }
    else//iOS15以下,改用原来旧方式实现
    {
        [self pushLocalNotificationIniOS14ToApp:0 withArray:sourceURLsArr completed:^{
            completed();
        }];
    }
}
  1. 新方案里面,通过NSFileManager把输出音频保存在【AppGroup】的/Library/Sounds/里面,坑点就是,AVAssetExportSession的输出路径必须要保证文件夹存在,不然会提示操作有误,当时直接通过contentsOfDirectoryAtPath来生成两个文件夹,结果不行, 必须要逐个生成,并且要留意生成的文件后缀要符合输出格式要求
///在AppGroup中合并音频
- (void)mergeAVAssetWithSourceURLs:(NSArray *)sourceURLsArr completed:(void (^)(NSString * soundName,NSURL * soundsFileURL)) completed{
    //创建音频轨道,并获取多个音频素材的轨道
    AVMutableComposition *composition = [AVMutableComposition composition];
    //音频插入的开始时间,用于记录每次添加音频文件的开始时间
    __block CMTime beginTime = kCMTimeZero;
    [sourceURLsArr enumerateObjectsUsingBlock:^(id  _Nonnull audioFileURL, NSUInteger idx, BOOL * _Nonnull stop) {
        //获取音频素材
        AVURLAsset *audioAsset1 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audioFileURL]];
        //音频轨道
        AVMutableCompositionTrack *audioTrack1 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
        //获取音频素材轨道
        AVAssetTrack *audioAssetTrack1 = [[audioAsset1 tracksWithMediaType:AVMediaTypeAudio] firstObject];
        //音频合并- 插入音轨文件
        [audioTrack1 insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset1.duration) ofTrack:audioAssetTrack1 atTime:beginTime error:nil];
        // 记录尾部时间
        beginTime = CMTimeAdd(beginTime, audioAsset1.duration);
    }];
    //用动态日期会占用空间
//    NSDateFormatter *formater = [[NSDateFormatter alloc] init];
//    [formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss-SSS"];
//    NSString * timeFromDateStr = [formater stringFromDate:[NSDate date]];
//    NSString *outPutFilePath = [NSHomeDirectory() stringByAppendingFormat:@"/tmp/sound-%@.mp4", timeFromDateStr];
    NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kAppGroupID];
//    NSURL * soundsURL = [groupURL URLByAppendingPathComponent:@"/Library/Sounds/" isDirectory:YES];
    //建立文件夹
    NSURL * soundsURL = [groupURL URLByAppendingPathComponent:@"Library/" isDirectory:YES];
    if (![[NSFileManager defaultManager] contentsOfDirectoryAtPath:soundsURL.path error:nil]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:soundsURL.path withIntermediateDirectories:YES attributes:nil error:nil];
    }
    //建立文件夹
    NSURL * soundsURL2 = [groupURL URLByAppendingPathComponent:@"Library/Sounds/" isDirectory:YES];
    if (![[NSFileManager defaultManager] contentsOfDirectoryAtPath:soundsURL2.path error:nil]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:soundsURL2.path withIntermediateDirectories:YES attributes:nil error:nil];
    }
    // 新建文件名,如果存在就删除旧的
    NSString * soundName = [NSString stringWithFormat:@"sound.m4a"];
    NSString *outPutFilePath = [NSString stringWithFormat:@"Library/Sounds/%@", soundName];
    NSURL * soundsFileURL = [groupURL URLByAppendingPathComponent:outPutFilePath isDirectory:NO];
//    NSString * filePath = soundsURL.absoluteString;
    if ([[NSFileManager defaultManager] fileExistsAtPath:soundsFileURL.path]) {
        [[NSFileManager defaultManager] removeItemAtPath:soundsFileURL.path error:nil];
    }
    //导出合并后的音频文件
    //音频文件目前只找到支持m4a 类型的
    AVAssetExportSession *session = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
    // 音频文件输出
    session.outputURL = soundsFileURL;
    session.outputFileType = AVFileTypeAppleM4A; //与上述的`present`相对应
    session.shouldOptimizeForNetworkUse = YES;   //优化网络
    [session exportAsynchronouslyWithCompletionHandler:^{
        if (session.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"合并成功----%@", outPutFilePath);
            if (completed) {
                completed(soundName,soundsFileURL);
            }
        } else {
            // 其他情况, 具体请看这里`AVAssetExportSessionStatus`.
            NSLog(@"合并失败----%ld", (long)session.status);
            if (completed) {
                completed(nil,nil);
            }
        }
    }];
}
  1. iOS15以下方案不变,通过循环递归推送多条语音信息来实现
////循环调用本地通知,播放音频文件
-(void)pushLocalNotificationIniOS14ToApp:(NSInteger)index withArray:(NSArray *)tmparray completed:(XSNotificationPushCompleted)completed{
    __block NSInteger tmpindex = index;
    if(tmpindex < [tmparray count]){
        NSString *audioFileURL = tmparray[tmpindex];
        NSString * mp3Name = [audioFileURL lastPathComponent];
        AVURLAsset *audioAsset=[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:audioFileURL] options:nil];
        //获取本地mpe3e文件时长
        CMTime audioDuration = audioAsset.duration;
        float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; //标题
        content.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@",mp3Name]];
        content.body = @"";
        // repeats,是否重复,如果重复的话时间必须大于60s,要不会报错
        UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01  repeats:NO];
        /* */
        //添加通知的标识符,可以用于移除,更新等搡作
        NSString * identifier = [[NSUUID UUID] UUIDString];
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
        [center addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) {
            //第一条推送成功后,递归执行
            float time = audioDurationSeconds+0.1;
            if (![mp3Name containsString:@"pre"]) {
                time = 0.4;
            }
            tmpindex = tmpindex+1;
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, time * NSEC_PER_SEC);
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
                [self pushLocalNotificationIniOS14ToApp:tmpindex withArray:tmparray  completed:completed];
            });
        }];
    }else{
        completed();
    }
}


目录
相关文章
|
8月前
|
Android开发 开发者 UED
Android平台RTMP推送端实现外部数据对接推送和录像
好多开发者在做Android平台RTMP推送对接的同时,除了编码前的数据外,还有些外部编码数据推送诉求,他们希望外部的编码音视频数据不止可以实现RTMP推送,还可以同时在推送端实时录制下来,本文以我们(官方)Android平台RTMP直播推送模块为例,介绍下外部数据对接流程和数据录制流程。
|
3月前
|
监控 API iOS开发
克魔助手 - iOS性能检测平台
众所周知,如今的用户变得越来越关心app的体验,开发者必须关注应用性能所带来的用户流失问题。目前危害较大的性能问题主要有:闪退、卡顿、发热、耗电快、网络劫持等,但是做过iOS开发的人都知道,在开发过程中我们没有一个很直观的工具可以实时的知道开发者写出来的代码会不会造成性能问题,虽然Xcode里提供了耗电量检测、内存泄漏检测等工具,但是这些工具使用效果并不理想(如Leak无法发现循环引用造成的内存泄漏)。所以这篇文章主要是介绍一款实时监控app各项性能指标的工具,包括CPU占用率、内存使用量、内存泄漏、FPS、卡顿检测,并且会分析造成这些性能问题的原因。
|
9月前
|
iOS开发
在iOS中调用leaveRoom方法并使用mPaaS音视频通话
在iOS中调用leaveRoom方法并使用mPaaS音视频通话
94 1
|
6月前
|
视频直播 API iOS开发
微信团队分享:详解iOS版微信视频号直播中因帧率异常导致的功耗问题
功耗优化一直是 app 性能优化中让人头疼的问题,尤其是在直播这种用户观看时长特别久的场景。怎样能在不影响主体验的前提下,进一步优化微信iOS端视频号直播的功耗占用,本文给出了一个不太一样的答案。
93 0
|
8月前
|
小程序
uniapp 微信语音播放功能(整理)
uniapp 微信语音播放功能(整理)
|
8月前
|
开发工具 Android开发 开发者
Android平台GB28181接入端语音广播和语音对讲规范解读和技术实现
我在之前的blog,有提到过Android端GB28181接入端的语音广播和语音对讲,今天主要从GB/T28181-2016官方规范和交互流程,大概介绍下Android平GB28181接入端的语音广播和语音对讲。
152 0
|
自然语言处理 开发工具 Swift
移动用户反馈-iOS接入指南 | 学习笔记
快速学习移动用户反馈-iOS接入指南
222 0
移动用户反馈-iOS接入指南 | 学习笔记
|
数据安全/隐私保护 iOS开发
IOS系统推送原理
IOS系统推送原理
160 0
|
开发框架 开发工具
使用融云SDK在APICloud 平台实现单人多人音频通话
使用融云SDK在APICloud 平台实现单人多人音频通话,使用之前必须先获取 token、init、connect,同时需要到融云后台开通音视频通话功能(开通或者关闭 30 分钟后生效)。单人通话逻辑比较简单,主要会用到 didReceiveCall、didConnect、didDisconnect 等三个事件。
194 0
|
开发工具
使用融云SDK在APICloud平台实现单人多人音频通话
使用之前必须先获取token、init、connect,同时需要到融云后台开通音视频通话功能(开通或者关闭30分钟后生效)。单人通话逻辑比较简单,主要会用到didReceiveCall、didConnect、didDisconnect等三个事件。多人通话逻辑复杂一点,并且只能应用在群组或者讨论组,会用到didReceiveCall、didConnect、remoteUserDidJoin、remoteUserDidLeft、remoteUserDidInvite、didDisconnect等六个事件。
143 0
使用融云SDK在APICloud平台实现单人多人音频通话