使用 Text-To-Speech postUtteranceDelay 时回避背景音乐不会 un-duck

Ducking background music when using Text-To-Speech postUtteranceDelay doesn't un-duck

问题:

使用 Text-To-Speech 时,我希望背景音频变暗(或 'duck'),说出一句话,然后 un-duck 背景音频。它主要工作,但是当尝试 un-duck 时,它会保持闪避状态,而不会在停用时抛出错误。

上下文和代码:

说话的方法:

// Create speech utterance
AVSpeechUtterance *speechUtterance = [[AVSpeechUtterance alloc]initWithString:textToSpeak];
speechUtterance.rate = instance.speechRate;
speechUtterance.pitchMultiplier = instance.speechPitch;
speechUtterance.volume = instance.speechVolume;
speechUtterance.postUtteranceDelay = 0.005;

AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:instance.voiceLanguageCode];
speechUtterance.voice = voice;

if (instance.speechSynthesizer.isSpeaking) {
    [instance.speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}

AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *activationError = nil;
[audioSession setActive:YES error:&activationError];
if (activationError) {
    NSLog(@"Error activating: %@", activationError);
}

[instance.speechSynthesizer speakUtterance:speechUtterance]; 

然后在speechUtterance说完后停用:

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
{
    dispatch_queue_t myQueue = dispatch_queue_create("com.company.appname", nil);
dispatch_async(myQueue, ^{
        NSError *error = nil;

        if (![[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error]) {
            NSLog(@"Error deactivating: %@", error);
        }
    });
}

在 App Delegate 中设置应用的音频类别:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{    
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *setCategoryError = nil;
    [audioSession setCategory:AVAudioSessionCategoryPlayback
                                 withOptions:AVAudioSessionCategoryOptionDuckOthers error:&setCategoryError];
}

我尝试过的:

当我停用 AVAudioSession ducking/unducking 在延迟 后工作 :

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_queue_create("com.company.appname", nil), ^(void){
    NSError *error = nil;

    if (![[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error]) {
        NSLog(@"Error deactivating: %@", error);
    }
});

但是,延迟很明显,我在控制台中收到错误消息:

[avas] AVAudioSession.mm:1074:-[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.

问题:

如何正确组合 AVSpeechSynthesizer 和背景音频回避?

编辑: 显然问题源于在 AVSpeechUtterance 上使用 postUtteranceDelay,导致音乐持续变暗。删除 属性 可以解决问题。但是,我的一些话语需要 postUtteranceDelay,所以我更新了标题。

在收听 Spotify 时,无需任何 issue/error 使用您的代码,回避工作(开始和停止)。我在 iOS 9.1 上使用了 iPhone 6S,所以这可能是一个 iOS 10 问题。

我建议完全删除调度包装,因为它没有必要。这可能会为您解决问题。

工作代码示例如下,我所做的只是创建一个新项目 ("Single View Application") 并将我的 AppDelegate.m 更改为如下所示:

#import "AppDelegate.h"
@import AVFoundation;

@interface AppDelegate () <AVSpeechSynthesizerDelegate>
@property (nonatomic, strong) AVSpeechSynthesizer *speechSynthesizer;
@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];

    NSError *setCategoryError = nil;
    [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDuckOthers error:&setCategoryError];
    if (setCategoryError) {
        NSLog(@"error setting up: %@", setCategoryError);
    }

    self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
    self.speechSynthesizer.delegate = self;

    AVSpeechUtterance *speechUtterance = [[AVSpeechUtterance alloc] initWithString:@"Hi there, how are you doing today?"];

    AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"];
    speechUtterance.voice = voice;

    NSError *activationError = nil;
    [audioSession setActive:YES error:&activationError];
    if (activationError) {
        NSLog(@"Error activating: %@", activationError);
    }

    [self.speechSynthesizer speakUtterance:speechUtterance];

    return YES;
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
    NSError *error = nil;
    if (![[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error]) {
        NSLog(@"Error deactivating: %@", error);
    }
}

@end

在物理设备上 运行 时控制台的唯一输出是:

2016-12-21 09:42:08.484 DimOtherAudio[19017:3751445] Building MacinTalk voice for asset: (null)

更新

设置 postUtteranceDelay 属性 对我造成了同样的问题。

postUtteranceDelay 的文档指出:

The amount of time a speech synthesizer will wait after the utterance is spoken before handling the next queued utterance.

When two or more utterances are spoken by an instance of AVSpeechSynthesizer, the time between periods when either is audible will be at least the sum of the first utterance’s postUtteranceDelay and the second utterance’s preUtteranceDelay.

从文档中可以清楚地看出,此值仅设计用于在添加另一个话语时使用。我确认添加第二个未设置的话语 postUtteranceDelay 取消音频。

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

NSError *setCategoryError = nil;
[audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDuckOthers error:&setCategoryError];
if (setCategoryError) {
    NSLog(@"error setting up: %@", setCategoryError);
}

self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
self.speechSynthesizer.delegate = self;

AVSpeechUtterance *speechUtterance = [[AVSpeechUtterance alloc] initWithString:@"Hi there, how are you doing today?"];
speechUtterance.postUtteranceDelay = 0.005;

AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"];
speechUtterance.voice = voice;

NSError *activationError = nil;
[audioSession setActive:YES error:&activationError];
if (activationError) {
    NSLog(@"Error activating: %@", activationError);
}

[self.speechSynthesizer speakUtterance:speechUtterance];

// second utterance without postUtteranceDelay
AVSpeechUtterance *speechUtterance2 = [[AVSpeechUtterance alloc] initWithString:@"Duck. Duck. Goose."];
[self.speechSynthesizer speakUtterance:speechUtterance2];

这是我的 Swift 3 版本,摘自上面的 Casey's

import Foundation
import AVFoundation

class Utils: NSObject {
    static let shared = Utils()

    let synth = AVSpeechSynthesizer()
    let audioSession = AVAudioSession.sharedInstance()

    override init() {
        super.init()

        synth.delegate = self
    }

    func say(sentence: String) {
        do {
            try audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.duckOthers)

            let utterance = AVSpeechUtterance(string: sentence)

            try audioSession.setActive(true)

            synth.speak(utterance)
        } catch {
            print("Uh oh!")
        }
    }
}

extension Utils: AVSpeechSynthesizerDelegate {
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        do {
            try audioSession.setActive(false)
        } catch {
            print("Uh oh!")
        }
    }
}

然后我在我的应用程序中的任何地方调用它,例如:Utils.shared.say(sentence: "Thanks Casey!")