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

參考:
https://www.jianshu.com/p/a01c0b59b9c4
https://juejin.cn/post/7026639897289031687

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章