Добрый день, читатели!
Я хотел бы поделиться решением проблемы проигрывания аудио из приложений 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
Автор: bgln