面向Apple developer学习:AirPlay | Apple Developer Documentation
Airplay
AirPlay允许人们将媒体内容从iOS、ipad、macOS和tvOS设备无线传输到支持AirPlay的Apple TV、HomePod以及电视和扬声器上。
网页链接的最佳实践
首选系统提供的媒体播放器。内置的媒体播放器提供了一套标准的控件,并支持章节导航、字幕、封闭字幕和AirPlay流媒体等功能。它也很容易实现,在整个系统中提供一致和熟悉的播放体验,并适应大多数媒体应用程序的需求。只有当系统提供的播放器不能满足你的应用需求时,才考虑设计一个自定义视频播放器。有关开发人员指导,请参见AVPlayerViewController。
以尽可能高的分辨率提供内容。您的HTTP Live Streaming (HLS)播放列表需要包含所有可用的分辨率,以便人们可以在适合他们使用的设备的分辨率下体验您的内容(AVFoundation会根据设备自动选择分辨率)。如果你没有包含一系列分辨率,那么当人们将你的内容流到可以以更高分辨率播放的设备上时,你的内容看起来质量很低。例如,在720p的iPhone上看起来很棒的内容,当人们使用AirPlay将其流式传输到4K电视时,就会显得质量很低。
只播放人们期待的内容。避免流媒体内容,如背景循环和短视频体验,只有在应用程序本身的上下文中才有意义。开发者指南请参见usesExternalPlaybackWhileExternalScreenIsActive。
支持AirPlay流媒体和镜像。同时支持这两个功能可以给用户带来最大的灵活性。
支持远程控制事件。当你这样做时,人们可以在锁定屏幕上选择播放、暂停和快进等动作,也可以通过与Siri或HomePod的互动来选择。有关开发人员指导,请参见远程命令中心事件。
当你的应用进入后台或设备锁定时,不要停止播放。例如,人们希望他们在查看邮件或让设备进入睡眠状态时,从你的应用开始播放的电视节目能够继续播放。在这种情况下,避免自动镜像也很重要,因为人们不想在没有明确选择的情况下在他们的设备上播放其他内容。
不要打断其他应用的播放,除非你的应用开始播放沉浸式内容。例如,如果你的应用程序在启动时播放视频或自动播放内联视频,只在本地设备上播放该内容,同时允许当前播放继续。有关开发人员指导,请参见ambient。
让人们在播放时使用应用的其他部分。当AirPlay处于活动状态时,你的应用需要保持功能。如果用户离开了播放屏幕,确保应用内的其他视频不会开始播放并打断流媒体内容。
如有必要,提供用于控制媒体播放的自定义接口。如果你不能使用系统提供的媒体播放器,你可以创建一个自定义的媒体播放器,让人们以一种直观的方式进入AirPlay。如果需要这样做,请确保提供与系统提供的按钮的外观和行为相匹配的自定义按钮,包括指示回放何时开始、正在发生或不可用的不同视觉状态。在启动AirPlay的自定义控件中,只使用苹果提供的符号,并将AirPlay图标正确地放置在自定义播放器中——即右下角(在iOS 16和iPadOS 16及更高版本中)。
在你的应用中添加支持AirPlay
AirPlay使您能够将内容从Apple设备无线发送到Apple TV或支持AirPlay的扬声器。AirPlay为无线音频分发提供了增强的支持,包括向多个启用AirPlay的扬声器发送内容的能力。
在页面链接中识别应用程序播放的音频类型
在iOS、tvOS和watchOS中,将音频会话的路由共享策略设置为. longform。长格式音频是除了系统声音之外的任何东西,比如音乐、有声书或播客。此设置标识应用程序播放的音频,例如以下示例。
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback,
mode: .default,
policy: .longFormAudio)
添加一个AirPlay pickerin
将AVRoutePickerView添加到你的视图层次结构中,在你的应用程序中包含一个AirPlay选择器。选择器为用户提供了一个潜在的AirPlay设备列表,他们可以在你的应用程序中使用。要控制何时显示选择器,使用AVRouteDetector来识别路由检测器的状态。
iOS, iPadOS, Mac Catalyst, tvOS
class AVRoutePickerView : UIView
macOS
class AVRoutePickerView : NSView
当用户点击按钮时,系统会弹出一个窗口,显示附近所有可以接收和播放媒体的AirPlay设备。如果你的应用更喜欢视频内容,系统会在列表中显示支持视频的设备。
在页面链接中配置按钮的文本、颜色和媒体首选项
下面的代码示例在自定义文本旁边创建视图:
HStack {
Text("Choose output device")
.font(.title)
.frame(maxWidth: .infinity, alignment: .center)
.fixedSize()
.padding(.leading)
if routeDetected {
DevicePickerView() // See implementation below.
.frame(width: 60, height: 60)
.padding(.trailing)
}
}
你的应用程序配置按钮的配色方案,并指示你的应用程序是否喜欢视频内容,如下面的代码所示:
struct DevicePickerView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let routePickerView = AVRoutePickerView()
// Configure the button's color.
routePickerView.delegate = context.coordinator
routePickerView.backgroundColor = UIColor.white
routePickerView.tintColor = UIColor.black
// Indicate whether your app prefers video content.
routePickerView.prioritizesVideoDevices = true
return routePickerView
添加媒体播放器
使用api自定义您的AirPlay采用与媒体播放器集成。使用MPRemoteCommandCenter可以接收远程命令。如果你使用MPNowPlayingInfoCenter,你可以通知系统元数据关于在设备上播放的音轨。
配置一个快速流媒体页面链接的应用
采用两种利用AirPlay增强缓冲的播放API集之一:
对于简单的增强缓冲,使用AVPlayer或AVQueuePlayer。这对于视频内容非常有效。有关更多信息,请参见为内容实现简单的增强缓冲。
为了更灵活地增强缓冲,使用AVSampleBufferAudioRenderer和AVSampleBufferRenderSynchronizer。这个选项对于那些需要控制I/O,对媒体数据进行预处理,或者有AVPlayer不支持的DRM模型的应用来说更好。有关更多信息,请参见为内容实现灵活的增强缓冲。
HomeKit
link
HomeKit允许人们使用Siri或iPhone、iPad、Apple Watch和Mac上的Home应用程序安全地控制家中连接的配件。
在iOS系统中,Home应用程序还允许用户管理和配置配件。
您的iOS、tvOS或watchOS应用程序可以与HomeKit(以及扩展的Home应用程序)集成,以提供自定义或配件特定的体验。例如,您可以:
帮助人们设置,命名和组织他们的配件
允许细粒度的附件配置和控制
提供对定制配件功能的访问
向人们展示如何创建强大的,免提的自动化
提供支持
有关开发人员指导,请参见HomeKit。如果您是MFi被许可人,请访问MFi门户网站,获取有关附件包装的命名和消息传递的指导。
术语和布局页面链接
HomeKit将家庭建模为对象的层次结构,并定义了引用它们的术语词汇表。Home应用程序使用HomeKit对象模型和术语,让人们通过语音、应用程序和自动化来直观地控制配件。
对于你的应用来说,使用HomeKit定义的术语和对象模型是至关重要的,这样你就可以加强人们的理解,让家庭自动化变得平易近人。
在HomeKit模型中,home对象是包含所有其他对象(如房间、配件和区域)的层次结构的根。当有多个home时,每个home都是不同层次结构的根。
承认HomeKit使用的分层模型。即使你的应用程序没有在其UI中按房间和区域组织配件,在帮助人们设置或控制配件时引用HomeKit模型也是有用的。人们需要知道配件的位置,这样他们才能使用Siri和HomePod来控制它们,说出“Siri,打开楼上的灯”或“这里很黑”之类的命令。有关更多指导,
让人们更容易找到配件的相关HomeKit细节。如果你的应用侧重于配件,不要在难以发现的设置屏幕中隐藏其他HomeKit信息,比如配件的区域或房间。相反,可以考虑让相关的HomeKit信息在附件详细信息视图中容易获得。
认识到人们可以拥有不止一个家。即使你的应用程序不支持每个用户多户的概念,也可以考虑在配件细节视图中提供相关的家庭信息。
不要呈现重复的家庭设置。如果你的应用程序有一个不同的角度来组织一个家,不要让他们重新设置他们的家的全部或部分,或者通过显示重复的设置视图来使人们感到困惑。始终遵循人们在Home应用程序中所做的设置,并找到一种直观的方式在你的UI中呈现这些细节。
主页链接
HomeKit使用“家”一词来表示物理上的家、办公室或其他与人们相关的地点。一个人可能有多套房子。
Rooms
房间代表家里的一个实体房间。房间没有大小或位置等属性;它们只是对人们有意义的名字,比如“卧室”或“办公室”。当人们为一个房间分配配件时,他们可以使用语音命令,比如“Siri,打开除卧室以外的所有灯”,或者“Siri,打开厨房和走廊的灯”。
附件,服务和特性页面链接
“配件”一词代表物理的、连接的家庭配件,如吊扇、灯、锁或相机。HomeKit使用category来表示一种配件类型,例如恒温器、风扇或灯。通常,配件制造商会将每个配件分配到一个类别,但你的应用可以在必要时帮助人们进行分配。例如,连接风扇或灯的开关需要与其控制的配件分配到同一类别。
配件的可控特性,如连接灯的开关,被称为服务。一些配件提供多种服务。例如,连接的车库门可以让人们单独控制灯和门,或者连接的插座可以支持单独控制顶部出口和底部出口。应用程序不会在UI中使用“服务”这个词;相反,他们使用的是描述服务的名称,比如车库门开启器和吊扇灯。当人们用Siri控制家里的配件时,他们说的是服务名称,而不是配件名称。有关命名的更多指导,请参见帮助人们选择有用的名称。
特征是服务的可控属性。例如,在吊扇中,风扇服务可能具有速度特性,而灯服务可能具有亮度特性。应用不会在UI中使用“特性”这个词;相反,他们使用描述属性的术语,如速度和亮度。
服务组表示一组附属服务,有人可能希望将其作为一个单元进行控制。例如,如果房间的一个角落里有一盏落地灯和两盏台灯,人们可能会将这三种服务分配给一个名为阅读灯的服务组。这样做可以让人们使用阅读灯服务组来独立控制这三盏灯,而不受房间里所有其他灯的影响。
动作和场景页面链接
“动作”一词指的是服务特性的改变,例如调整风扇的转速或灯光的亮度。人员和自动化可以启动操作。
场景是控制一个或多个附件中的一个或多个服务的一组操作。例如,人们可能会创建一个电影时间场景,降低客厅的阴影并使灯光变暗,或者一个早安场景,打开灯,提高阴影,并启动厨房的咖啡机。
提示
HomeKit API使用术语“动作集”而不是“场景”。在你的应用UI中,总是使用场景这个术语。
自动化页面链接
自动化使配件对特定情况做出反应,例如当一个人的位置发生变化时,一天中的特定时间发生时,另一个配件打开或关闭时,或者传感器检测到某些东西时。例如,一个自动化装置可以在日落或人们到家时打开家里的灯。
区域页面链接
一个区域代表家中包含多个房间的区域,例如楼上或楼下。设置一个区域是可选的,但这样做可以让人们一次控制多个配件。例如,将所有楼下的灯分配到一个名为楼下的区域,让人们可以使用诸如“Siri,关掉楼下所有的灯”这样的语音命令。
设置页面链接
使用系统提供的设置流程给人们一个熟悉的体验。HomeKit的设置流程比传统的设置流程更快,因为它可以让用户在几个步骤中命名配件、加入网络、与HomeKit配对、分配房间和服务类别,以及指定收藏夹。使用系统提供的设置流程还可以让你的应用专注于推广定制功能,使你的配件独一无二。对于开发人员的指导,请参见addAndSetupAccessories(with:completionHandler:)。
提供上下文来解释为什么需要访问人们的Home数据。创建一个目的字符串,用一个短语描述为什么你要求访问数据的许可,比如“让你通过苹果设备上的苹果家庭应用程序和Siri控制这个配件。”
不要要求人们创建帐户或提供个人信息。相反,请根据HomeKit获取您可能需要的任何信息。如果你的应用程序提供额外的服务,需要一个帐户,如云服务,设置为可选的帐户设置,并等到初始HomeKit设置后提供它。
尊重人们的设置选择。当人们选择使用HomeKit来设置你的配件时,不要强迫他们在HomeKit设置过程中设置其他平台。跨平台的设置体验会阻止人们立即使用配件,并可能因呈现太多控制配件的方法而造成混乱。
仔细考虑如何以及何时提供定制配件设置体验。总是从呈现系统提供的设置流程开始。然后,在配件的基本功能可用之后,提供一个定制的安装后体验,突出你的配件的独特功能,帮助人们最大限度地利用它。例如,一家灯具制造商的应用程序可以帮助人们在家中创建个性化的灯光场景,使用从他们图书馆的照片中扫描出来的关键颜色。
Siri互动页面链接
HomeKit支持使用语音命令进行强大的免提控制。你可以帮助人们使用Siri快速有效地与家中的配件、服务和区域进行交互。
提供语音命令示例,演示在设置过程中使用Siri控制配件。一旦人们完成了新配件的设置,就可以考虑在几个Siri短语示例中使用他们选择的服务名称,并鼓励人们试用。
设置好之后,可以考虑教人们更复杂的Siri命令。人们可能没有意识到他们可以使用Siri和HomePod来控制他们的配件的广泛的自然语言短语。设置完成后,在应用程序中找到有用的地方来帮助人们了解这些类型的命令。例如,在场景细节视图中,你可以告诉人们,你可以说“嘿Siri,设置‘电影时间’。”
除了识别房屋、房间、区域、服务和场景的名称外,Siri还可以使用配件类别和特征等信息来识别服务。例如,当人们使用“更亮”或“更暗”这样的词时,即使他们没有说出服务的名称,Siri也能识别出他们指的是具有亮度特征的服务。
HomeKit使您的应用程序能够协调和控制来自多个供应商的家庭自动化配件,以呈现连贯的,以用户为中心的界面。
这个示例展示了支持homekit的配件制造商可能提供的那种应用程序的简化版本。你使用这个应用程序来配置和控制一个特定的设备——一个来自虚构制造商Kilgo Devices的车库门开启器。该应用程序提供了苹果家庭应用程序不提供的次要和自定义功能。它提供了与Home应用程序的方法和术语一致的用户体验,但并不试图复制Home应用程序的每个功能。
配置示例代码投影页面链接
为了能够使用HomeKit,你需要启用HomeKit功能,并在应用的Info中包含NSHomeKitUsageDescription键。在这个示例应用中,已经启用了该功能,并提供了使用说明。
在构建和运行应用程序之前,执行以下步骤:
在目标的General窗格中设置一个有效的签名团队,这样Xcode就可以在你第一次构建时创建一个包含HomeKit授权的配置文件。
在Mac上下载并安装家用配件模拟器(HAS),以便能够模拟启用了homekit的配件。参见使用HomeKit配件模拟器测试你的应用。
导入样例应用捆绑的hasaccessory文件,以定义应用控制的特定车库开门器配件。从HAS菜单中选择File > Import Accessory。在出现的对话框中,导航到下载的示例代码项目的Documentation文件夹,并选择车库。hasaccessory文件。
导入创建了一个带有隐藏附件信息服务(所有附件都有)的单个附件,以及两个用户交互服务:一个控制车库门,另一个控制连接的灯泡。大多数相关特征都是其服务的标准。只有一项——灯泡的衰减率——是定制的。您可以使用HAS来检查和操作所有这些项目。
创建一个主页管理器并获取
你总是使用一个HMHomeManager的实例作为根HomeKit对象。家庭管理器包含一组家庭,每个家庭都有一组配件。示例应用程序定义了一个HomeStore类,作为一个单例使用,它为应用程序保存唯一的home管理器:
class HomeStore: NSObject {
/// A singleton that can be used anywhere in the app to access the home manager.
static var shared = HomeStore()
/// The one and only home manager that belongs to the home store singleton.
let homeManager = HMHomeManager()
/// A set of objects that want to receive home delegate callbacks.
var homeDelegates = Set<NSObject>()
/// A set of objects that want to receive accessory delegate callbacks.
var accessoryDelegates = Set<NSObject>()
}
您创建一个配件列表集合视图控制器来显示连接的配件列表。因为这个根视图控制器永远不会被释放,所以它可以安全地将自己分配为HMHomeManagerDelegate协议委托:
HomeStore.shared.homeManager.delegate = self
当home列表发生变化时,home管理器会告诉它的委托,包括home管理器在初始化期间第一次从HomeKit数据库加载数据。当发生这种情况时,配件列表将重新加载以显示主目录中的配件,或者如果不存在则提示用户创建新目录。
你可以扩展应用程序,允许用户在所有已知的房屋中进行选择,而不是总是选择主要的房屋。你也可以允许用户添加、删除或重命名家庭,尽管用户很少执行这些任务,并且通常依赖于家庭应用程序来完成这些任务。
添加新配件页面链接
第一次运行应用程序时,配件列表是空的,因为你没有关联Kilgo Devices的任何配件。这款应用的UI在导航栏上有一个+按钮,点击这个按钮就可以开始搜索本地网络上的配件。按钮的tap处理程序调用home的addAndSetupAccessories(completionHandler:)方法。
home?.addAndSetupAccessories(completionHandler: { error in
if let error = error {
print(error)
} else {
// Make no assumption about changes; just reload everything.
self.reloadData()
}
})
这提供了标准的HomeKit UI,用于定位和添加新配件到给定的家庭。如果成功,完成处理程序刷新应用程序的HomeKit数据副本并重新绘制显示。
当你的应用进入标准配件关联流程(与Home应用使用的流程相同)时,用户需要遵循以下步骤:
扫描或输入新设备的HomeKit设置码。此代码随设备一起打包,或者在配件的HAS显示中可用。
从网络上没有现有HomeKit关联的设备列表中选择。这包括可以访问局域网的真实设备和模拟设备。
等待HomeKit验证步骤1中输入的安装代码是否与设备的代码匹配。
为与设备关联的每个服务分配一个名称和房间。HomeKit为每个服务提供了一个默认的名称和空间,每个服务一个页面,用户可以接受或更改。
用户在步骤4中指定的每件物品都会在Home应用中显示为“附件”。然而,在HomeKit中,这些是HMService实例。它们属于一个HMAccessory实例,该实例表示您在步骤2中选择的物理设备。为了保持与Home应用程序一致的用户体验,示例应用程序UI(以及本文的其余部分)还将每个HMService实例引用为附件。
只显示配件相关的应用
根据从HomeKit捕获到Kilgo服务数组的数据副本绘制显示:
var kilgoServices = [HMService]() // These are called "accessories" in the UI.
当配件列表重新加载时,无论是因为设置了新的home,还是因为配件添加流程完成,您都要填充上面的数组,过滤掉来自Kilgo以外的制造商的HMAccessory实例,以及不与用户交互的HMService实例。此外,在收集列表时,您还可以请求通知任何相应特征的更改,这些特征是给定服务的状态和控制的单个点:
for accessory in home.accessories.filter({ $0.manufacturer == "Kilgo Devices, Inc." }) {
accessory.delegate = HomeStore.shared
for service in accessory.services.filter({ $0.isUserInteractive }) {
kilgoServices.append(service)
// Ask for notifications from any characteristics that support them.
for characteristic in service.characteristics.filter({
$0.properties.contains(HMCharacteristicPropertySupportsEventNotification)
}) {
characteristic.enableNotification(true) { _ in }
}
}
}
因此,显示屏只显示与该特定应用程序相关的配件。
为特定的配件定制常见的交互
配件(如灯泡)具有用户控制或观察的特性(如电源状态、色温、亮度等)。用户通常更关心这些特征中的一个,因为他们最常更改或阅读该特征。这是主要特性,您应该让用户快速访问它。对于一个灯泡,用户通常想要打开或关闭它,所以电源状态是主要特征。
由您来定义您所控制的配件的主要特征。你可以通过在HMService的扩展中创建一个computed属性来返回primary特性的类型:
var primaryControlCharacteristicType: String? {
switch kilgoServiceType {
case .lightBulb: return HMCharacteristicTypePowerState
case .garageDoor: return HMCharacteristicTypeTargetDoorState
case .unknown: return nil
}
}
然后使用这个主要特征类型来定位并返回具有该类型的特征:
var primaryControlCharacteristic: HMCharacteristic? {
return characteristics.first { $0.characteristicType == primaryControlCharacteristicType }
}
Kilgo Devices的灯泡和车库门都是二元主态。灯泡开着或关着。门的目标状态是打开或关闭。这使它成为一个拨动开关足以控制所有主要特性的界面。您可以将其作为附件列表集合视图中每个项目的tap处理程序来实现。当用户轻击配件时,读取当前特征值后取反
func tap() {
if let characteristic = service?.primaryControlCharacteristic,
let value = characteristic.value as? Bool {
// Provide visual feedback that the item was tapped.
bounce()
// Write the new value to HomeKit.
characteristic.writeValue(!value) { error in
self.redrawState(error: error)
}
}
}
写入涉及到网络访问,因此当写入完成时,HomeKit调用一个完成处理程序。利用这个机会更新接口的状态,如上面的代码片段所示。
启用自定义配置
当用户点击配件的信息按钮时,应用程序就会显示有关配件的详细信息。在详细视图中,用户可以重命名配件,将其分配到一个房间,将其从家中移除,并查看设备信息,如固件版本。用户还可以点击“设置”,显示该配件的次要特征列表。
通过只呈现相关的特征类型来控制用户体验。HMService的KilgoService扩展定义了一个计算属性,将可显示的特征列表限制在一个策划列表中:
var displayableCharacteristics: [HMCharacteristic] {
let characteristicTypes = [HMCharacteristicTypePowerState,
HMCharacteristicTypeBrightness,
HMCharacteristicTypeHue,
HMCharacteristicTypeSaturation,
HMCharacteristicTypeTargetDoorState,
HMCharacteristicTypeCurrentDoorState,
HMCharacteristicTypeObstructionDetected,
HMCharacteristicTypeTargetLockMechanismState,
HMCharacteristicTypeCurrentLockMechanismState,
KilgoCharacteristicTypes.fadeRate.rawValue]
return characteristics.filter { characteristicTypes.contains($0.characteristicType) }
}
这些大多是HomeKit标准类型,所有这些都适用于Kilgo设备。在相同的扩展中,还有一个自定义类型-rate—defined
enum KilgoCharacteristicTypes: String {
case fadeRate = "7E536242-341C-4862-BE90-272CE15BD633"
}
特征类型存储为UUID字符串。代码中为淡出率指定的值与附件模拟器中找到的值相匹配,您可以在HAS中检查附件模拟器。如果你也制作了一个真正的Kilgo设备,那么它所使用的值也必须匹配。