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();
    }
}


目录
相关文章
|
6月前
|
语音技术 开发工具 图形学
Unity与IOS⭐一、百度语音IOS版Demo调试方法
Unity与IOS⭐一、百度语音IOS版Demo调试方法
|
3月前
|
开发框架 前端开发 Android开发
探索安卓和iOS应用开发中的跨平台解决方案
【10月更文挑战第42天】在移动应用开发的广阔天地中,安卓和iOS系统如同两座巍峨的山峰,分别占据着半壁江山。开发者们在这两座山峰之间穿梭,努力寻找一种既能节省资源又能提高效率的跨平台开发方案。本文将带你走进跨平台开发的世界,探讨各种解决方案的优势与局限,并分享一些实用的代码示例,助你在应用开发的道路上更加游刃有余。
|
4月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
128 7
|
5月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台解决方案
【9月更文挑战第27天】在移动应用开发的广阔天地中,安卓和iOS两大操作系统如同双子星座般耀眼。开发者们在这两大平台上追逐着创新的梦想,却也面临着选择的难题。如何在保持高效的同时,实现跨平台的开发?本文将带你探索跨平台开发的魅力所在,揭示其背后的技术原理,并通过实际案例展示其应用场景。无论你是安卓的忠实拥趸,还是iOS的狂热粉丝,这篇文章都将为你打开一扇通往跨平台开发新世界的大门。
114 8
|
9月前
|
存储 Android开发 iOS开发
iOS不支持HEIC格式的图片显示和标签函数显示问题及解决方案
iOS不支持HEIC格式的图片显示和标签函数显示问题及解决方案
266 0
|
6月前
|
测试技术 开发工具 iOS开发
iOS自动化测试方案(三):WDA+iOS自动化测试解决方案
这篇文章是iOS自动化测试方案的第三部分,介绍了在没有MacOS系统条件下,如何使用WDA(WebDriverAgent)结合Python客户端库facebook-wda和tidevice工具,在Windows系统上实现iOS应用的自动化测试,包括环境准备、问题解决和扩展应用的详细步骤。
520 1
iOS自动化测试方案(三):WDA+iOS自动化测试解决方案
|
6月前
|
前端开发 开发工具 Android开发
探索安卓与iOS应用开发:跨平台解决方案的崛起
【8月更文挑战第27天】在移动设备日益普及的今天,安卓和iOS系统占据了市场的主导地位。开发者们面临着一个重要问题:是选择专注于单一平台,还是寻找一种能够同时覆盖两大系统的解决方案?本文将探讨跨平台开发工具的优势,分析它们如何改变了移动应用的开发格局,并分享一些实用的开发技巧。无论你是新手还是资深开发者,这篇文章都将为你提供有价值的见解和建议。
|
6月前
|
语音技术 开发工具 图形学
Unity与IOS⭐二、Unity接入IOS版百度语音:语音唤醒
Unity与IOS⭐二、Unity接入IOS版百度语音:语音唤醒
Unity与IOS⭐二、Unity接入IOS版百度语音:语音唤醒
|
6月前
|
前端开发 JavaScript Android开发
探索Android和iOS开发中的跨平台解决方案
【8月更文挑战第1天】随着移动应用市场的不断扩张,开发者面临一个共同的挑战——如何高效地为多个平台创建和维护应用程序。本文将深入探讨跨平台开发工具,特别是Flutter和React Native,通过比较它们的优势和限制,并辅以实际代码示例,揭示这些工具如何帮助开发者在保持高性能的同时,实现代码的最大化重用。
|
6月前
|
前端开发 JavaScript Android开发
安卓与iOS开发中的跨平台解决方案
【8月更文挑战第24天】在移动应用开发领域,安卓和iOS两大平台占据了主导地位。然而,为这两个平台分别开发和维护应用会带来额外的时间和成本。本文将探讨跨平台开发的概念、优势以及流行的跨平台框架,如React Native和Flutter,并分析它们如何解决多平台开发的挑战。

热门文章

最新文章