@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,46 +1,42 @@
1
- # AV Playback
2
-
3
- Patterns for media playback with AVPlayer, streaming HLS content, audio
4
- session configuration, background audio, Now Playing integration, remote
5
- command handling, and Picture-in-Picture.
1
+ # Audio and video playback
6
2
 
7
3
  ## Contents
8
4
 
9
- - [AVPlayer and AVPlayerViewController Setup](#avplayer-and-avplayerviewcontroller-setup)
10
- - [AVPlayerItem and AVAsset Loading](#avplayeritem-and-avasset-loading)
11
- - [Playback Controls](#playback-controls)
12
- - [Observing Player State and Time](#observing-player-state-and-time)
13
- - [Streaming HLS Content](#streaming-hls-content)
14
- - [AVAudioSession Configuration](#avaudiosession-configuration)
15
- - [Background Audio Setup](#background-audio-setup)
16
- - [Now Playing Info Center](#now-playing-info-center)
17
- - [MPRemoteCommandCenter](#mpremotecommandcenter)
18
- - [Picture-in-Picture](#picture-in-picture)
5
+ - [Players and where they render](#players-and-where-they-render)
6
+ - [Assets and items](#assets-and-items)
7
+ - [Transport controls](#transport-controls)
8
+ - [Watching state and time](#watching-state-and-time)
9
+ - [HLS streaming](#hls-streaming)
10
+ - [The audio session](#the-audio-session)
11
+ - [Background audio](#background-audio)
12
+ - [Now Playing](#now-playing)
13
+ - [Remote commands](#remote-commands)
14
+ - [Picture in Picture](#picture-in-picture)
15
+ - [Documentation](#documentation)
19
16
 
20
- ## AVPlayer and AVPlayerViewController Setup
17
+ ## Players and where they render
21
18
 
22
- `AVPlayer` manages playback of a single media asset. Use
23
- `AVPlayerViewController` (AVKit) for the system-standard playback UI with
24
- transport controls, or `AVPlayerLayer` for a custom player interface.
19
+ `AVPlayer` drives playback of one asset at a time. Pick the surface by how
20
+ much UI you want to own:
25
21
 
26
- Docs: [AVPlayer](https://sosumi.ai/documentation/avfoundation/avplayer),
27
- [AVPlayerViewController](https://sosumi.ai/documentation/avkit/avplayerviewcontroller)
22
+ - `AVPlayerViewController` (AVKit) brings the system transport controls.
23
+ - `AVPlayerLayer` draws frames only; you build the controls.
28
24
 
29
- ### AVPlayerViewController in SwiftUI
25
+ SwiftUI:
30
26
 
31
27
  ```swift
32
- import SwiftUI
33
28
  import AVKit
34
29
 
35
- struct VideoPlayerView: View {
36
- let url: URL
30
+ struct TrailerView: View {
31
+ let trailer: URL
37
32
  @State private var player: AVPlayer?
38
33
 
39
34
  var body: some View {
40
35
  VideoPlayer(player: player)
41
36
  .onAppear {
42
- player = AVPlayer(url: url)
43
- player?.play()
37
+ let fresh = AVPlayer(url: trailer)
38
+ player = fresh
39
+ fresh.play()
44
40
  }
45
41
  .onDisappear {
46
42
  player?.pause()
@@ -50,652 +46,410 @@ struct VideoPlayerView: View {
50
46
  }
51
47
  ```
52
48
 
53
- ### AVPlayerViewController in UIKit
49
+ UIKit:
54
50
 
55
51
  ```swift
56
- import UIKit
57
- import AVKit
58
-
59
- final class VideoViewController: UIViewController {
60
- private var player: AVPlayer?
61
-
62
- func presentVideo(url: URL) {
63
- player = AVPlayer(url: url)
64
- let controller = AVPlayerViewController()
65
- controller.player = player
66
- present(controller, animated: true) {
67
- self.player?.play()
68
- }
69
- }
52
+ @MainActor
53
+ func presentTrailer(_ trailer: URL, from host: UIViewController) {
54
+ let screen = AVPlayerViewController()
55
+ screen.player = AVPlayer(url: trailer)
56
+ host.present(screen, animated: true) { screen.player?.play() }
70
57
  }
71
58
  ```
72
59
 
73
- ### Custom Player with AVPlayerLayer
74
-
75
- For full control over the player UI, embed an `AVPlayerLayer`:
60
+ Custom surface:
76
61
 
77
62
  ```swift
78
- import AVFoundation
79
- import UIKit
80
-
81
- final class PlayerView: UIView {
82
- override class var layerClass: AnyClass { AVPlayerLayer.self }
83
-
84
- var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
63
+ final class FilmView: UIView {
64
+ override static var layerClass: AnyClass { AVPlayerLayer.self }
65
+ private var film: AVPlayerLayer { unsafeDowncast(layer, to: AVPlayerLayer.self) }
85
66
 
86
67
  var player: AVPlayer? {
87
- get { playerLayer.player }
88
- set { playerLayer.player = newValue }
89
- }
90
-
91
- func configure() {
92
- playerLayer.videoGravity = .resizeAspect
68
+ get { film.player }
69
+ set {
70
+ film.player = newValue
71
+ film.videoGravity = .resizeAspect
72
+ }
93
73
  }
94
74
  }
95
75
  ```
96
76
 
97
- ## AVPlayerItem and AVAsset Loading
98
-
99
- `AVAsset` represents the static media (duration, tracks, metadata).
100
- `AVPlayerItem` adds the dynamic state (current time, buffering status) needed
101
- for playback.
77
+ ## Assets and items
102
78
 
103
- Docs: [AVPlayerItem](https://sosumi.ai/documentation/avfoundation/avplayeritem),
104
- [AVAsset](https://sosumi.ai/documentation/avfoundation/avasset)
79
+ An `AVAsset` is the media itself: duration, tracks, metadata. An `AVPlayerItem`
80
+ wraps it with the moving parts of playback such as current time and buffer
81
+ state.
105
82
 
106
83
  ```swift
107
- import AVFoundation
108
-
109
- // Local file
110
- let localURL = Bundle.main.url(forResource: "intro", withExtension: "mp4")!
111
- let localItem = AVPlayerItem(url: localURL)
112
-
113
- // Remote file
114
- let remoteURL = URL(string: "https://example.com/video.mp4")!
115
- let remoteItem = AVPlayerItem(url: remoteURL)
84
+ let quick = AVPlayerItem(url: bundledOrRemoteURL)
116
85
 
117
- // From an existing AVAsset (for more control)
118
- let asset = AVURLAsset(url: remoteURL, options: [
119
- AVURLAssetPreferPreciseDurationAndTimingKey: true
120
- ])
86
+ func preparedItem(for source: URL) async throws -> AVPlayerItem? {
87
+ let asset = AVURLAsset(url: source, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
88
+ let (length, tracks, playable) = try await asset.load(.duration, .tracks, .isPlayable)
89
+ guard playable, !tracks.isEmpty, length.isNumeric else { return nil }
90
+ return AVPlayerItem(asset: asset)
91
+ }
121
92
 
122
- // Load properties asynchronously before playback (iOS 15+)
123
- let duration = try await asset.load(.duration)
124
- let tracks = try await asset.load(.tracks)
125
- let isPlayable = try await asset.load(.isPlayable)
93
+ let deck = AVPlayer(playerItem: quick)
94
+ deck.replaceCurrentItem(with: AVPlayerItem(url: nextEpisodeURL))
126
95
 
127
- let item = AVPlayerItem(asset: asset)
128
- let player = AVPlayer(playerItem: item)
96
+ let playlist = AVQueuePlayer(items: episodeURLs.map { AVPlayerItem(url: $0) })
129
97
  ```
130
98
 
131
- ### Replacing the Current Item
132
-
133
- Reuse a single `AVPlayer` and swap items:
134
-
135
- ```swift
136
- let nextItem = AVPlayerItem(url: nextVideoURL)
137
- player.replaceCurrentItem(with: nextItem)
138
- player.play()
139
- ```
99
+ - `AVPlayerItem(url:)` accepts both bundle files and remote URLs.
100
+ - Load asset properties asynchronously with `load(_:)` (iOS 15+) before you
101
+ depend on them.
102
+ - Keep one player alive and swap media with `replaceCurrentItem(with:)`.
103
+ - `AVQueuePlayer` plays a list and moves to the next item by itself.
140
104
 
141
- ### Queue Playback with AVQueuePlayer
105
+ ## Transport controls
142
106
 
143
107
  ```swift
144
- let items = videoURLs.map { AVPlayerItem(url: $0) }
145
- let queuePlayer = AVQueuePlayer(items: items)
146
- queuePlayer.play()
147
- // Automatically advances to the next item
148
- ```
108
+ deck.play()
109
+ deck.pause()
110
+ deck.rate = 1.5
149
111
 
150
- ## Playback Controls
112
+ await deck.seek(to: CMTime(seconds: 42, preferredTimescale: 600))
113
+ await deck.seek(to: CMTime(seconds: 42, preferredTimescale: 600), toleranceBefore: .zero, toleranceAfter: .zero)
151
114
 
152
- ```swift
153
- // Play
154
- player.play()
155
-
156
- // Pause
157
- player.pause()
158
-
159
- // Set playback rate (1.0 = normal, 2.0 = 2x, 0.5 = half speed)
160
- player.rate = 1.5
161
-
162
- // Seek to a specific time
163
- let targetTime = CMTime(seconds: 30, preferredTimescale: 600)
164
- await player.seek(to: targetTime)
165
-
166
- // Seek with tolerance (for precise seeking, e.g., scrubbing)
167
- await player.seek(
168
- to: targetTime,
169
- toleranceBefore: .zero,
170
- toleranceAfter: .zero
171
- )
172
-
173
- // Seek to a percentage of duration
174
- func seekToPercentage(_ percentage: Double) async {
175
- guard let duration = player.currentItem?.duration,
176
- duration.isNumeric else { return }
177
- let targetSeconds = duration.seconds * percentage
178
- let target = CMTime(seconds: targetSeconds, preferredTimescale: 600)
179
- await player.seek(to: target)
115
+ func jump(_ player: AVPlayer, toFraction fraction: Double) async {
116
+ guard let total = player.currentItem?.duration, total.isNumeric else { return }
117
+ await player.seek(to: CMTime(seconds: total.seconds * fraction, preferredTimescale: 600))
180
118
  }
181
119
  ```
182
120
 
183
- ## Observing Player State and Time
184
-
185
- ### Periodic Time Observer
121
+ `rate` 1.0 is normal speed, 2.0 double, 0.5 half. The zero-tolerance seek is
122
+ exact and is what a scrubber wants; the plain seek is cheaper.
186
123
 
187
- Use `addPeriodicTimeObserver` to update UI elements like a progress bar:
124
+ ## Watching state and time
188
125
 
189
126
  ```swift
190
- import AVFoundation
191
-
192
- @Observable
193
- @MainActor
194
- final class PlayerManager {
195
- let player = AVPlayer()
196
- var currentTime: Double = 0
197
- var duration: Double = 0
198
- var isPlaying = false
199
-
200
- private var timeObserver: Any?
201
-
202
- func startObserving() {
203
- // Fire every 0.5 seconds on the main queue
204
- let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
205
- timeObserver = player.addPeriodicTimeObserver(
206
- forInterval: interval,
207
- queue: .main
208
- ) { [weak self] time in
209
- guard let self else { return }
210
- self.currentTime = time.seconds
211
- self.duration = self.player.currentItem?.duration.seconds ?? 0
212
- self.isPlaying = self.player.timeControlStatus == .playing
213
- }
214
- }
127
+ import Combine
215
128
 
216
- func stopObserving() {
217
- if let observer = timeObserver {
218
- player.removeTimeObserver(observer)
219
- timeObserver = nil
129
+ @Observable @MainActor
130
+ final class PlaybackMonitor {
131
+ private(set) var elapsed = 0.0
132
+ private(set) var total = 0.0
133
+ private(set) var isPlaying = false
134
+ private(set) var failure: Error?
135
+ private(set) var stallReason: AVPlayer.WaitingReason?
136
+ @ObservationIgnored private var ticker: Any?
137
+ @ObservationIgnored private var bag: Set<AnyCancellable> = []
138
+ @ObservationIgnored private let player: AVPlayer
139
+
140
+ init(player: AVPlayer) {
141
+ self.player = player
142
+ ticker = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
143
+ queue: .main) { [weak self] now in
144
+ MainActor.assumeIsolated {
145
+ guard let self else { return }
146
+ self.elapsed = now.seconds
147
+ self.total = player.currentItem?.duration.seconds ?? 0
148
+ self.isPlaying = player.timeControlStatus == .playing
149
+ }
220
150
  }
151
+ player.publisher(for: \.status)
152
+ .receive(on: DispatchQueue.main)
153
+ .sink { [weak self] status in
154
+ if status == .failed { self?.failure = player.error }
155
+ }
156
+ .store(in: &bag)
157
+ player.publisher(for: \.timeControlStatus)
158
+ .receive(on: DispatchQueue.main)
159
+ .sink { [weak self] mode in
160
+ self?.stallReason = mode == .waitingToPlayAtSpecifiedRate ? player.reasonForWaitingToPlay : nil
161
+ }
162
+ .store(in: &bag)
163
+ NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
164
+ .receive(on: DispatchQueue.main)
165
+ .sink { _ in player.seek(to: .zero) }
166
+ .store(in: &bag)
221
167
  }
222
168
 
223
- deinit {
224
- if let observer = timeObserver {
225
- player.removeTimeObserver(observer)
226
- }
169
+ func stop() {
170
+ if let ticker { player.removeTimeObserver(ticker) }
171
+ ticker = nil
227
172
  }
228
- }
229
- ```
230
-
231
- ### Observing Player Status with KVO
232
-
233
- Check player and item readiness before playing:
234
-
235
- ```swift
236
- import AVFoundation
237
- import Combine
238
173
 
239
- // Using Combine
240
- var cancellables = Set<AnyCancellable>()
241
-
242
- player.publisher(for: \.status)
243
- .sink { status in
244
- switch status {
245
- case .readyToPlay:
246
- print("Ready to play")
247
- case .failed:
248
- print("Failed: \(player.error?.localizedDescription ?? "")")
249
- case .unknown:
250
- print("Status unknown")
251
- @unknown default:
252
- break
253
- }
254
- }
255
- .store(in: &cancellables)
256
-
257
- // Observe buffering state
258
- player.publisher(for: \.timeControlStatus)
259
- .sink { status in
260
- switch status {
261
- case .playing: print("Playing")
262
- case .paused: print("Paused")
263
- case .waitingToPlayAtSpecifiedRate:
264
- print("Buffering: \(player.reasonForWaitingToPlay?.rawValue ?? "")")
265
- @unknown default: break
266
- }
174
+ isolated deinit {
175
+ if let ticker { player.removeTimeObserver(ticker) }
267
176
  }
268
- .store(in: &cancellables)
269
- ```
270
-
271
- ### Detecting Playback End
272
-
273
- ```swift
274
- NotificationCenter.default.addObserver(
275
- forName: .AVPlayerItemDidPlayToEndTime,
276
- object: player.currentItem,
277
- queue: .main
278
- ) { _ in
279
- // Playback finished -- loop, show replay button, or advance
280
- player.seek(to: .zero) // Loop
281
177
  }
282
178
  ```
283
179
 
284
- ## Streaming HLS Content
285
-
286
- HTTP Live Streaming (HLS) works directly with `AVPlayer`. Pass the `.m3u8`
287
- URL and AVFoundation handles adaptive bitrate selection, buffering, and
288
- failover.
289
-
290
- ```swift
291
- let hlsURL = URL(string: "https://example.com/stream/master.m3u8")!
292
- let player = AVPlayer(url: hlsURL)
293
- player.play()
294
-
295
- // AVPlayer automatically selects the best variant based on:
296
- // - Network bandwidth
297
- // - Device capabilities
298
- // - Display resolution
299
- ```
300
-
301
- ### Preferred Bitrate and Resolution
180
+ - Inside `MainActor.assumeIsolated` the properties need an explicit `self.`:
181
+ the implicit-self shorthand after `guard let self` does not reach a nested
182
+ closure.
183
+ - The periodic observer (every 0.5 s here) feeds progress UI. Remove its token
184
+ with `removeTimeObserver(_:)` on stop and on teardown.
185
+ - `status` moves to `.readyToPlay` or `.failed` (read `player.error`); it starts
186
+ as `.unknown`.
187
+ - `timeControlStatus` is `.playing`, `.paused` or
188
+ `.waitingToPlayAtSpecifiedRate`, the buffering state, with the cause in
189
+ `reasonForWaitingToPlay`.
190
+ - `.AVPlayerItemDidPlayToEndTime` on the current item marks the end; seek to
191
+ `.zero` to loop.
192
+ - `AVPlayer` is main-actor bound in the current SDK, so hop to the main queue
193
+ before touching it from publishers.
194
+
195
+ ## HLS streaming
196
+
197
+ Give `AVPlayer` an `.m3u8` URL and AVFoundation handles adaptive bitrate,
198
+ buffering and failover. It picks the variant from bandwidth, device capability
199
+ and display resolution. You can cap it:
302
200
 
303
201
  ```swift
304
- let item = AVPlayerItem(url: hlsURL)
305
-
306
- // Limit maximum resolution (e.g., for cellular)
307
- item.preferredMaximumResolution = CGSize(width: 1280, height: 720)
308
-
309
- // Limit peak bitrate (bits per second)
310
- item.preferredPeakBitRate = 2_000_000 // 2 Mbps
311
-
312
- // For forward buffering duration
313
- item.preferredForwardBufferDuration = 5 // seconds; 0 = system default
202
+ let live = AVPlayerItem(url: streamURL)
203
+ live.preferredMaximumResolution = CGSize(width: 1280, height: 720)
204
+ live.preferredPeakBitRate = 2_000_000
205
+ live.preferredForwardBufferDuration = 5
314
206
  ```
315
207
 
316
- ## AVAudioSession Configuration
317
-
318
- Configure `AVAudioSession` to tell the system how your app intends to use
319
- audio. This affects audio routing, mixing behavior, and background playback.
208
+ `preferredPeakBitRate` is in bits per second (2 Mbps above). A forward buffer of
209
+ 0 leaves the choice to the system.
320
210
 
321
- Docs: [AVAudioSession](https://sosumi.ai/documentation/avfaudio/avaudiosession),
322
- [AVAudioSession.Category](https://sosumi.ai/documentation/avfaudio/avaudiosession/category-swift.struct)
211
+ ## The audio session
323
212
 
324
- ### Categories and Modes
213
+ `AVAudioSession` states what the app does with sound, which decides routing,
214
+ mixing with other apps and background playback.
325
215
 
326
- | Category | Behavior | Common Use |
216
+ | Category | Behaviour | Typical use |
327
217
  |---|---|---|
328
- | `.playback` | Audio plays even with silent switch on; can play in background | Music, podcasts, video |
329
- | `.playAndRecord` | Simultaneous input and output | Voice/video calls, recording with monitoring |
330
- | `.ambient` | Mixes with other audio; silenced by switch | Game sound effects, casual audio |
331
- | `.soloAmbient` | Default; silences other audio; silenced by switch | Default app behavior |
218
+ | `.playback` | keeps playing with the silent switch on, can continue in background | music, podcasts, video |
219
+ | `.playAndRecord` | input and output at once | calls, recording while monitoring |
220
+ | `.ambient` | mixes with other audio, muted by the silent switch | game effects, incidental sound |
221
+ | `.soloAmbient` | the default: silences other audio, muted by the silent switch | |
332
222
 
333
223
  ```swift
334
224
  import AVFAudio
335
225
 
336
- func configureAudioSession(forPlayback: Bool = true) throws {
337
- let session = AVAudioSession.sharedInstance()
338
-
339
- if forPlayback {
340
- // Media playback: audio continues with silent switch, supports background
341
- try session.setCategory(
342
- .playback,
343
- mode: .default,
344
- options: []
345
- )
346
- } else {
347
- // Mix with other apps (e.g., game sounds over user's music)
348
- try session.setCategory(
349
- .ambient,
350
- mode: .default,
351
- options: [.mixWithOthers]
352
- )
353
- }
354
-
355
- try session.setActive(true)
226
+ func enableMixedEffects() throws {
227
+ let audio = AVAudioSession.sharedInstance()
228
+ try audio.setCategory(.ambient, options: .mixWithOthers)
229
+ try audio.setActive(true)
356
230
  }
357
231
 
358
- // For video calls
359
- try AVAudioSession.sharedInstance().setCategory(
360
- .playAndRecord,
361
- mode: .videoChat,
362
- options: [.defaultToSpeaker, .allowBluetooth]
363
- )
364
- ```
365
-
366
- ### Handling Audio Interruptions
367
-
368
- ```swift
369
- NotificationCenter.default.addObserver(
370
- forName: AVAudioSession.interruptionNotification,
371
- object: AVAudioSession.sharedInstance(),
372
- queue: .main
373
- ) { notification in
374
- guard let info = notification.userInfo,
375
- let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
376
- let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
377
- return
378
- }
379
-
380
- switch type {
381
- case .began:
382
- // Pause playback -- system has interrupted audio
383
- player.pause()
384
- case .ended:
385
- let options = info[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0
386
- if AVAudioSession.InterruptionOptions(rawValue: options).contains(.shouldResume) {
387
- player.play()
388
- }
389
- @unknown default:
390
- break
391
- }
232
+ func enableVideoCall() throws {
233
+ let audio = AVAudioSession.sharedInstance()
234
+ try audio.setCategory(.playAndRecord, mode: .videoChat, options: [.allowBluetoothHFP, .defaultToSpeaker])
235
+ try audio.setActive(true)
392
236
  }
393
237
  ```
394
238
 
395
- ### Handling Route Changes
239
+ `.allowBluetooth` is deprecated in the iOS 26 SDK; `.allowBluetoothHFP` is its
240
+ replacement.
241
+
242
+ Interruptions and route changes:
396
243
 
397
244
  ```swift
398
- NotificationCenter.default.addObserver(
399
- forName: AVAudioSession.routeChangeNotification,
400
- object: AVAudioSession.sharedInstance(),
401
- queue: .main
402
- ) { notification in
403
- guard let info = notification.userInfo,
404
- let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
405
- let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
406
- return
245
+ func observeAudioEvents(pause: @escaping @Sendable () -> Void, resume: @escaping @Sendable () -> Void) {
246
+ let center = NotificationCenter.default
247
+ center.addObserver(forName: AVAudioSession.interruptionNotification, object: nil, queue: .main) { note in
248
+ guard let raw = note.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
249
+ let kind = AVAudioSession.InterruptionType(rawValue: raw) else { return }
250
+ switch kind {
251
+ case .began:
252
+ pause()
253
+ case .ended:
254
+ let flags = note.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0
255
+ if AVAudioSession.InterruptionOptions(rawValue: flags).contains(.shouldResume) { resume() }
256
+ @unknown default:
257
+ break
258
+ }
407
259
  }
408
-
409
- if reason == .oldDeviceUnavailable {
410
- // Headphones unplugged -- pause playback (Apple HIG requirement)
411
- player.pause()
260
+ center.addObserver(forName: AVAudioSession.routeChangeNotification, object: nil, queue: .main) { note in
261
+ guard let raw = note.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
262
+ AVAudioSession.RouteChangeReason(rawValue: raw) == .oldDeviceUnavailable else { return }
263
+ pause()
412
264
  }
413
265
  }
414
266
  ```
415
267
 
416
- ## Background Audio Setup
417
-
418
- To play audio when the app is in the background, two things are required:
419
-
420
- 1. Enable the `audio` background mode in your app's capabilities.
421
- 2. Configure `AVAudioSession` with the `.playback` category.
422
-
423
- ### Info.plist Configuration
424
-
425
- ```xml
426
- <key>UIBackgroundModes</key>
427
- <array>
428
- <string>audio</string>
429
- </array>
430
- ```
431
-
432
- Or enable "Audio, AirPlay, and Picture in Picture" in Xcode's Signing &
433
- Capabilities tab.
434
-
435
- ### Activating Background Audio
268
+ Pause when an interruption begins; resume at its end only if the options say
269
+ `.shouldResume`. When headphones are unplugged (`.oldDeviceUnavailable`),
270
+ pause: the Human Interface Guidelines expect it.
436
271
 
437
- ```swift
438
- import AVFAudio
272
+ ## Background audio
439
273
 
440
- func enableBackgroundAudio() throws {
441
- let session = AVAudioSession.sharedInstance()
442
- try session.setCategory(.playback, mode: .default)
443
- try session.setActive(true)
444
- }
445
- ```
274
+ Two things are needed together: the `audio` background mode and the
275
+ `.playback` category.
446
276
 
447
- **Rules:**
277
+ - Info.plist: `UIBackgroundModes` containing `audio`, or the Xcode capability
278
+ "Audio, AirPlay, and Picture in Picture".
448
279
  - Call `setCategory` before `setActive`.
449
- - The `.playback` category is required; `.ambient` and `.soloAmbient` do not
450
- support background audio.
451
- - Deactivate the session when playback ends to let other apps use audio:
280
+ - `.ambient` and `.soloAmbient` stop in the background; only `.playback` keeps
281
+ going.
282
+ - When finished, give audio back to other apps:
452
283
 
453
284
  ```swift
454
- func deactivateAudioSession() {
455
- try? AVAudioSession.sharedInstance().setActive(
456
- false,
457
- options: .notifyOthersOnDeactivation
458
- )
459
- }
285
+ try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
460
286
  ```
461
287
 
462
- ## Now Playing Info Center
463
-
464
- Update `MPNowPlayingInfoCenter` so the system displays track information on
465
- the lock screen, Control Center, and connected accessories (CarPlay, AirPods).
288
+ ## Now Playing
466
289
 
467
- Docs: [MPNowPlayingInfoCenter](https://sosumi.ai/documentation/mediaplayer/mpnowplayinginfocenter)
290
+ `MPNowPlayingInfoCenter` fills the Lock Screen, Control Center and accessories
291
+ such as CarPlay and AirPods.
468
292
 
469
293
  ```swift
470
294
  import MediaPlayer
471
295
 
472
- func updateNowPlayingInfo(
473
- title: String,
474
- artist: String,
475
- albumTitle: String? = nil,
476
- duration: TimeInterval,
477
- currentTime: TimeInterval,
478
- artwork: UIImage? = nil
479
- ) {
480
- var info: [String: Any] = [
296
+ func publishNowPlaying(title: String, artist: String, album: String, length: Double, cover: UIImage) {
297
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = [
481
298
  MPMediaItemPropertyTitle: title,
482
299
  MPMediaItemPropertyArtist: artist,
483
- MPMediaItemPropertyPlaybackDuration: duration,
484
- MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime,
300
+ MPMediaItemPropertyAlbumTitle: album,
301
+ MPMediaItemPropertyPlaybackDuration: length,
302
+ MPNowPlayingInfoPropertyElapsedPlaybackTime: 0.0,
485
303
  MPNowPlayingInfoPropertyPlaybackRate: 1.0,
486
- MPNowPlayingInfoPropertyMediaType: MPNowPlayingInfoMediaType.audio.rawValue
304
+ MPNowPlayingInfoPropertyMediaType: MPNowPlayingInfoMediaType.audio.rawValue,
305
+ MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: cover.size) { _ in cover },
487
306
  ]
488
-
489
- if let albumTitle {
490
- info[MPMediaItemPropertyAlbumTitle] = albumTitle
491
- }
492
-
493
- if let artwork {
494
- info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(
495
- boundsSize: artwork.size
496
- ) { _ in artwork }
497
- }
498
-
499
- MPNowPlayingInfoCenter.default().nowPlayingInfo = info
500
307
  }
501
308
 
502
- // Update elapsed time during playback
503
- func updateElapsedTime(_ seconds: TimeInterval, rate: Float = 1.0) {
309
+ func refreshProgress(elapsed: Double, rate: Double) {
504
310
  var info = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
505
- info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = seconds
311
+ info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsed
506
312
  info[MPNowPlayingInfoPropertyPlaybackRate] = rate
507
313
  MPNowPlayingInfoCenter.default().nowPlayingInfo = info
508
314
  }
509
315
  ```
510
316
 
511
- ## MPRemoteCommandCenter
317
+ While playing, update elapsed time and rate by copying the current dictionary,
318
+ changing the two values and assigning it back.
512
319
 
513
- Register handlers for lock screen, Control Center, and accessory controls
514
- (play, pause, skip, seek). Without these, the system controls won't work.
320
+ ## Remote commands
515
321
 
516
- Docs: [MPRemoteCommandCenter](https://sosumi.ai/documentation/mediaplayer/mpremotecommandcenter)
322
+ Lock Screen, Control Center and headphone buttons do nothing until the app
323
+ registers handlers.
517
324
 
518
325
  ```swift
519
- import MediaPlayer
520
-
521
- func setupRemoteCommands(player: AVPlayer) {
522
- let commandCenter = MPRemoteCommandCenter.shared()
523
-
524
- // Play
525
- commandCenter.playCommand.isEnabled = true
526
- commandCenter.playCommand.addTarget { _ in
527
- player.play()
528
- return .success
529
- }
530
-
531
- // Pause
532
- commandCenter.pauseCommand.isEnabled = true
533
- commandCenter.pauseCommand.addTarget { _ in
534
- player.pause()
535
- return .success
536
- }
537
-
538
- // Toggle play/pause (headphone button, etc.)
539
- commandCenter.togglePlayPauseCommand.isEnabled = true
540
- commandCenter.togglePlayPauseCommand.addTarget { _ in
541
- if player.timeControlStatus == .playing {
542
- player.pause()
543
- } else {
544
- player.play()
545
- }
326
+ @MainActor
327
+ func wireRemote(to player: AVPlayer) {
328
+ let remote = MPRemoteCommandCenter.shared()
329
+ let failed = MPRemoteCommandHandlerStatus.commandFailed
330
+
331
+ remote.playCommand.isEnabled = true
332
+ remote.playCommand.addTarget { _ in player.play(); return MPRemoteCommandHandlerStatus.success }
333
+ remote.pauseCommand.isEnabled = true
334
+ remote.pauseCommand.addTarget { _ in player.pause(); return MPRemoteCommandHandlerStatus.success }
335
+ remote.togglePlayPauseCommand.isEnabled = true
336
+ remote.togglePlayPauseCommand.addTarget { _ in
337
+ player.timeControlStatus == .playing ? player.pause() : player.play()
546
338
  return .success
547
339
  }
548
340
 
549
- // Skip forward (e.g., 15 seconds)
550
- commandCenter.skipForwardCommand.isEnabled = true
551
- commandCenter.skipForwardCommand.preferredIntervals = [15]
552
- commandCenter.skipForwardCommand.addTarget { event in
553
- guard let skipEvent = event as? MPSkipIntervalCommandEvent else {
554
- return .commandFailed
555
- }
556
- let currentTime = player.currentTime().seconds
557
- let target = CMTime(
558
- seconds: currentTime + skipEvent.interval,
559
- preferredTimescale: 600
560
- )
561
- player.seek(to: target)
341
+ remote.skipForwardCommand.preferredIntervals = [15]
342
+ remote.skipForwardCommand.addTarget { event in
343
+ guard let skip = event as? MPSkipIntervalCommandEvent else { return failed }
344
+ let target = player.currentTime().seconds + skip.interval
345
+ player.seek(to: CMTime(seconds: target, preferredTimescale: 600))
562
346
  return .success
563
347
  }
564
-
565
- // Skip backward (e.g., 15 seconds)
566
- commandCenter.skipBackwardCommand.isEnabled = true
567
- commandCenter.skipBackwardCommand.preferredIntervals = [15]
568
- commandCenter.skipBackwardCommand.addTarget { event in
569
- guard let skipEvent = event as? MPSkipIntervalCommandEvent else {
570
- return .commandFailed
571
- }
572
- let currentTime = player.currentTime().seconds
573
- let target = CMTime(
574
- seconds: max(0, currentTime - skipEvent.interval),
575
- preferredTimescale: 600
576
- )
577
- player.seek(to: target)
348
+ remote.skipBackwardCommand.preferredIntervals = [15]
349
+ remote.skipBackwardCommand.addTarget { event in
350
+ guard let skip = event as? MPSkipIntervalCommandEvent else { return failed }
351
+ let target = max(0, player.currentTime().seconds - skip.interval)
352
+ player.seek(to: CMTime(seconds: target, preferredTimescale: 600))
578
353
  return .success
579
354
  }
580
355
 
581
- // Scrubbing (seek bar on lock screen)
582
- commandCenter.changePlaybackPositionCommand.isEnabled = true
583
- commandCenter.changePlaybackPositionCommand.addTarget { event in
584
- guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent else {
585
- return .commandFailed
586
- }
587
- let target = CMTime(
588
- seconds: positionEvent.positionTime,
589
- preferredTimescale: 600
590
- )
591
- player.seek(to: target)
356
+ remote.changePlaybackPositionCommand.addTarget { event in
357
+ guard let scrub = event as? MPChangePlaybackPositionCommandEvent else { return failed }
358
+ player.seek(to: CMTime(seconds: scrub.positionTime, preferredTimescale: 600))
592
359
  return .success
593
360
  }
594
361
 
595
- // Disable unsupported commands to remove them from UI
596
- commandCenter.nextTrackCommand.isEnabled = false
597
- commandCenter.previousTrackCommand.isEnabled = false
362
+ remote.nextTrackCommand.isEnabled = false
363
+ remote.previousTrackCommand.isEnabled = false
598
364
  }
599
- ```
600
365
 
601
- ### Cleanup
602
-
603
- ```swift
604
- func teardownRemoteCommands() {
605
- let commandCenter = MPRemoteCommandCenter.shared()
606
- commandCenter.playCommand.removeTarget(nil)
607
- commandCenter.pauseCommand.removeTarget(nil)
608
- commandCenter.togglePlayPauseCommand.removeTarget(nil)
609
- commandCenter.skipForwardCommand.removeTarget(nil)
610
- commandCenter.skipBackwardCommand.removeTarget(nil)
611
- commandCenter.changePlaybackPositionCommand.removeTarget(nil)
366
+ func unwireRemote() {
367
+ let remote = MPRemoteCommandCenter.shared()
368
+ for command in [remote.playCommand, remote.pauseCommand, remote.togglePlayPauseCommand,
369
+ remote.skipForwardCommand, remote.skipBackwardCommand,
370
+ remote.changePlaybackPositionCommand] {
371
+ command.removeTarget(nil)
372
+ }
612
373
  }
613
374
  ```
614
375
 
615
- ## Picture-in-Picture
616
-
617
- `AVPictureInPictureController` enables floating video playback that continues
618
- when the user navigates away. Requires the `audio` background mode.
376
+ `togglePlayPauseCommand` is the headphone button. Disabling a command you do
377
+ not support (next and previous track here) hides it from the system UI. On
378
+ teardown, `removeTarget(nil)` on every command you registered.
619
379
 
620
- Docs: [AVPictureInPictureController](https://sosumi.ai/documentation/avkit/avpictureinpicturecontroller),
621
- [Adopting Picture in Picture in a Custom Player](https://sosumi.ai/documentation/avkit/adopting-picture-in-picture-in-a-custom-player)
380
+ ## Picture in Picture
622
381
 
623
- ### With AVPlayerViewController (Automatic)
382
+ `AVPictureInPictureController` keeps video floating while the person uses other
383
+ apps. It depends on the `audio` background mode.
624
384
 
625
- `AVPlayerViewController` supports PiP automatically when the background audio
626
- capability is enabled. No extra code needed.
385
+ `AVPlayerViewController` handles PiP by itself once that capability is on;
386
+ `allowsPictureInPicturePlayback` is `true` by default. A custom player wires it
387
+ up:
627
388
 
628
389
  ```swift
629
- let controller = AVPlayerViewController()
630
- controller.player = player
631
- controller.allowsPictureInPicturePlayback = true // true by default
632
- ```
633
-
634
- ### With a Custom Player
635
-
636
- ```swift
637
- import AVKit
638
-
639
- @Observable
640
390
  @MainActor
641
- final class PiPManager: NSObject, AVPictureInPictureControllerDelegate {
642
- private var pipController: AVPictureInPictureController?
643
- var isPiPActive = false
644
- var isPiPPossible = false
645
-
646
- func setup(playerLayer: AVPlayerLayer) {
647
- guard AVPictureInPictureController.isPictureInPictureSupported() else {
648
- return
649
- }
650
-
651
- pipController = AVPictureInPictureController(playerLayer: playerLayer)
652
- pipController?.delegate = self
653
-
654
- // Auto-start PiP when app goes to background
655
- pipController?.canStartPictureInPictureAutomaticallyFromInline = true
391
+ final class FloatingVideo: NSObject, @preconcurrency AVPictureInPictureControllerDelegate {
392
+ private var pip: AVPictureInPictureController?
393
+ private(set) var isFloating = false
394
+ var onRestore: (() -> Void)?
395
+
396
+ func attach(to film: AVPlayerLayer) {
397
+ guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
398
+ let controller = AVPictureInPictureController(playerLayer: film)
399
+ controller?.delegate = self
400
+ controller?.canStartPictureInPictureAutomaticallyFromInline = true
401
+ pip = controller
656
402
  }
657
403
 
658
- func togglePiP() {
659
- guard let pipController else { return }
660
- if pipController.isPictureInPictureActive {
661
- pipController.stopPictureInPicture()
662
- } else {
663
- pipController.startPictureInPicture()
664
- }
404
+ func toggle() {
405
+ guard let pip else { return }
406
+ pip.isPictureInPictureActive ? pip.stopPictureInPicture() : pip.startPictureInPicture()
665
407
  }
666
408
 
667
- // MARK: - AVPictureInPictureControllerDelegate
668
-
669
- nonisolated func pictureInPictureControllerWillStartPictureInPicture(
670
- _ controller: AVPictureInPictureController
671
- ) {
672
- Task { @MainActor in isPiPActive = true }
409
+ nonisolated func pictureInPictureControllerWillStartPictureInPicture(_ floating: AVPictureInPictureController) {
410
+ Task { @MainActor in isFloating = true }
673
411
  }
674
412
 
675
- nonisolated func pictureInPictureControllerDidStopPictureInPicture(
676
- _ controller: AVPictureInPictureController
677
- ) {
678
- Task { @MainActor in isPiPActive = false }
413
+ nonisolated func pictureInPictureControllerDidStopPictureInPicture(_ floating: AVPictureInPictureController) {
414
+ Task { @MainActor in isFloating = false }
679
415
  }
680
416
 
681
- nonisolated func pictureInPictureController(
417
+ func pictureInPictureController(
682
418
  _ controller: AVPictureInPictureController,
683
- restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
684
- completionHandler: @escaping (Bool) -> Void
419
+ restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
685
420
  ) {
686
- // Restore your player UI here, then call the handler
687
- Task { @MainActor in
688
- // Navigate back to the player view
689
- completionHandler(true)
690
- }
421
+ onRestore?()
422
+ completionHandler(true)
691
423
  }
692
424
  }
693
425
  ```
694
426
 
695
- ### PiP Requirements Checklist
696
-
697
- - [ ] `UIBackgroundModes` includes `audio` in Info.plist
698
- - [ ] `AVAudioSession` category set to `.playback`
699
- - [ ] Check `AVPictureInPictureController.isPictureInPictureSupported()` before setup
700
- - [ ] Implement `restoreUserInterfaceForPictureInPictureStop` delegate method
701
- - [ ] Call completion handler in the restore delegate method (failure to call causes hangs)
427
+ `canStartPictureInPictureAutomaticallyFromInline` starts PiP on its own when
428
+ the app moves to the background. The restore callback must bring the player UI
429
+ back (or navigate to it) and then call the completion handler.
430
+ AVKit delivers these callbacks on the main thread. The restore method stays
431
+ main-actor isolated under the `@preconcurrency` conformance (Swift checks the
432
+ thread at run time) instead of hopping through a `Task`: the completion handler
433
+ is not `Sendable`, and Swift 6 rejects sending it into a task.
434
+
435
+ ### PiP checklist
436
+
437
+ - [ ] `UIBackgroundModes` contains `audio`
438
+ - [ ] The audio session category is `.playback`
439
+ - [ ] `isPictureInPictureSupported()` is checked before setup
440
+ - [ ] The restore delegate method is implemented
441
+ - [ ] Its completion handler is always called; skipping it leaves the system
442
+ waiting
443
+
444
+ ## Documentation
445
+
446
+ - [AVPlayer](https://developer.apple.com/documentation/avfoundation/avplayer)
447
+ - [AVPlayerViewController](https://developer.apple.com/documentation/avkit/avplayerviewcontroller)
448
+ - [AVPlayerItem](https://developer.apple.com/documentation/avfoundation/avplayeritem)
449
+ - [AVAsset](https://developer.apple.com/documentation/avfoundation/avasset)
450
+ - [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession)
451
+ - [AVAudioSession.Category](https://developer.apple.com/documentation/avfaudio/avaudiosession/category)
452
+ - [MPNowPlayingInfoCenter](https://developer.apple.com/documentation/mediaplayer/mpnowplayinginfocenter)
453
+ - [MPRemoteCommandCenter](https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter)
454
+ - [AVPictureInPictureController](https://developer.apple.com/documentation/avkit/avpictureinpicturecontroller)
455
+ - [Custom player PiP article](https://developer.apple.com/documentation/avkit/adopting-picture-in-picture-in-a-custom-player)