B035c8e46b434111b279f612ac80db97
用 SiriKit 播放你的 App 内容: SiriKit Media Intents

WWDC 2019 Session 207: Introducting SiriKit Media Intent

引言

新的 SiriKit Media Intents 支持用户用自然语言来告诉 Siri 进行播放相关的操作,包括了播放指定歌曲、将歌曲加入收藏、喜欢某首歌曲等等。举个例子,用户说「在 Apple Music 中播放周杰伦的歌曲」,Siri 即可在后台唤起 Apple Music,然后在 Siri 中显示一个播放器,直接开始播放歌曲。这个新功能减少了用户的操作路径,让用户的播放流程更自然,对音频类的 App 有较大的价值(Apple Music 在 iOS 12 上就支持了该功能,大家可以用下面介绍的短语体验)。本文在 Session 内容的基础上加入了作者的一些实践结果。

Apple Music 中对 Media Intent 的支持

Apple Music 中对 Media Intent 的支持

Session 207 分为三个部分:

  1. 介绍新的 SiriKit Media Intents
  2. 处理 SiriKit Media Request
  3. 最佳实践

1. 新的 SiriKit Media Intents:

  • INPlayMediaIntent
    可以让用户播放一些音频,触发短语是「在 < 我的 App > 中播放 < 歌曲 >」,比如「在 Apple Music 中播放夜曲」。也支持包括倍速播放、随机播放、上一首下一首等功能。
  • INAddMediaIntent
    将音频加入播放列表,比如「将这首歌加入我的播放列表」。
  • INUpdateMediaAffinityIntent
    用户可以标记音频为喜欢或不喜欢,帮助 App 进行个性化推荐,比如「我喜欢这首歌」。
  • INSearchForMediaIntent
    用户可以搜索指定的音频节目,触发短语是「在 < 我的 App > 中搜索 < 歌曲 >」,比如「在 Apple Music 中搜索邓丽君的歌曲」。

支持的5种音频类型

  • 音乐 Music
    「在 < 我的 App > 中播放 < 歌曲 >」。支持的类型也包括专辑、歌手、播放列表、歌曲类型等。在 INMediaItemType 中有详细的类型支持列表,详情可以查看文档.
    同时,该 Intent 也支持播放控制,如倍速播放、随机播放、上下首等。
  • 播客 Podcasts
    「在 < 我的 App > 中播放播客 Stuff You Should Know」。同样支持播放控制。
  • 有声书 Audiobooks
    「在 < 我的 App > 中播放有声书 Becoming」。支持倍速播放。
  • 电台 Radio
    「在 < 我的 App > 中播放 89.1FM」。
  • 通用类型 General
    如果你的 App 不支持以上几种类型的音频,SiriKit 也支持一些通用的识别。「在 < 我的 App > 中播放 < 节目 >」,Siri 依然可以识别出 < 节目 >,并提供该字段的信息给你的 App,只是该信息就不会带有 INMediaSearch 中列举出类型的信息了。

作者注:在上述短语中有一点需要特别注意,如果短语不含有特定的音频类型的词,比如「歌曲」、「播客」、「有声书」,Siri 也无法为识别出来的内容加上类型信息。所以很大程度上需要开发者自己对 Siri 提供的信息进行再次处理,以提供准确的内容。各种语言的 Siri 都会有这种情况出现。期待 Siri 有更充足的语料库来进行准确识别。除此之外,App 的名字也是必须的。用户必须说「在 < App > 中播放」,Siri 才会把 Intent 交给这个 App 处理,否则 Siri 一般会调起 Apple Music。

2. 处理 SiriKit Media Request

处理 SiriKit Media Request 的过程与通常的 SiriKit 处理过程类似,所有的请求处理都在 Intent App Extension 中完成。

当用户说「在我的App中播放一首好歌」时,处理过程就开始了。 Siri 会识别出这个 Intent,并启动你的 Intents App Extention。处理过程共分三步:Resolve、Confirm、Handle。

1. Resolve

在播放的场景中,我们会从参数 intent 中获取 INMediaSearch 对象,我们使用该对象的信息在 App 中进行搜索。获取一个或多个可以用于播放的 INMediaItem。如果找不到或者产生了错误,可以返回 INPlayMediaMediaItemResolutionResult.unsupported(),这会让 Siri 显示一个合适的错误提示。

func resolveMediaItems(for intent: INPlayMediaIntent, with completion: @escaping
([INPlayMediaMediaItemResolutionResult]) -> Void) {
  var result = INPlayMediaMediaItemResolutionResult.unsupported() // 初始化为 unsupported
  if let mediaName = intent.mediaSearch?.mediaName { // 取出 mediaName 用于之后的查找
    for item in mediaItemsFromMyAppCatalog(intent) where item.name == mediaName {
      let mediaItem = INMediaItem(identifier: item.id, title: item.name, type:
        item.type, artwork: nil, artist: item.artist) // 找到了对应的 mediaItem
      result = INPlayMediaMediaItemResolutionResult.success(with: mediaItem) // 用找到的 mediaItem 生成一个 succes 的 result
      break
    }
  }
  completion([result])
}

