@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

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 (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. 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)