WWDC Quick Look 💓 By SwiftGGTeam
Integrate your media app with HomePod

Integrate your media app with HomePod

观看原视频

Highlight

iOS 17 让 HomePod 用户可以直接通过 Siri 播放 iPhone 或 iPad 上安装的媒体应用内容,已有 SiriKit Media Intents 支持的应用无需任何代码改动即可使用,系统通过 Wi-Fi 将请求路由到用户设备,应用启动后通过 AirPlay 将内容回传到 HomePod。

核心内容

工作原理

用户向 HomePod 发出播放请求时,流程如下:

  1. HomePod 处理语音请求,生成 SiriKit Intent
  2. Intent 通过 Wi-Fi 发送到用户的 iPhone(主设备)
  3. iPhone 启动应用,应用处理播放请求
  4. 音频通过 AirPlay 回传到 HomePod

设备需要在同一 Wi-Fi 网络,但不需要物理 proximity。用户在客厅对 HomePod 说话,手机在卧室充电也能正常工作。

00:50

支持的媒体类型和请求

支持的媒体类型包括音乐、有声读物、播客、电台、冥想等。Play、Add 和 Affinity(喜爱/添加到播放列表)请求都会路由到主设备。

请求示例:

  • “Play jazz on Control Audio” — 播放类型 + 应用名
  • “Play Push It by Dukes on Control Audio” — 歌曲名 + 应用名
  • “Play my audiobook” — 恢复上次播放
  • “Play the latest news” — 播客/新闻类请求
  • “Add this to my playlist” — 添加到播放列表
  • “I like this song” — 标记喜爱

02:15

实现 SiriKit Media Intents

import Intents

class PlayMediaIntentHandler: NSObject, INPlayMediaIntentHandling {
    func handle(intent: INPlayMediaIntent, completion: @escaping (INPlayMediaIntentResponse) -> Void) {
        // 检查请求的具体内容
        if let mediaName = intent.mediaSearch?.mediaName,
           mediaName == "Push It" {
            let mediaItem = INMediaItem(
                identifier: "song-push-it",
                title: "Push It",
                type: .song,
                artwork: nil
            )
            let response = INPlayMediaIntentResponse(
                code: .success,
                userActivity: nil
            )
            response.mediaItems = [mediaItem]
            completion(response)
            return
        }
        
        // 检查是否是不支持的媒体类型
        if intent.mediaSearch?.mediaType == .music {
            let response = INPlayMediaIntentResponse(
                code: .failureRequiringAppLaunch,
                userActivity: nil
            )
            completion(response)
            return
        }
        
        // 默认:未找到内容
        completion(INPlayMediaIntentResponse(code: .failure, userActivity: nil))
    }
}

关键点:

  • 已有 SiriKit Media Intents 支持的应用无需任何改动
  • 新应用需要添加 Intents Extension,实现 INPlayMediaIntentHandling
  • 返回具体的错误原因(如不支持的媒体类型、需要登录),Siri 会给出更有帮助的回复

04:11

响应方式选择

import Intents

func handle(intent: INPlayMediaIntent, completion: @escaping (INPlayMediaIntentResponse) -> Void) {
    // 方式一:后台播放(推荐用于音频)
    let backgroundResponse = INPlayMediaIntentResponse(
        code: .handleInApp,
        userActivity: nil
    )
    
    // 方式二:前台启动
    let foregroundResponse = INPlayMediaIntentResponse(
        code: .continueInApp,
        userActivity: nil
    )
    
    // 音频播放推荐用 handleInApp,不需要解锁手机
    completion(backgroundResponse)
}

关键点:

  • handleInApp 在后台启动应用播放,用户不需要解锁手机
  • continueInApp 将应用带到前台
  • HomePod 场景下后台播放体验更好,因为手机可能不在手边

07:10

详细内容

音频会话配置

正确的音频会话配置是后台播放的基础。

import AVFoundation

func configureAudioSession() {
    let session = AVAudioSession.sharedInstance()
    
    do {
        // 设置为播放类别,支持后台播放
        try session.setCategory(.playback, mode: .default)
        
        // 播客/有声读物推荐用 spokenAudio 模式
        // 被打断时暂停而非降低音量
        try session.setCategory(.playback, mode: .spokenAudio)
        
        // 激活会话
        try session.setActive(true)
    } catch {
        print("Failed to configure audio session: \(error)")
    }
}

关键点:

  • 必须在激活会话前设置 category 为 .playback
  • .spokenAudio 模式在被打断时暂停播放,适合播客和有声读物
  • 未正确配置时,应用进入后台(如锁屏)后播放会停止

08:55

