На WWDC всегда есть отличная коллекция музыки для отличного настроения. Во время WWDC’19 мой друг, используя свой телефон на Android, распознавал игравшие песни и создал из них плэйлист. У моего iPhone тогда такой возможности не было (или я о такой не знал). Теперь с помощью ShazamKit давайте создадим для этой цели приложение и назовем его Musadora!
В этой статье мы последовательно пройдемся через следующее:
- Что такое ShazamKit ?
- Как это работает
- Службы приложения ShazamKit
- Распознавание Музыки
- Библиотека Shazam
- Создание пользовательского интерфейса с помощью SwiftUI
- Распознавание фона
Внимание - это бета-версия программы, которая в будущем в любое время может измениться.
Для этого туториала вам понадобится Xcode 13.0 и iOS 15.0. При написании этого поста я использовал Xcode 13.0 Beta 2 и iOS 15.0 Beta 2.
Что такое ShazamKit?
ShazamKit - это фреймворк от Apple, который помогает вам как разработчику встроить распознавание музыки в ваше приложение. Это может быть или распознавание песен из каталога Shazam, или даже ваше собственное аудио. Вы можете добавить определившуюся песню в историю распознавания музыки Shazam. Apple неожиданно сделала его доступным и для Android.
Как это работает
Раньше я предполагал, что Shazam действительно сопоставляет фактическое аудио песни с их обширной библиотекой. Но, согласно документации,
чтобы найти пару, ShazamKit использует уникальную акустическую подпись аудиозаписи. Данная подпись фиксирует частотно-временное распределение энергии аудиосигнала, и она значительно меньше оригинального звука. Это также одностороннее преобразование, поэтому изменение подписи обратно в запись маловероятно.
Проще говоря, Shazam создает частотный график на основе времени под названием Spectrogram для каждого аудио в каталоге. Далее на основе графика создается подпись. Референциальная (ссылочная) подпись - это уникальная подпись и метаданные с информацией о песне.
Музыкальный каталог Shazam - это коллекция подобных уникальных ссылочных подписей. ShazamKit создает аудиообраз отправляемой ему записи, чтобы в дальнейшем сравнить её с каталогом. Если имеется достаточная доля соответствия, он возвращает нам такую информацию, как название песни и её исполнитель. Его алгоритм вполне мощный, чтобы распознавать музыку даже на шумном фоне.
Т.к. аудиообраз не может быть преобразован обратно в исходную запись, содержимое остается безопасным и приватным.
Существует целая научно-исследовательская работа по их алгоритму для тех, кому любопытно узнать больше.
Начало работы
Чтобы начать работу с ShazamKit и его сервисами, мы должны включить его для идентификатора нашего приложения. На портале для разработчиков, в разделе «Certificates, Identifiers, и Profiles» перейдите на вкладку «Identifiers» на боковой панели и нажмите значок «Add», чтобы создать новый идентификатор приложения.
Нажмите Продолжить. Назовите Bundle ID в соответствии с вашими предпочтениями. В разделе «App Services» выберите ShazamKit для использования его функционала.
ShazamKit
Он разделен на три компонента:
- Распознавание каталога Shazam
- Распознавание каталога Пользователя
- Управление Библиотекой
Мы будем использовать распознавание каталога Shazam для определения песен в нашем приложении. Затем мы добавим их в библиотеку Shazam.
Распознавание Музыки
Откройте Xcode и создайте новый iOS проект на SwiftUI с названием Musadora. Создайте новый файл Swift с именем HomeViewModel.swift и добавьте в него следующий класс:
import Combine
import ShazamKit
import AVKit
@MainActor
class HomeViewModel: NSObject, ObservableObject {
// 1
@Published private(set) var mediaItems: [SHMediaItem] = []
// 2
@Published private(set) var isRecognizingSong = false
// 3
private let session = SHSession()
// 4
private let audioEngine = AVAudioEngine()
private let feedback = UINotificationFeedbackGenerator()
override init() {
super.init()
// 5
session.delegate = self
}
}
Здесь разбивка данного кода:
- Массив SHMediaItem представляет собой метаданные для ссылочной подписи. Каждый раз, когда Shazam находит совпадение, мы добавляем элемент в этот массив.
- @Published переменная isRecognizingSong помогает нам управлять состоянием распознавания песни.
- Объект session используется для управления сопоставлением аудиозаписи.
- AVAudioEngine одновременно преобразует звук в подпись во время записи.
- Результаты SHSession передаются через его делегата. Присвоив session.delegate значение self, мы можем получить доступ ко всем его методам.
Сначала мы начинаем с кода для записи аудио. Добавьте следующее в HomeViewModel.swift:
// MARK: Audio Recognition
extension HomeViewModel {
// 1
private func prepareAudioRecording() throws {
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.record)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
}
// 2
private func generateSignature() {
let inputNode = engine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: .zero)
inputNode.installTap(onBus: .zero, bufferSize: 1024,
format: recordingFormat) { [weak session] buffer, _ in
session?.matchStreamingBuffer(buffer, at: nil)
}
}
// 3
private func startAudioRecording() throws {
try engine.start()
isRecognizingSong = true
}
}
Суть приложения в Распознавании. Поэтому вот подробное пояснение:
- Начинаем с общего экземпляра AVAudioSession и настраиваем его для записи звука с микрофона. Далее активируем параметр audioSession.
- Создаем переменную inputNode для хранения текущей дорожки входного аудиосигнала. Мы создаем audioFormat со стандартным форматом. Настраиваем входной сигнал микрофона на поддерживаемый аудиоформат. Далее мы устанавливаем аудиоразъем (отвод) на шину. В данном блоке, находящееся в буфере аудио мы конвертируем в подпись и ищем ссылочные подписи в каталоге сессии.
- Запускаем аудио движок и устанавливаем isRecognizingSong в значение true.
Для упрощения процесса распознавания аудио, мы создаем два публичных метода для вызова в наших View:
public func startRecognition() {
feedback.prepare()
// 1
do {
if engine.isRunning {
stopRecognition()
return
}
// 2
try prepareAudioRecording()
generateSignature()
try startAudioRecording()
feedback.notificationOccurred(.success)
} catch {
// Handle errors here
print(error)
feedback.notificationOccurred(.error)
}
}
public func stopRecognition() {
isRecognizingSong = false
engine.stop()
engine.inputNode.removeTap(onBus: .zero)
}
Пройдемся по коду:
- Если аудио движок уже запущен, то срабатывает функция остановки распознавания: останавливается движок и убирается разъем с входного узла.
- Подготавливает аудио к записи, генерирует подпись и начинает аудиозапись.
SHSessionDelegate предоставляет нам два метода: один при обнаружении совпадения, а другой - при отсутствии совпадения. Когда мы находим совпадение, он возвращает объект SHMatch, представляющий медиа элементы каталога. Мы получаем доступ к первому элементу в элементах. Если mediaItems уже содержит данную песню, мы ее игнорируем. Если распознанная песня новая, мы добавляем ее в массив.
// MARK:- SHSessionDelegate
extension HomeViewModel: SHSessionDelegate {
func session(_ session: SHSession, didFind match: SHMatch) {
guard let mediaItem = match.mediaItems.first else { return }
async {
if mediaItems.contains(where: { $0.shazamID == mediaItem.shazamID }) {
// Song already identified and in the list. Do nothing.
} else {
self.mediaItems.append(mediaItem)
}
}
}
}
Добавление музыки в Библиотеку Shazam
Вы также можете добавить распознанную музыку из вашего приложения в библиотеку Shazam. Для этого добавьте следующий код в HomeViewModel:
public func addToShazamLibrary() {
SHMediaLibrary.default.add(mediaItems) { error in
if let error = error {
print(error)
} else {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
}
}
}
Мы получаем доступ к пользовательской библиотеке Shazam по умолчанию и добавляем в нее список mediaItems.
Заметка
Начиная с 3 беты, медиа-элементы, добавленные в дефолтный экземпляр SHMediaLibrary, не отображаются в Shazam. (77785557)
Обходной путь: Нажмите и удерживайте модуль Центра управления распознаванием музыки для просмотра содержимого SHMediaLibrary.
Благодаря этому мы написали функционал, запрашиваемый для распознавания музыки с ShazamKit. Давайте создадим UI, чтобы применить его!
Создание Пользовательского Интерфейса с использованием SwiftUI
Вы можете спроектировать экран исходя из своих предпочтений. Я использую обложку альбома и отображаю название песни и имя исполнителя рядом с ней.
struct ShazamMusicCard: View {
var item: SHMediaItem
var body: some View {
HStack {
ArtworkImage(url: url) { image in
image
.scaledToFit()
.transition(.opacity.combined(with: .scale))
}
.cornerRadius(12.0)
.frame(width: 100.0, height: 100.0)
VStack(alignment: .leading, spacing: 4.0) {
Text(name)
.fontWeight(.bold)
.font(.callout)
Text(artistName)
.fontWeight(.light)
.font(.caption)
}
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)
}
}
private var name: String {
item.title ?? ""
}
private var artistName: String {
item.artist ?? ""
}
private var url: URL? {
item.artworkURL
}
}
Добавляем карточку поверх размытого фонового изображения:
struct ShazamMusicRow: View {
var item: SHMediaItem
var body: some View {
ZStack {
ArtworkImage(url: item.artworkURL) { image in
image
.scaledToFill()
.layoutPriority(-1)
.overlay(Color.black.opacity(0.8))
.transition(.opacity.combined(with: .scale))
}
ShazamMusicCard(item: item)
.background(.ultraThinMaterial)
}
.cornerRadius(12.0)
.padding(4.0)
}
}
ArtworkImage - удобное view для использования последней версии AsyncImage в SwiftUI Release 3.0 и некоторых view по умолчанию для обработки различных этапов:
struct ArtworkImage: View where Content: View {
private let url: URL?
private var content: (_ image: Image) -> Content
public init(url: URL?, @ViewBuilder content: @escaping (_ image: Image) -> Content) {
self.url = url
self.content = content
}
var body: some View {
if let url = url {
AsyncImage(url: url, transaction: .init(animation: .spring())) { phase in
switch phase {
case .empty: progressView()
case .success(let image): content(image.resizable())
case .failure(let error as NSError): errorView(with: error)
@unknown default: unknownView()
}
}
} else {
Text("Wrong URL")
}
}
private func progressView() -> some View {
ProgressView().transition(.opacity.combined(with: .scale))
}
private func errorView(with error: NSError) -> some View {
ZStack {
Color.red.transition(.opacity.combined(with: .scale))
Text(error.localizedDescription).foregroundColor(.white)
}
.transition(.opacity.combined(with: .scale))
}
private func unknownView() -> some View {
Color.gray.transition(.opacity.combined(with: .scale))
}
}
Создаем другой SwiftUI файл с названием HomeButtonsView.swift и добавляем в него:
extension Image {
func imageButton(with size: CGFloat, color: Color) -> some View {
self
.resizable()
.scaledToFit()
.frame(width: size, height: size)
.foregroundColor(color)
}
}
struct ButtonImageType {
static let addToLibrary = "square.and.arrow.down.fill"
static let startRecognition = "shazamIcon"
static let stopRecognition = "stop.circle.fill"
}
struct HomeButtonsView: View {
@ObservedObject var viewModel: HomeViewModel
private let size = 50.0
private var isRecognizingSong: Bool {
viewModel.isRecognizingSong
}
var body: some View {
HStack {
// 1
Button(action: { viewModel.addToShazamLibrary() }) {
Image(systemName: ButtonImageType.addToLibrary)
.imageButton(with: size, color: .green)
}
Spacer()
// 2
Button(action: { viewModel.startRecognition() }) {
Image(ButtonImageType.startRecognition)
.imageButton(with: size * 2, color: .clear)
}
.disabled(isRecognizingSong)
.scaleEffect(isRecognizingSong ? 0.8 : 1)
.animation(songRecognitionAnimation(), value: isRecognizingSong)
Spacer()
// 3
Button(action: { viewModel.stopRecognition() }) {
Image(systemName: ButtonImageType.stopRecognition)
.imageButton(with: size, color: .red)
}
}
.padding(.horizontal, 24)
}
// 4
private func songRecognitionAnimation() -> Animation {
isRecognizingSong ? .easeInOut(duration: 1.5).repeatForever() : .default
}
}
Что делает данный код:
- Добавляем список медиа элементов в библиотеку Shazam. Если вы откроете центр управления и долго нажмете на иконку Shazam, вы найдете распознанные песни с упоминанием названия приложения.
- Запускаем распознавание аудио нажатием на кнопку Shazam.
- Останавливаем распознавание аудио нажатием на кнопку Stop.
- Анимируем кнопку с помощью простой анимации движения, пока она распознает песни.
Завершая процесс проектирования, мы добавляем ShazamMusicRow в список и HomeButtonsView под ним.
struct ContentView: View {
@StateObject private var viewModel = HomeViewModel()
@Environment(\.openURL) var openURL
var body: some View {
NavigationView {
VStack {
List {
ForEach(viewModel.mediaItems, id: \.shazamID) { item in
Button(action: { openAppleMusic(with: item.appleMusicURL) }) {
ShazamMusicRow(item: item)
}
.buttonStyle(.plain)
.listRowSeparator(.hidden)
}
}
.listStyle(.plain)
HomeButtonsView(viewModel: viewModel)
}
.navigationTitle("Musadora")
}
.navigationViewStyle(.stack)
}
private func openAppleMusic(with url: URL?) {
if let url = url {
openURL(url)
}
}
}
Мы перебираем медиа-элементы, и при нажатии на отдельную строку открываем эту конкретную песню в Apple Music.
Теперь мы готовы Shazamить всё! Запустите приложение и наслаждайтесь своим собственным приложением с плейлистом Shazam!
Распознавание фона
В данный момент приложение распознает музыку только когда она играет на переднем плане. Представьте, что вы находитесь на вечеринке и хотите создать плэйлист из классных песен. Сделав наше приложение более эффективным, мы включим фоновый режим, чтобы вы могли держать телефон в кармане и наслаждаться вечеринкой!
Выберите в Targets Musadora, выберите Signing & Capabilities и нажмите кнопку Capability. Найдите Background Modes (фоновые режимы) и добавьте их. В разделе Background Modes установите флажок напротив Audio, Airplay, and Picture in Picture. Это поможет нам записывать и распознавать аудио в фоновом режиме.
Запустите приложение, нажмите кнопку Shazam и заблокируйте iPhone. Воспроизведите музыку и перепроверьте приложение. Вы найдете распознанную песню в списке!
Вывод
Shazam - это привлекательное приложение для почти мгновенного распознавания музыки. Благодаря Apple, дающей нам возможность экспериментировать, мы можем создавать творческие приложения или интегрировать их в существующие!
Надеюсь, вам понравился этот туториал. Давайте всё заShazamим!