Управление воспроизведением мультимедиа с помощью LockScreen и Центра управления в iOS с использованием Swift

Если вы когда-либо использовали iPhone или iPad для прослушивания музыки, вы, вероятно, видели экран «Сейчас исполняется». На этом экране отображаются название и исполнитель воспроизводимой в данный момент песни, а также элементы управления для приостановки, пропуска и регулировки громкости. Но в Now Playing есть нечто большее, чем просто это — на самом деле это мощный инструмент для управления воспроизведением музыки, а также важная часть экосистемы iOS.

В этом посте мы более подробно рассмотрим Now Playing в iOS, изучим его функции и возможности, а также некоторые способы его использования в ваших приложениях.

Основные сведения о программе "Исполняется"

Начнем с основ. Когда вы воспроизводите музыку на своем iPhone или iPad, вы можете получить доступ к экрану «Сейчас исполняется», проведя пальцем вниз от правого верхнего угла экрана, чтобы получить доступ к Центру управления, который включает виджет «Сейчас исполняется», показывающий текущую дорожку. Кроме того, вы можете увидеть экран NowPlaying на экране блокировки.

Хорошо, теперь мы понимаем, что сейчас играет, как нам его реализовать? Что ж, мы можем выполнить следующие шаги, чтобы реализовать это в ваших собственных приложениях.

  1. Настройка AvAudioSession.
  2. Включение фоновых режимов для звука.
  3. Настройка удаленных команд.
  4. Настройка элементов управления NowPlaying и отображение информации.
  5. Обработка ошибок и обработка прерываний.

Настройка AvAudioSession

Я не буду вдаваться в подробности настройки аудиосессии, вы можете обратиться к этой документации, она очень хорошо объясняет AVAudioSession.

// Get the singleton instance.
    let audioSession = AVAudioSession.sharedInstance()
    do {
        // Set the audio session category, mode, and options.
        try audioSession.setCategory(.playback, mode: .moviePlayback, options: [])
        try audioSession.setActive(true)
    } catch {
        print("Failed to set audio session category.")
    }

Включение фоновых режимов для аудио

Выберите цель вашего приложения в Xcode и перейдите на вкладку «Возможности». На вкладке «Возможности» установите переключатель «Фоновые режимы» в положение «Вкл.» и выберите параметр «Аудио, AirPlay и картинка в картинке» в списке доступных режимов.

Настройка удаленных команд

Каждая удаленная команда представляет собой MPRemoteCommand из платформы Media Player, которая отвечает на события удаленной команды.

Примеры MPRemoteCommand — playCommand, pauseCommand, nextTrackCommand и т. д. (Справочник)

Теперь нам нужно зарегистрироваться для различных удаленных команд и соответствующим образом реагировать на каждую команду. Для этого мы можем использовать общий экземпляр MPRemoteCommandCenter и обрабатывать события.

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

Что-то вроде этого ..

enum NowPlayableCommand: CaseIterable {
    
    case play, pause, togglePlayPause,
         nextTrack, previousTrack,
         changePlaybackRate, changePlaybackPosition,
         skipForward, skipBackward,
         seekForward, seekBackward
}

// MARK:- MPRemoteCommand

extension NowPlayableCommand {
  var remoteCommand: MPRemoteCommand {
        
        let commandCenter = MPRemoteCommandCenter.shared()
        
        switch self {
        case .play:
            return commandCenter.playCommand
        case .pause:
            return commandCenter.pauseCommand
        case .togglePlayPause:
            return commandCenter.togglePlayPauseCommand
        case .nextTrack:
            return commandCenter.nextTrackCommand
        case .previousTrack:
            return commandCenter.previousTrackCommand
        case .changePlaybackRate:
            return commandCenter.changePlaybackRateCommand
        case .changePlaybackPosition:
            return commandCenter.changePlaybackPositionCommand
        case .skipForward:
            return commandCenter.skipForwardCommand
        case .skipBackward:
            return commandCenter.skipBackwardCommand
    
        case .seekForward:
            return commandCenter.seekForwardCommand
        case .seekBackward:
            return commandCenter.seekBackwardCommand
        }
    }
    // Adding Handler and accepting an escaping closure for event handling for a praticular remote command
    func addHandler(remoteCommandHandler: @escaping  (NowPlayableCommand, MPRemoteCommandEvent)->(MPRemoteCommandHandlerStatus)) {
        
        switch self {
        case .skipBackward:
            MPRemoteCommandCenter.shared().skipBackwardCommand.preferredIntervals = [10.0]
            
        case .skipForward:
            MPRemoteCommandCenter.shared().skipForwardCommand.preferredIntervals = [10.0]
            
        default:
            break
        }
        self.remoteCommand.addTarget { event in
            remoteCommandHandler(self,event)
        }
    }
}

Настройка элементов управления NowPlaying и отображение информации

Теперь мы создадим протокол, которому может соответствовать наш класс видеоплеера, что-то вроде этого.

protocol NowPlayable {
    var supportedNowPlayableCommands: [NowPlayableCommand] { get }
    
    func configureRemoteCommands(remoteCommandHandler: @escaping  (NowPlayableCommand, MPRemoteCommandEvent)->(MPRemoteCommandHandlerStatus))
    func handleRemoteCommand(for type: NowPlayableCommand, with event: MPRemoteCommandEvent)-> MPRemoteCommandHandlerStatus
    
    func handleInterruption(for type: NowPlayableInterruption)
    
    func handleNowPlayingItemChange()
    func handleNowPlayingItemPlaybackChange()
    