AirPlay 集成

import MediaPlayer

func setupNowPlayingAndRemoteCommands() {
    // 设置当前播放信息
    var nowPlayingInfo: [String: Any] = [
        MPMediaItemPropertyTitle: "Push It",
        MPMediaItemPropertyArtist: "Dukes",
        MPNowPlayingInfoPropertyPlaybackRate: 1.0
    ]
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    
    // 接收远程控制命令
    let commandCenter = MPRemoteCommandCenter.shared()
    
    commandCenter.playCommand.addTarget { event in
        self.resumePlayback()
        return .success
    }
    
    commandCenter.pauseCommand.addTarget { event in
        self.pausePlayback()
        return .success
    }
    
    commandCenter.nextTrackCommand.addTarget { event in
        self.skipToNext()
        return .success
    }
    
    commandCenter.previousTrackCommand.addTarget { event in
        self.skipToPrevious()
        return .success
    }
}

关键点:

  • MPNowPlayingInfoCenter 向系统报告当前播放内容
  • MPRemoteCommandCenter 接收播放/暂停/跳过等远程命令
  • 这是 AirPlay 正常工作的必要条件

10:52

处理播放中断

import AVFoundation

class AudioPlayerManager: NSObject {
    override init() {
        super.init()
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleInterruption),
            name: AVAudioSession.interruptionNotification,
            object: nil
        )
    }
    
    @objc func handleInterruption(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
              let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
              let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
            return
        }
        
        switch type {
        case .began:
            // 暂停播放
            pausePlayback()
        case .ended:
            if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
                let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
                if options.contains(.shouldResume) {
                    // 恢复播放
                    resumePlayback()
                }
            }
        @unknown default:
            break
        }
    }
}

关键点:

  • 监听 AVAudioSession.interruptionNotification
  • 来电、导航提示、Siri 请求等都会触发中断
  • 中断结束后根据 shouldResume 选项决定是否恢复播放

10:28

请求路由机制

Siri 如何确定使用哪台设备:

  • 请求路由到主 iPhone(在 Apple ID 和 Find My 设置中共享位置的设备)
  • 请求用户需要在 Home 中注册并启用”识别我的声音”
  • 不需要启用个人请求
  • 首次使用应用时,Siri 会请求权限访问应用数据

11:46

个人应用词汇表

import Intents

func donatePersonalVocabulary() {
    let intent = INAddMediaIntent(
        mediaSearch: INMediaItem(
            identifier: "playlist-workout",
            title: "Workout Mix",
            type: .playlist,
            artwork: nil
        )
    )
    
    // 向系统捐赠个人词汇
    let interaction = INInteraction(intent: intent, response: nil)
    interaction.donate { error in
        if let error = error {
            print("Donation failed: \(error)")
        }
    }
}

关键点:

  • 使用个人应用词汇表告知系统用户特定的实体
  • 包括个人播放列表、购买的有声读物、收藏的播客等
  • 提升 Siri 对个性化请求的识别率

08:30

核心启发

  • 做什么:为媒体应用添加 SiriKit Media Intents 支持

    • 为什么值得做:无需额外代码即可让应用在 HomePod 上可用,覆盖全新的使用场景
    • 怎么开始:添加 Intents Extension,实现 INPlayMediaIntentHandling,处理 mediaNamemediaType 字段
  • 做什么:优化 AirPlay 音频体验

    • 为什么值得做:HomePod 场景完全依赖 AirPlay,缓冲和延迟直接影响用户体验
    • 怎么开始:配置 AVAudioSession.playback 类别,设置 MPNowPlayingInfoCenterMPRemoteCommandCenter,使用缓冲播放 API 消除恢复延迟
  • 做什么:实现具体的错误反馈

    • 为什么值得做:模糊的”找不到内容”会让用户困惑,具体的错误提示能帮助用户解决问题
    • 怎么开始:对不支持的媒体类型返回 .failureRequiringAppLaunch,在应用内给出具体原因(如”不支持播放流派”、“需要登录”)
  • 做什么:为播客和有声读物优化音频会话

    • 为什么值得做.spokenAudio 模式在中断时暂停而非降低音量,确保用户不会错过内容
    • 怎么开始:将 AVAudioSession 的 mode 设置为 .spokenAudio,处理 interruptionNotification 在中断结束后恢复播放
  • 做什么:捐赠个人词汇提升识别率

    • 为什么值得做:用户创建的播放列表、收藏的内容名称是通用模型无法识别的
    • 怎么开始:用 INInteraction 捐赠用户特定的媒体项,让 Siri 了解用户的个性化内容

关联 Session

评论

GitHub Issues · utterances