Класс для проигрывания аудио из приложений iOS

Добрый день, Хабрасообщество!

Я хотел бы поделиться решением проблемы проигрывания аудио из приложений iOS. Мы столкнулись с этим в процессе разработки очередного приложения: нам хотелось запускать и останавливать воспроизведение музыки и звуковых эффектов в разных местах, зачастую находящихся в разных классах приложения.



Обычно “болванка” необходимого функционала для этого копируется и адаптируется под конкретный сценарий использования. Мы делали это не раз и решили, что пришло время для более элегантного решения. Таким решением оказалось сделать “синглтон”, который был бы не только доступен из разных мест в приложении, но и сэкономил бы ресурсы системы в случае использования одного и того же аудио несколько раз.


Имплементация


В iOS, воспроизвести звуки можно несколькими способами. Система разделяет звуки на “системные” – короткие звуки, которые проигрываются чтобы проинформировать пользователя о каком-то действии; например “озвучить” нажатие на кнопку или подтвердить отправку электронной почты. Другая категория это “музыка” – продолжительное аудио, такое как песни, мелодии и т.д. В нашем случае — это была мелодия, написанная для приложения замечательным композитором Бахтияром Аманжолом.

Проигрывание коротких звуков обеспечивaется с помощью “System Sound Services”. Воспроизведение более продолжительного аудио обеспечивается целой серией средств, работающих на разных уровнях абстракции; мы приняли решение воспользоваться AVAudioPlayer.

Для удобства использования, мы решили дать доступ к функционалу посредством “методов класса” нежели чем “методов объекта”. В результате, проиграть звук можно посредством вот такого кода:

[MCSoundBoard playSoundForKey:@"ding"];


Для того чтобы реализовать такого рода вызов и, одновременно, кэшировать фрагменты аудио, мы использовали шаблон “синглтон”. Синглтоны в Objective-C можно реализовать многими способами. Исследуя эту проблему, мы набрели на очень аккуратный метод, описанный здесь. Вот как выглядит необходимый код:

+ (MCSoundBoard *)sharedInstance
{
    __strong static id _sharedObject = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedObject = [[self alloc] init];
    });
    return _sharedObject;
}


Затем мы определили “публичные” методы (обратите внимание что это – методы класса):

+ (void)addSoundAtPath:(NSString *)filePath forKey:(id)key;
+ (void)playSoundForKey:(id)key;

+ (void)addAudioAtPath:(NSString *)filePath forKey:(id)key;
+ (void)playAudioForKey:(id)key;
+ (void)stopAudioForKey:(id)key;
+ (void)pauseAudioForKey:(id)key;


Эти публичные статичные методы вызывают методы объекта синглтона:

- (void)addSoundAtPath:(NSString *)filePath forKey:(id)key
{
    NSURL* fileURL = [NSURL fileURLWithPath:filePath];
    SystemSoundID soundId;
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &soundId);

    [_sounds setObject:[NSNumber numberWithInt:soundId] forKey:key];
}

+ (void)addSoundAtPath:(NSString *)filePath forKey:(id)key
{
    [[self sharedInstance] addSoundAtPath:filePath forKey:key];
}


Fade–out


На этом, “музыкальный” функционал приложения был готов. Осталось избавиться от слишком «резких» включений и выключений музыки. Решением стало постепенно добавлять или убавлять уровень звука. AVFoundation такой возможности не давал. К счастью реализовать это с помощью основной библиотеки не так уж сложно:

- (void)fadeOutAndStop:(NSTimer *)timer
{
    AVAudioPlayer *player = timer.userInfo;
    float volume = player.volume;
    volume = volume - 1.0 / MCSOUNDBOARD_AUDIO_FADE_STEPS;
    volume = volume < 0.0 ? 0.0 : volume;
    player.volume = volume;

    if (volume == 0.0) {
        [timer invalidate];
        [player pause];
    }
}

- (void)stopAudioForKey:(id)key fadeOutInterval:(NSTimeInterval)fadeOutInterval
{
    AVAudioPlayer *player = [_audio objectForKey:key];

    // If fade in inteval interval is not 0, schedule fade in
    if (fadeOutInterval > 0) {
        NSTimeInterval interval = fadeOutInterval / MCSOUNDBOARD_AUDIO_FADE_STEPS;
        [NSTimer scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(fadeOutAndStop:)
                                       userInfo:player
                                        repeats:YES];
    } else {
        [player stop];
    }
}


Вот и все. Надеюсь, материал поможет людям столкнувшимся с аналогичной задачей.

Дополнительно: Страница MCSoundBoard на GitHub

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 0

Only users with full accounts can post comments. Log in, please.