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