这里的 .unsupported() 方法是只会简单的返回不支持,如果需要返回详细的原因,可以使用 unsupported(forReason:),reason 为 INPlayMediaMediaItemUnsupportedReason 类型。

public enum INPlayMediaMediaItemUnsupportedReason : Int {
    case loginRequired
    case subscriptionRequired
    case unsupportedMediaType
    case explicitContentSettings
    case cellularDataSettings
}

在不同 Intent 的 Resolve 方法中,虽然参数有点不同,但是过程是相同的。INMediaSearch 对象中包含了用户需要播放的节目的所有信息,我们需要利用这些信息来找到用户希望播放的节目。首先需要从 INMediaSearch 中取得 MediaName,然后从 App 中找到所有可能的 MediaItem,并根据 MediaName 筛选出用户需要的那一个。如果找到 MediaItem,就可以用它来创建一个 Success Result,作为参数传入 completion

2. Confirm

在播放的场景中,不提倡使用 confirm 这一步。在对 Apple 自己 App 的回顾中发现,如果播放时需要用户确认,会降低用户继续进行播放的可能性。

3. Handle

对于 INPlayMediaIntent 来说,这一步比较简单,我们只需返回 .handleInApp,这会使 App 在后台启动。在 App 后台启动过程中,我们播放对应的音频。

func handle(intent: INPlayMediaIntent, completion: (INPlayMediaIntentResponse) -> Void) {
  completion(INPlayMediaIntentResponse(code: .handleInApp, userActivity: nil)) // 传入 handleInApp,告诉 Siri 在后台启动 App
}

AppDelegateapplication:handle:completionHandler: 方法中,取出之前的 MediaItem,开始播放。

func application(_ application: UIApplication, handle intent: INIntent, completionHandler: @escaping (INIntentResponse) -> Void) {
  if let playMediaIntent = intent as? INPlayMediaIntent {
    if let mediaItems = playMediaIntent.mediaItems { // 取出之前放到 Result 中的 MediaItem
      let mediaItemToPlay = mediaItems.first
      // 一些初始化工作,让 App 开始播放节目
      beginPlayback(mediaItemToPlay)
      completionHandler(INPlayMediaIntentResponse(code: .success, userActivity: nil))
    }
  }
}

这里比较有技巧的部分是测试,你需要保证音频正确播放,然而此时你还看不到对应的UI。你也需要确保测试是在正确的场景中进行的,比如在 CarPlay 中。

Demo 演示:

Demo 代码可以从这里下载,这个页面上也包含了详细的配置信息。

如果需要手动添加对新的 Intent 的支持,可以按下面的步骤来。

  1. 将 Intents Extension 加入 App 中:
    前往 File - New - Target,选择 Intent Extension:
    创建 Intent Extension

    创建 Intent Extension

  2. 在 App 中添加对 Siri 的支持:

  3. 配置支持的 Media Intent:

  4. 确保文件加入 Extension 的编译过程:

  5. 创建 Intent Handler 文件,实现 Resolve 和 Handle 方法:

  6. AppDelegate 中实现 application:handle:completionHandler:

注意点:如果在配置支持的 Media Categories 时没有参考 INMediaItemType 中列出的 Topic 来选,就算说的短语中带上类型词如「歌曲」、「专辑」,Siri 也不会识别出对应的类型。举个例子,如果没有勾选 Music,那么就算说「在 < 我的 App > 中播放歌曲 < 夜曲 >」,INMediaSearch 对象中带着的 INMediaSearchType 也依然是 .unknown 而不是 .song

3. 最佳实践

在支持 shortcuts 的情况下增加对新 Intents 的支持

如果你的 App 在 iOS 12 中就已经支持了 shortcuts,支持新功能将会比较简单。

首先,Handle 方法的实现和在后台启动 App 的代码是相同的,这里无需添加额外代码。
需要实现的是 Resolve 方法。
最后,为你的 App 支持的媒体类型更新 Intents Extension。

代码中需要增加的只有 resolve 方法的实现

代码中需要增加的只有 resolve 方法的实现

对 Apple Watch 的支持

由于 Watch 上的 App 需要在前台启动,因此与在 iOS 上不同的是,需要返回的 INPlayMediaIntentResponseCode.continueInApp,之后的处理会在 WKExtensionDelegate 完成。这部分代码与在 AppDelegate 的中类似,取出 Intent 中的 MediaItem 然后开始播放即可。

不同的是由于网络在 Watch 上是比较珍贵的资源,这里推荐尽量使用缓存来处理,只有在必要的时候才进行网络请求。

在 Resolve 方法中进行高效的查找

当用户说「在 < 我的App > 中播放 < 歌名 >」时,一般我们会用这歌名去进行一些匹配搜索的操作。这里需要考虑一些匹配上的问题:

top Created with Sketch.