    func addNowPlayingObservers()
    
    func setNowPlayingInfo(with metadata: NowPlayableStaticMetadata)
    func setNowPlayingPlaybackInfo(with metadata: NowPlayableDynamicMetaData)
    
    func resetNowPlaying()
}

Поддерживаемые воспроизводимые команды — это удаленные команды, которые вы хотите отображать в окне «Сейчас воспроизводится», и которые могут реагировать на действия.

Функция setNowPlayingInfo используется для установки информации о текущем воспроизведении.
Здесь мы используем общий экземпляр MPNowPlayingInfoCenter и передаем словарь.

func setNowPlayingInfo(with metadata: NowPlayableStaticMetadata) {
        let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
        
        var nowPlayingInfo = [String:Any]()
        
        nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = metadata.isLive
        nowPlayingInfo[MPMediaItemPropertyTitle] = metadata.title
        nowPlayingInfo[MPMediaItemPropertyArtist] = metadata.artist
        nowPlayingInfo[MPMediaItemPropertyArtwork] = metadata.artworkImage
        nowPlayingInfo[MPMediaItemPropertyAlbumArtist] = metadata.albumArtist
        nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata.albumTitle
        
        nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
    }

Общий экземпляр MPNowPlayingInfoCenter имеет словарь nowPlayingInfo. Мы можем создать и заполнить метаданными временный словарь и установить словарь nowPlayingInfo из этого временного файла.

Здесь я устанавливаю статические метаданные, такие как обложка, заголовок и т. д. Для настройки динамических метаданных, таких как рейтинг игрока, позиция поиска и т. д., мы можем использовать другие ключи словаря. Дополнительные ключи см. здесь.

Мы можем вызывать setNowPlayingInfo всякий раз, когда воспроизводится новый элемент воспроизведения.
И мы также увидим, что нам нужно вызывать setNowPlayingInfo несколько раз для синхронизации метаданных динамического проигрывателя, таких как скорость игрока, позиция поиска и т. д., с информацией nowPlaying.

Теперь функция configureRemoteCommands() позволяет нам регистрироваться для удаленных команд и может быть реализована примерно так.

func configureRemoteCommands(remoteCommandHandler: @escaping  (NowPlayableCommand, MPRemoteCommandEvent)->(MPRemoteCommandHandlerStatus)) {
        
        guard supportedNowPlayableCommands.count > 1 else {
            assertionFailure("Fatal error, atleast one remote command needs to be registered")
            return
        }
        
        supportedNowPlayableCommands.forEach { nowPlayableCommand in
            nowPlayableCommand.removeHandler()
            nowPlayableCommand.addHandler(remoteCommandHandler: remoteCommandHandler)
        }
        
    }

Я использую экранирование закрытия, чтобы передать управление или обработку событий удаленной команды классу видеоплеера, где ваш класс видеоплеера соответствует NowPlaying. Таким образом, у нас есть чистый код и определенные обязанности.

В классе видеоплеера, где он соответствует протоколу NowPlayable, мы можем реализовать

func handleRemoteCommand(for type: NowPlayableCommand,
                             with event: MPRemoteCommandEvent)

Здесь мы можем сравнить, какой тип NowPlayableCommand существует, и на основе этого мы можем реагировать на изменения игрока. Например,

switch type {
        case .play :
            player.resume()
            
        case .pause:
            player.pause()

Обратите внимание, что нам не нужно вручную вызывать эту функцию. Поскольку мы зарегистрировались для удаленных команд (см. перечисление NowPlayableCommand), эта функция будет вызываться автоматически. Нам просто нужно реагировать на изменения от удаленных команд.

Наконец, наблюдайте за видеоплеером с помощью наблюдателей, а затем при каждом изменении события плеера (например, поиске, паузе и т. д.) нам нужно вызвать setNowPlayingInfo (с динамическими метаданными), который в дальнейшем создаст словарь и передаст его в общий экземпляр MPNowPlayingInfoCenter NowPlaying словарь. (См. выше реализацию функции)

Заключительные примечания

Now Playing — мощный инструмент в iOS, предоставляющий пользователям удобный способ управления воспроизведением музыки. Интегрировав Now Playing в свое приложение, вы можете предоставить своим пользователям более плавное и интегрированное воспроизведение музыки. Именно это я и сделал, когда работал над приложением Cricbuzz, которое скачали более 100 миллионов. Добавив функцию «Сейчас исполняется», мы предоставили пользователям больше возможностей управления с экрана блокировки, и отзывы, которые мы получили, были исключительно положительными. Пользователям понравилась возможность беспрепятственно управлять воспроизведением музыки, не отставая от последних результатов и новостей крикета.

Если вы работаете над приложением, которое включает воспроизведение музыки, или просто хотите улучшить взаимодействие с пользователем, рассмотрите возможность интеграции Now Playing в свое приложение. Это мощный инструмент, который может существенно повлиять на то, как ваши пользователи взаимодействуют с вашим приложением.

Ссылки, чтобы помочь дальше:

  1. https://medium.com/@varundudeja/showing-media-player-system-controls-on-notification-screen-in-ios-swift-4e27fbf73575
  2. https://developer.apple.com/documentation/mediaplayer/mpnowplayinginfocenter

Надеюсь, вы найдете это полезным. Если вам это понравилось или у вас есть отзывы или дополнительные вопросы, пожалуйста, прокомментируйте ниже или свяжитесь со мной по Linkedin.

Спасибо за чтение!