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

Обычно “болванка” необходимого функционала для этого копируется и адаптируется под конкретный сценарий использования. Мы делали это не раз и решили, что пришло время для более элегантного решения. Таким решением оказалось сделать “синглтон”, который был бы не только доступен из разных мест в приложении, но и сэкономил бы ресурсы системы в случае использования одного и того же аудио несколько раз.
В iOS, воспроизвести звуки можно несколькими способами. Система разделяет звуки на “системные” – короткие звуки, которые проигрываются чтобы проинформировать пользователя о каком-то действии; например “озвучить” нажатие на кнопку или подтвердить отправку электронной почты. Другая категория это “музыка” – продолжительное аудио, такое как песни, мелодии и т.д. В нашем случае — это была мелодия, написанная для приложения замечательным композитором Бахтияром Аманжолом.
Проигрывание коротких звуков обеспечивaется с помощью “System Sound Services”. Воспроизведение более продолжительного аудио обеспечивается целой серией средств, работающих на разных уровнях абстракции; мы приняли решение воспользоваться AVAudioPlayer.
Для удобства использования, мы решили дать доступ к функционалу посредством “методов класса” нежели чем “методов объекта”. В результате, проиграть звук можно посредством вот такого кода:
Для того чтобы реализовать такого рода вызов и, одновременно, кэшировать фрагменты аудио, мы использовали шаблон “синглтон”. Синглтоны в Objective-C можно реализовать многими способами. Исследуя эту проблему, мы набрели на очень аккуратный метод, описанный здесь. Вот как выглядит необходимый код:
Затем мы определили “публичные” методы (обратите внимание что это – методы класса):
Эти публичные статичные методы вызывают методы объекта синглтона:
На этом, “музыкальный” функционал приложения был готов. Осталось избавиться от слишком «резких» включений и выключений музыки. Решением стало постепенно добавлять или убавлять уровень звука. AVFoundation такой возможности не давал. К счастью реализовать это с помощью основной библиотеки не так уж сложно:
Вот и все. Надеюсь, материал поможет людям столкнувшимся с аналогичной задачей.
Дополнительно: Страница MCSoundBoard на GitHub
Я хотел бы поделиться решением проблемы проигрывания аудио из приложений 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