@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,668 +1,550 @@
1
- # AVKit Patterns
1
+ # AVKit patterns
2
2
 
3
- Advanced patterns for AVKit media playback beyond the main skill coverage.
3
+ Advanced playback recipes for iOS 26 and Swift 6.3.
4
4
 
5
5
  ## Contents
6
6
 
7
- - [Custom Player UI with AVPlayerLayer](#custom-player-ui-with-avplayerlayer)
8
- - [Interstitial Content](#interstitial-content)
9
- - [Background Playback](#background-playback)
10
- - [Error Handling](#error-handling)
11
- - [Observing Playback State](#observing-playback-state)
12
- - [Video Frame Analysis](#video-frame-analysis)
13
- - [HDR Content](#hdr-content)
14
- - [SwiftUI Player Manager](#swiftui-player-manager)
15
- - [AVPlayerViewController in UIViewControllerRepresentable](#avplayerviewcontroller-in-uiviewcontrollerrepresentable)
7
+ - [Custom player with AVPlayerLayer](#custom-player-with-avplayerlayer)
8
+ - [Interstitials](#interstitials)
9
+ - [Background playback](#background-playback)
10
+ - [Errors](#errors)
11
+ - [Observing playback](#observing-playback)
12
+ - [Video frame analysis](#video-frame-analysis)
13
+ - [HDR](#hdr)
14
+ - [SwiftUI player model](#swiftui-player-model)
15
+ - [Wrapping AVPlayerViewController for SwiftUI](#wrapping-avplayerviewcontroller-for-swiftui)
16
16
 
17
- ## Custom Player UI with AVPlayerLayer
17
+ ## Custom player with AVPlayerLayer
18
18
 
19
- When `AVPlayerViewController` does not meet your design requirements, build a
20
- custom player UI using `AVPlayerLayer` directly. You lose system transport
21
- controls but gain full control over the interface.
19
+ Drawing video through `AVPlayerLayer` means giving up the system transport
20
+ controls in exchange for complete control over the UI.
22
21
 
23
22
  ```swift
24
23
  import AVFoundation
25
24
  import UIKit
26
25
 
27
- final class CustomPlayerView: UIView {
28
- override class var layerClass: AnyClass { AVPlayerLayer.self }
26
+ @MainActor
27
+ final class FilmSurface: UIView {
28
+ override static var layerClass: AnyClass { AVPlayerLayer.self }
29
29
 
30
- var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
30
+ var filmLayer: AVPlayerLayer {
31
+ layer as! AVPlayerLayer
32
+ }
31
33
 
32
34
  var player: AVPlayer? {
33
- get { playerLayer.player }
34
- set { playerLayer.player = newValue }
35
+ get { filmLayer.player }
36
+ set { filmLayer.player = newValue }
35
37
  }
36
38
 
37
- override init(frame: CGRect) {
38
- super.init(frame: frame)
39
- playerLayer.videoGravity = .resizeAspect
39
+ override init(frame rect: CGRect) {
40
+ super.init(frame: rect)
41
+ filmLayer.videoGravity = .resizeAspect
40
42
  }
41
43
 
42
- required init?(coder: NSCoder) {
43
- super.init(coder: coder)
44
- playerLayer.videoGravity = .resizeAspect
44
+ required init?(coder decoder: NSCoder) {
45
+ super.init(coder: decoder)
46
+ filmLayer.videoGravity = .resizeAspect
45
47
  }
46
48
  }
47
49
  ```
48
50
 
49
- ### Adding PiP to a Custom Player
51
+ The force cast is safe because `layerClass` guarantees the layer type.
50
52
 
51
- Use `AVPictureInPictureController` with the custom player layer:
53
+ ### Adding PiP to it
52
54
 
53
55
  ```swift
56
+ import AVFoundation
54
57
  import AVKit
58
+ import UIKit
55
59
 
56
- final class CustomPlayerController: UIViewController, AVPictureInPictureControllerDelegate {
57
- private let playerView = CustomPlayerView()
58
- private var pipController: AVPictureInPictureController?
60
+ @MainActor
61
+ final class CustomCinemaController: UIViewController, @preconcurrency AVPictureInPictureControllerDelegate {
62
+ private let surface = FilmSurface()
59
63
  private let player = AVPlayer()
64
+ private var pip: AVPictureInPictureController?
65
+ private let overlayControls = UIStackView()
60
66
 
61
- override func viewDidLoad() {
62
- super.viewDidLoad()
63
- view.addSubview(playerView)
64
- playerView.player = player
67
+ override func loadView() {
68
+ view = surface
69
+ }
65
70
 
71
+ func load(_ url: URL) {
72
+ surface.player = player
66
73
  if AVPictureInPictureController.isPictureInPictureSupported() {
67
- pipController = AVPictureInPictureController(playerLayer: playerView.playerLayer)
68
- pipController?.delegate = self
69
- pipController?.canStartPictureInPictureAutomaticallyFromInline = true
74
+ let controller = AVPictureInPictureController(playerLayer: surface.filmLayer)
75
+ controller?.delegate = self
76
+ controller?.canStartPictureInPictureAutomaticallyFromInline = true
77
+ pip = controller
70
78
  }
71
- }
72
-
73
- func play(url: URL) {
74
- player.replaceCurrentItem(with: AVPlayerItem(url: url))
79
+ let episode = AVPlayerItem(url: url)
80
+ player.replaceCurrentItem(with: episode)
75
81
  player.play()
76
82
  }
77
83
 
78
- @IBAction func userTappedPictureInPictureButton(_ sender: UIButton) {
79
- guard pipController?.isPictureInPicturePossible == true else { return }
80
- pipController?.startPictureInPicture()
84
+ @objc func pipButtonTapped() {
85
+ guard let pip, pip.isPictureInPicturePossible else { return }
86
+ pip.startPictureInPicture()
81
87
  }
82
88
 
83
- // MARK: - AVPictureInPictureControllerDelegate
84
-
85
- func pictureInPictureControllerWillStartPictureInPicture(
86
- _ pictureInPictureController: AVPictureInPictureController
87
- ) {
88
- // Prepare UI for PiP (e.g., hide custom controls)
89
+ func pictureInPictureControllerWillStartPictureInPicture(_ controller: AVPictureInPictureController) {
90
+ overlayControls.isHidden = true
89
91
  }
90
92
 
91
- func pictureInPictureControllerDidStopPictureInPicture(
92
- _ pictureInPictureController: AVPictureInPictureController
93
- ) {
94
- // Restore custom controls
93
+ func pictureInPictureControllerDidStopPictureInPicture(_ controller: AVPictureInPictureController) {
94
+ overlayControls.isHidden = false
95
95
  }
96
96
 
97
- func pictureInPictureController(
98
- _ pictureInPictureController: AVPictureInPictureController,
99
- restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
100
- ) {
101
- // Re-present the player view controller
102
- completionHandler(true)
97
+ func pictureInPictureController(_ controller: AVPictureInPictureController,
98
+ restoreUserInterfaceForPictureInPictureStopWithCompletionHandler finish: @escaping (Bool) -> Void) {
99
+ finish(true)
103
100
  }
104
101
  }
105
102
  ```
106
103
 
107
- ### PiP with Sample Buffers
104
+ - Create the PiP controller only when the device supports PiP.
105
+ - Set its delegate and the automatic-inline flag, then load the item and play.
106
+ - The button action re-checks `isPictureInPicturePossible` before starting.
107
+ - Hide custom controls when PiP starts, show them again when it stops, and
108
+ always call `completionHandler(true)` in the restore callback.
108
109
 
109
- For apps rendering video frames manually (e.g., from a custom pipeline), use
110
- the sample buffer content source:
110
+ ### PiP from sample buffers
111
+
112
+ When the app renders frames itself into an `AVSampleBufferDisplayLayer`, build
113
+ the controller from a content source:
111
114
 
112
115
  ```swift
116
+ import AVFoundation
113
117
  import AVKit
118
+ import CoreMedia
114
119
 
115
- func setupSampleBufferPiP(
116
- displayLayer: AVSampleBufferDisplayLayer,
117
- delegate: AVPictureInPictureSampleBufferPlaybackDelegate
118
- ) -> AVPictureInPictureController? {
119
- guard AVPictureInPictureController.isPictureInPictureSupported() else { return nil }
120
-
121
- let contentSource = AVPictureInPictureController.ContentSource(
122
- sampleBufferDisplayLayer: displayLayer,
123
- playbackDelegate: delegate
124
- )
125
- let controller = AVPictureInPictureController(contentSource: contentSource)
126
- return controller
127
- }
128
- ```
129
-
130
- Only show a custom PiP start affordance after device support is known. When the
131
- user taps that affordance, check the controller's `isPictureInPicturePossible`
132
- because support can still be false for the current layer, item, media format,
133
- or playback state. Start custom PiP only in response to user interaction.
134
-
135
- The playback delegate must report timing and play/pause state:
120
+ @MainActor
121
+ final class RemoteDesktopPiP: NSObject, @preconcurrency AVPictureInPictureSampleBufferPlaybackDelegate {
122
+ let frameLayer = AVSampleBufferDisplayLayer()
123
+ private(set) var pip: AVPictureInPictureController?
124
+ private var paused = false
136
125
 
137
- ```swift
138
- final class SampleBufferPlaybackHandler: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
139
- var isPlaying = true
140
- var currentTime: CMTime = .zero
141
- var duration: CMTime = CMTime(seconds: 300, preferredTimescale: 600)
126
+ func prepare() {
127
+ if !AVPictureInPictureController.isPictureInPictureSupported() { return }
128
+ let source = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: frameLayer, playbackDelegate: self)
129
+ pip = AVPictureInPictureController(contentSource: source)
130
+ }
142
131
 
143
- func pictureInPictureController(
144
- _ pictureInPictureController: AVPictureInPictureController,
145
- setPlaying playing: Bool
146
- ) {
147
- isPlaying = playing
132
+ func userTappedPiP() {
133
+ guard let pip, pip.isPictureInPicturePossible else { return }
134
+ pip.startPictureInPicture()
148
135
  }
149
136
 
150
- func pictureInPictureControllerTimeRangeForPlayback(
151
- _ pictureInPictureController: AVPictureInPictureController
152
- ) -> CMTimeRange {
153
- CMTimeRange(start: .zero, duration: duration)
137
+ func pictureInPictureController(_ controller: AVPictureInPictureController, setPlaying playing: Bool) {
138
+ paused = !playing
154
139
  }
155
140
 
156
- func pictureInPictureControllerIsPlaybackPaused(
157
- _ pictureInPictureController: AVPictureInPictureController
158
- ) -> Bool {
159
- !isPlaying
141
+ func pictureInPictureControllerTimeRangeForPlayback(_ controller: AVPictureInPictureController) -> CMTimeRange {
142
+ CMTimeRange(start: .negativeInfinity, duration: .positiveInfinity)
160
143
  }
161
144
 
162
- func pictureInPictureController(
163
- _ pictureInPictureController: AVPictureInPictureController,
164
- didTransitionToRenderSize newRenderSize: CMVideoDimensions
165
- ) {
166
- // Adjust rendering resolution for PiP window size
145
+ func pictureInPictureControllerIsPlaybackPaused(_ controller: AVPictureInPictureController) -> Bool {
146
+ paused
167
147
  }
168
148
 
169
- func pictureInPictureController(
170
- _ pictureInPictureController: AVPictureInPictureController,
171
- skipByInterval skipInterval: CMTime,
172
- completion: @escaping () -> Void
173
- ) {
174
- currentTime = CMTimeAdd(currentTime, skipInterval)
175
- completion()
149
+ func pictureInPictureController(_ controller: AVPictureInPictureController,
150
+ didTransitionToRenderSize newRenderSize: CMVideoDimensions) {}
151
+
152
+ func pictureInPictureController(_ controller: AVPictureInPictureController,
153
+ skipByInterval skipInterval: CMTime,
154
+ completion completionHandler: @escaping () -> Void) {
155
+ completionHandler()
176
156
  }
177
157
 
178
- func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(
179
- _ pictureInPictureController: AVPictureInPictureController
180
- ) -> Bool {
158
+ func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ controller: AVPictureInPictureController) -> Bool {
181
159
  false
182
160
  }
183
161
  }
184
162
  ```
185
163
 
186
- ## Interstitial Content
164
+ - Offer a PiP button only once support is confirmed.
165
+ - On tap, check `isPictureInPicturePossible` again; the layer, item, format or
166
+ playback state can still rule PiP out. Start only from the user's tap.
167
+ - The playback delegate must answer the system's questions about timing and
168
+ play state: `setPlaying`, the playable time range (an infinite range here, for
169
+ live content), whether playback is paused, render-size changes, skip requests
170
+ (call the completion), and whether background audio should be blocked.
187
171
 
188
- Use interstitials for ads, legal notices, and other timeline ranges with
189
- special playback rules. `AVPlayerViewController` can mark these ranges in the
190
- timeline and call delegate methods when interstitial playback begins and ends.
172
+ ## Interstitials
191
173
 
192
- ### Stream-Defined Interstitial Ranges
174
+ Interstitials cover ads, legal notices and any stretch of the timeline with
175
+ special rules. `AVPlayerViewController` marks them on the scrubber and tells its
176
+ delegate when one starts and ends.
193
177
 
194
178
  ```swift
179
+ import AVFoundation
195
180
  import AVKit
196
181
 
197
- func inspectInterstitials(for playerItem: AVPlayerItem) {
198
- for interstitial in playerItem.interstitialTimeRanges {
199
- let range = interstitial.timeRange
200
- print("Interstitial starts at \(range.start.seconds)s")
201
- }
182
+ @MainActor
183
+ func adBreaks(in item: AVPlayerItem) -> [CMTimeRange] {
184
+ item.interstitialTimeRanges.map(\.timeRange)
202
185
  }
203
- ```
204
-
205
- On iOS, valid scheduling sources are either stream-defined interstitials or
206
- app-created schedules through `AVPlayerInterstitialEventController`. For
207
- stream-defined breaks, the manifest or source media owns the schedule, and
208
- AVFoundation exposes it through `playerItem.interstitialTimeRanges` for
209
- inspection and delegate coordination. Do not assign to
210
- `playerItem.interstitialTimeRanges` directly as the scheduling recipe.
211
-
212
- ### App-Scheduled Interstitials
213
-
214
- For app-scheduled interstitial content, use `AVPlayerInterstitialEventController`
215
- from AVFoundation. Creating a controller schedule causes playback to ignore
216
- interstitial events present in the source media, so use it only when the app
217
- owns the schedule.
218
-
219
- ```swift
220
- import AVFoundation
221
-
222
- func setupInterstitialEvents(primaryPlayer: AVPlayer) {
223
- guard let primaryItem = primaryPlayer.currentItem else { return }
224
-
225
- let controller = AVPlayerInterstitialEventController(primaryPlayer: primaryPlayer)
226
-
227
- let adAsset = AVURLAsset(url: URL(string: "https://example.com/ad.m3u8")!)
228
- let adItem = AVPlayerItem(asset: adAsset)
229
186
 
230
- let event = AVPlayerInterstitialEvent(
231
- primaryItem: primaryItem,
232
- time: CMTime(seconds: 60, preferredTimescale: 600)
187
+ @MainActor
188
+ func scheduleMidroll(on player: AVPlayer, adURL: URL, at seconds: Double) -> AVPlayerInterstitialEventController? {
189
+ guard let main = player.currentItem else { return nil }
190
+ let controller = AVPlayerInterstitialEventController(primaryPlayer: player)
191
+ let midroll = AVPlayerInterstitialEvent(
192
+ primaryItem: main,
193
+ time: CMTime(seconds: seconds, preferredTimescale: 600)
233
194
  )
234
- event.templateItems = [adItem]
235
- event.restrictions = [.requiresPlaybackAtPreferredRateForAdvancement]
236
-
237
- controller.events = [event]
195
+ midroll.templateItems = [AVPlayerItem(asset: AVURLAsset(url: adURL))]
196
+ midroll.restrictions = [.requiresPlaybackAtPreferredRateForAdvancement]
197
+ controller.events = [midroll]
198
+ return controller
238
199
  }
239
- ```
240
-
241
- ### Delegate Callbacks for Interstitials
242
200
 
243
- ```swift
244
- func playerViewController(
245
- _ playerViewController: AVPlayerViewController,
246
- willPresent interstitial: AVInterstitialTimeRange
247
- ) {
248
- // About to play interstitial content - disable skip controls
249
- playerViewController.requiresLinearPlayback = true
250
- }
201
+ @MainActor
202
+ final class AdGate: NSObject, @preconcurrency AVPlayerViewControllerDelegate {
203
+ func playerViewController(_ viewer: AVPlayerViewController, willPresent adBreak: AVInterstitialTimeRange) {
204
+ viewer.requiresLinearPlayback = true
205
+ }
251
206
 
252
- func playerViewController(
253
- _ playerViewController: AVPlayerViewController,
254
- didPresent interstitial: AVInterstitialTimeRange
255
- ) {
256
- // Interstitial finished - re-enable seeking
257
- playerViewController.requiresLinearPlayback = false
207
+ func playerViewController(_ viewer: AVPlayerViewController, didPresent adBreak: AVInterstitialTimeRange) {
208
+ viewer.requiresLinearPlayback = false
209
+ }
258
210
  }
259
211
  ```
260
212
 
261
- ## Background Playback
262
-
263
- ### Info.plist Configuration
213
+ - Read stream-defined breaks by iterating `interstitialTimeRanges` and taking
214
+ each `timeRange`.
215
+ - On iOS there are two valid sources: interstitials defined in the stream, or
216
+ an `AVPlayerInterstitialEventController` schedule. Assigning
217
+ `interstitialTimeRanges` is not a scheduling mechanism.
218
+ - Once `AVPlayerInterstitialEventController(primaryPlayer:)` sets a schedule,
219
+ playback ignores interstitial events embedded in the source media. Use it only
220
+ when the app owns the ad schedule.
221
+ - Lock seeking in `willPresent` and unlock it in `didPresent`.
264
222
 
265
- Add the `audio` background mode:
266
-
267
- ```xml
268
- <key>UIBackgroundModes</key>
269
- <array>
270
- <string>audio</string>
271
- </array>
272
- ```
223
+ ## Background playback
273
224
 
274
- ### Runtime Behavior
225
+ - Info.plist: `UIBackgroundModes` array containing `audio`.
226
+ - Set the `.playback` category, activate when playback starts, and hold the
227
+ `AVPlayer` strongly for as long as audio should continue.
228
+ - iOS normally pauses video when its scene moves to the background unless the
229
+ video goes into PiP. Use PiP for video that keeps playing, and the background
230
+ audio mode for sound that should keep going once the app is no longer in front.
275
231
 
276
- Set the audio session category to `.playback`, activate it when playback begins,
277
- and keep a strong reference to the `AVPlayer` for as long as playback should
278
- continue. iOS generally pauses video when a scene backgrounds unless playback
279
- moves to PiP. Use PiP for background video continuity; use background audio mode
280
- for audio that should continue after the user leaves the app.
232
+ ## Errors
281
233
 
282
- ## Error Handling
234
+ ```swift
235
+ import AVFoundation
283
236
 
284
- ### Observing Player Item Status
237
+ enum PlaybackProblem {
238
+ case offline, timedOut, media(Error), unknown
239
+ }
285
240
 
286
- ```swift
287
- func observePlayerItem(_ item: AVPlayerItem) {
288
- let observation = item.observe(\.status) { item, _ in
289
- switch item.status {
290
- case .readyToPlay:
291
- // Safe to begin playback
292
- break
293
- case .failed:
294
- if let error = item.error as? NSError {
295
- handlePlaybackError(error)
241
+ @MainActor
242
+ func watchStatus(of item: AVPlayerItem, report: @escaping @MainActor (PlaybackProblem?) -> Void) -> NSKeyValueObservation {
243
+ item.observe(\.status, options: [.new]) { observed, _ in
244
+ MainActor.assumeIsolated {
245
+ switch observed.status {
246
+ case .readyToPlay:
247
+ report(nil)
248
+ case .failed:
249
+ report(classify(observed.error))
250
+ case .unknown:
251
+ return
252
+ @unknown default:
253
+ report(.unknown)
296
254
  }
297
- case .unknown:
298
- break
299
- @unknown default:
300
- break
301
255
  }
302
256
  }
303
257
  }
304
258
 
305
- func handlePlaybackError(_ error: NSError) {
306
- switch error.domain {
307
- case NSURLErrorDomain:
308
- // Network-related errors
309
- if error.code == NSURLErrorNotConnectedToInternet {
310
- // Show offline message
311
- } else if error.code == NSURLErrorTimedOut {
312
- // Offer retry
313
- }
314
- case AVFoundationErrorDomain:
315
- // AVFoundation-specific errors
316
- break
317
- default:
318
- break
259
+ func classify(_ error: Error?) -> PlaybackProblem {
260
+ guard let error = error as NSError? else { return .unknown }
261
+ switch (error.domain, error.code) {
262
+ case (NSURLErrorDomain, NSURLErrorNotConnectedToInternet): return .offline
263
+ case (NSURLErrorDomain, NSURLErrorTimedOut): return .timedOut
264
+ case (AVFoundationErrorDomain, _): return .media(error)
265
+ default: return .media(error)
319
266
  }
320
267
  }
321
- ```
322
268
 
323
- ### Observing Player Time Control Status
324
-
325
- ```swift
326
- func observePlayer(_ player: AVPlayer) {
327
- let observation = player.observe(\.timeControlStatus) { player, _ in
328
- switch player.timeControlStatus {
329
- case .paused:
330
- break
331
- case .playing:
332
- break
333
- case .waitingToPlayAtSpecifiedRate:
334
- if let reason = player.reasonForWaitingToPlay {
335
- switch reason {
336
- case .toMinimizeStalls:
337
- // Buffering - show spinner
338
- break
339
- case .evaluatingBufferingRate:
340
- break
341
- case .noItemToPlay:
342
- break
343
- default:
344
- break
345
- }
269
+ @MainActor
270
+ func watchStalls(of player: AVPlayer, spinner: @escaping @MainActor (Bool) -> Void) -> NSKeyValueObservation {
271
+ player.observe(\.timeControlStatus, options: [.new]) { observed, _ in
272
+ MainActor.assumeIsolated {
273
+ switch observed.timeControlStatus {
274
+ case .waitingToPlayAtSpecifiedRate:
275
+ spinner(observed.reasonForWaitingToPlay == .toMinimizeStalls)
276
+ case .playing, .paused:
277
+ spinner(false)
278
+ @unknown default:
279
+ spinner(false)
346
280
  }
347
- @unknown default:
348
- break
349
281
  }
350
282
  }
351
283
  }
352
284
  ```
353
285
 
354
- ### Handling AVKitError
286
+ - `AVPlayerItem.status` moves from `.unknown` to `.readyToPlay` or `.failed`;
287
+ on failure read `item.error`.
288
+ - Split network errors (`NSURLErrorDomain`: show an offline message for
289
+ `NSURLErrorNotConnectedToInternet`, offer a retry for `NSURLErrorTimedOut`)
290
+ from media errors in `AVFoundationErrorDomain`.
291
+ - `AVPlayer.timeControlStatus` is `.paused`, `.playing` or
292
+ `.waitingToPlayAtSpecifiedRate`. While waiting, `reasonForWaitingToPlay` says
293
+ why: `.toMinimizeStalls` (show a buffering indicator),
294
+ `.evaluatingBufferingRate` or `.noItemToPlay`.
355
295
 
356
- ```swift
357
- import AVKit
296
+ `AVKitError` codes:
358
297
 
359
- func handleAVKitError(_ error: Error) {
360
- guard let avkitError = error as? AVKitError else { return }
361
-
362
- switch avkitError.code {
363
- case .pictureInPictureStartFailed:
364
- // Check audio session/background mode, support, current possibility,
365
- // and whether the player layer or content source is active.
366
- break
367
- case .contentRatingUnknown:
368
- // Content rating could not be determined
369
- break
370
- case .contentDisallowedByPasscode:
371
- // Parental controls restrict this content
372
- break
373
- case .contentDisallowedByProfile:
374
- // MDM profile restricts this content
375
- break
376
- case .unknown:
377
- break
378
- @unknown default:
379
- break
380
- }
381
- }
382
- ```
298
+ | Code | Meaning |
299
+ |---|---|
300
+ | `.pictureInPictureStartFailed` | PiP could not start; verify the playback category, the `audio` background mode, device support, `isPictureInPicturePossible`, and that the layer or content source feeding PiP is still active |
301
+ | `.contentRatingUnknown` | the content's rating could not be determined |
302
+ | `.contentDisallowedByPasscode` | blocked by parental restrictions |
303
+ | `.contentDisallowedByProfile` | blocked by a device management profile |
304
+ | `.unknown` | unspecified failure |
383
305
 
384
- ## Observing Playback State
385
-
386
- ### Periodic Time Observer
306
+ ## Observing playback
387
307
 
388
308
  ```swift
389
- func addPeriodicObserver(to player: AVPlayer) -> Any {
390
- let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
391
- return player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
392
- let seconds = CMTimeGetSeconds(time)
393
- // Update progress UI
394
- }
395
- }
396
-
397
- // Remove when done
398
- // player.removeTimeObserver(observer)
399
- ```
400
-
401
- ### Boundary Time Observer
402
-
403
- Trigger actions at specific points in the timeline:
309
+ import AVFoundation
404
310
 
405
- ```swift
406
- func addBoundaryObserver(to player: AVPlayer, at times: [CMTime]) -> Any {
407
- let timeValues = times.map { NSValue(time: $0) }
408
- return player.addBoundaryTimeObserver(forTimes: timeValues, queue: .main) {
409
- // Reached a boundary time
311
+ @MainActor
312
+ final class ChapterClock {
313
+ private let player: AVPlayer
314
+ private var ticker: Any?
315
+ private var markers: Any?
316
+ private var endObserver: NSObjectProtocol?
317
+
318
+ init(player: AVPlayer) {
319
+ self.player = player
320
+ }
321
+
322
+ func start(chapters: [CMTime], onTick: @escaping @Sendable (CMTime) -> Void, onChapter: @escaping @Sendable () -> Void) {
323
+ let halfSecond = CMTime(value: 1, timescale: 2)
324
+ ticker = player.addPeriodicTimeObserver(forInterval: halfSecond,
325
+ queue: .main, using: onTick)
326
+ markers = player.addBoundaryTimeObserver(forTimes: chapters.map { NSValue(time: $0) },
327
+ queue: .main, using: onChapter)
328
+ let center = NotificationCenter.default
329
+ endObserver = center.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem,
330
+ queue: .main) { _ in onChapter() }
331
+ }
332
+
333
+ func stop() {
334
+ if let ticker { player.removeTimeObserver(ticker) }
335
+ if let markers { player.removeTimeObserver(markers) }
336
+ if let endObserver { NotificationCenter.default.removeObserver(endObserver) }
337
+ ticker = nil
338
+ markers = nil
339
+ endObserver = nil
410
340
  }
411
341
  }
412
342
  ```
413
343
 
414
- ### Observing Playback End
415
-
416
- ```swift
417
- func observePlaybackEnd(for item: AVPlayerItem) {
418
- NotificationCenter.default.addObserver(
419
- forName: .AVPlayerItemDidPlayToEndTime,
420
- object: item,
421
- queue: .main
422
- ) { _ in
423
- // Playback finished - show replay button or load next item
424
- }
425
- }
426
- ```
344
+ - `addPeriodicTimeObserver(forInterval:queue:using:)` returns a token; pass it
345
+ to `removeTimeObserver(_:)` when done.
346
+ - `addBoundaryTimeObserver(forTimes:queue:using:)` fires at the listed times,
347
+ wrapped with `NSValue(time:)`.
348
+ - The end of an item is announced with `.AVPlayerItemDidPlayToEndTime`, scoped
349
+ to that item.
427
350
 
428
- ## Video Frame Analysis
351
+ ## Video frame analysis
429
352
 
430
- `AVPlayerViewController` supports automatic analysis of paused video frames
431
- for text recognition, visual search, and subject lifting.
353
+ `allowsVideoFrameAnalysis` (default `true`, iOS 16+) lets the user interact
354
+ with a paused frame: select text, look things up, lift a subject. Narrow it with
355
+ `videoFrameAnalysisTypes` (iOS 17+), whose options include `.text`,
356
+ `.visualSearch`, `.subject` and `.machineReadableCode`, plus `.default`.
432
357
 
433
358
  ```swift
434
- let playerVC = AVPlayerViewController()
435
-
436
- // Enable frame analysis (default is true)
437
- playerVC.allowsVideoFrameAnalysis = true
359
+ import AVKit
438
360
 
439
- // Configure which analysis types to perform
440
- playerVC.videoFrameAnalysisTypes = [.text, .visualSearch, .subject, .machineReadableCode]
361
+ @MainActor
362
+ func onlyTextAndCodes(_ viewer: AVPlayerViewController) {
363
+ viewer.allowsVideoFrameAnalysis = true
364
+ viewer.videoFrameAnalysisTypes = [.text, .machineReadableCode]
365
+ }
441
366
  ```
442
367
 
443
- ## HDR Content
368
+ ## HDR
444
369
 
445
- Control how HDR video content renders:
370
+ `AVPlayerViewController.preferredDisplayDynamicRange` (iOS 26+) picks how HDR
371
+ content is shown:
446
372
 
447
- ```swift
448
- // Let the system decide the best rendering
449
- playerVC.preferredDisplayDynamicRange = .automatic
373
+ | Value | Behavior |
374
+ |---|---|
375
+ | `.automatic` | the system decides (default) |
376
+ | `.standard` | force SDR |
377
+ | `.high` | full HDR |
378
+ | `.constrainedHigh` | limited HDR, useful when HDR sits next to SDR content |
450
379
 
451
- // Force SDR rendering
452
- playerVC.preferredDisplayDynamicRange = .standard
453
-
454
- // Full HDR
455
- playerVC.preferredDisplayDynamicRange = .high
380
+ ```swift
381
+ import AVKit
456
382
 
457
- // Constrained HDR (tone-mapped HDR)
458
- playerVC.preferredDisplayDynamicRange = .constrainedHigh
383
+ @MainActor
384
+ func calmHDR(_ viewer: AVPlayerViewController) {
385
+ viewer.preferredDisplayDynamicRange = .constrainedHigh
386
+ }
459
387
  ```
460
388
 
461
- ## SwiftUI Player Manager
462
-
463
- An `@Observable` manager for coordinating playback state across a SwiftUI app:
389
+ ## SwiftUI player model
464
390
 
465
391
  ```swift
466
- import AVKit
467
392
  import AVFoundation
393
+ import AVKit
394
+ import Observation
395
+ import SwiftUI
468
396
 
469
397
  @Observable
470
398
  @MainActor
471
- final class VideoPlayerManager {
399
+ final class EpisodePlayer {
472
400
  private(set) var player: AVPlayer?
401
+ private(set) var position: Double = 0
473
402
  private(set) var isPlaying = false
474
- private(set) var currentTime: TimeInterval = 0
475
- private(set) var duration: TimeInterval = 0
476
403
  private(set) var isBuffering = false
477
- private(set) var error: Error?
478
-
479
- private var timeObserver: Any?
480
- private var statusObservation: NSKeyValueObservation?
481
- private var timeControlObservation: NSKeyValueObservation?
482
-
483
- func loadMedia(url: URL) {
484
- cleanup()
485
-
486
- let item = AVPlayerItem(url: url)
487
- let newPlayer = AVPlayer(playerItem: item)
488
- player = newPlayer
489
-
490
- observeStatus(of: item)
491
- observeTimeControl(of: newPlayer)
492
- addPeriodicTimeObserver(to: newPlayer)
493
- }
494
-
495
- func play() {
496
- player?.play()
404
+ private(set) var length: Double = 0
405
+ private(set) var failure: String?
406
+
407
+ @ObservationIgnored private var tick: Any?
408
+ @ObservationIgnored private var observations: [NSKeyValueObservation] = []
409
+
410
+ func load(_ url: URL) {
411
+ tearDown()
412
+ let item = AVPlayerItem(asset: AVURLAsset(url: url))
413
+ let fresh = AVPlayer(playerItem: item)
414
+ player = fresh
415
+
416
+ observations.append(item.observe(\.status) { [weak self] observed, _ in
417
+ let status = observed.status
418
+ let seconds = CMTimeGetSeconds(observed.duration)
419
+ let message = observed.error?.localizedDescription
420
+ Task { @MainActor [weak self] in
421
+ guard let model = self else { return }
422
+ if status == .readyToPlay { model.length = seconds.isFinite ? seconds : 0 }
423
+ if status == .failed { model.failure = message }
424
+ }
425
+ })
426
+ observations.append(fresh.observe(\.timeControlStatus) { [weak self] observed, _ in
427
+ let state = observed.timeControlStatus
428
+ Task { @MainActor in
429
+ self?.isPlaying = state == .playing
430
+ self?.isBuffering = state == .waitingToPlayAtSpecifiedRate
431
+ }
432
+ })
433
+ let every = CMTime(value: 1, timescale: 2)
434
+ tick = fresh.addPeriodicTimeObserver(forInterval: every, queue: .main) { [weak self] now in
435
+ let seconds = now.seconds
436
+ Task { @MainActor in self?.position = seconds }
437
+ }
497
438
  }
498
439
 
499
- func pause() {
500
- player?.pause()
440
+ func toggle() {
441
+ guard let player else { return }
442
+ isPlaying ? player.pause() : player.play()
501
443
  }
502
444
 
503
- func seek(to time: TimeInterval) {
504
- let cmTime = CMTime(seconds: time, preferredTimescale: 600)
505
- player?.seek(to: cmTime, toleranceBefore: .zero, toleranceAfter: .zero)
445
+ func jump(to seconds: Double) {
446
+ player?.seek(to: CMTime(seconds: seconds, preferredTimescale: 600), toleranceBefore: .zero, toleranceAfter: .zero)
506
447
  }
507
448
 
508
- func cleanup() {
509
- if let observer = timeObserver, let player {
510
- player.removeTimeObserver(observer)
511
- }
512
- timeObserver = nil
513
- statusObservation?.invalidate()
514
- timeControlObservation?.invalidate()
449
+ func tearDown() {
450
+ if let tick, let player { player.removeTimeObserver(tick) }
451
+ tick = nil
452
+ observations.forEach { $0.invalidate() }
453
+ observations.removeAll()
515
454
  player = nil
516
- error = nil
517
455
  isPlaying = false
518
- currentTime = 0
519
- duration = 0
456
+ isBuffering = false
457
+ position = 0
458
+ length = 0
459
+ failure = nil
520
460
  }
521
461
 
522
- private func observeStatus(of item: AVPlayerItem) {
523
- statusObservation = item.observe(\.status) { [weak self] item, _ in
524
- Task { @MainActor in
525
- guard let self else { return }
526
- switch item.status {
527
- case .readyToPlay:
528
- self.duration = CMTimeGetSeconds(item.duration)
529
- case .failed:
530
- self.error = item.error
531
- case .unknown:
532
- break
533
- @unknown default:
534
- break
535
- }
536
- }
537
- }
538
- }
539
-
540
- private func observeTimeControl(of player: AVPlayer) {
541
- timeControlObservation = player.observe(\.timeControlStatus) { [weak self] player, _ in
542
- Task { @MainActor in
543
- guard let self else { return }
544
- self.isPlaying = player.timeControlStatus == .playing
545
- self.isBuffering = player.timeControlStatus == .waitingToPlayAtSpecifiedRate
546
- }
547
- }
548
- }
549
-
550
- private func addPeriodicTimeObserver(to player: AVPlayer) {
551
- let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
552
- timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
553
- Task { @MainActor in
554
- self?.currentTime = CMTimeGetSeconds(time)
555
- }
556
- }
557
- }
558
-
559
- deinit {
560
- if let observer = timeObserver, let player {
561
- player.removeTimeObserver(observer)
562
- }
462
+ isolated deinit {
463
+ if let tick, let player { player.removeTimeObserver(tick) }
563
464
  }
564
465
  }
565
- ```
566
-
567
- ### Using the Manager in SwiftUI
568
466
 
569
- ```swift
570
- import SwiftUI
571
- import AVKit
572
-
573
- struct VideoScreen: View {
574
- @State private var manager = VideoPlayerManager()
467
+ struct EpisodeScreen: View {
468
+ let episodeURL: URL
469
+ @State private var model = EpisodePlayer()
575
470
 
576
471
  var body: some View {
577
- VStack {
578
- if let player = manager.player {
579
- VideoPlayer(player: player)
580
- .frame(height: 300)
581
-
582
- HStack {
583
- Button(manager.isPlaying ? "Pause" : "Play") {
584
- manager.isPlaying ? manager.pause() : manager.play()
585
- }
586
-
587
- Text("\(Int(manager.currentTime))s / \(Int(manager.duration))s")
588
- }
472
+ VStack(spacing: 12) {
473
+ if let current = model.player {
474
+ VideoPlayer(player: current)
475
+ .aspectRatio(16 / 9, contentMode: .fit)
589
476
  }
590
-
591
- if manager.isBuffering {
592
- ProgressView("Buffering...")
477
+ HStack {
478
+ Button(model.isPlaying ? "Pause" : "Play") { model.toggle() }
479
+ Text("\(Int(model.position))s / \(Int(model.length))s").monospacedDigit()
480
+ if model.isBuffering { ProgressView() }
593
481
  }
594
-
595
- if let error = manager.error {
596
- Text("Error: \(error.localizedDescription)")
597
- .foregroundStyle(.red)
482
+ if let failure = model.failure {
483
+ Text(failure).foregroundStyle(.red)
598
484
  }
599
485
  }
600
- .task {
601
- manager.loadMedia(url: URL(string: "https://example.com/video.m3u8")!)
602
- }
486
+ .task { model.load(episodeURL) }
603
487
  }
604
488
  }
605
489
  ```
606
490
 
607
- ## AVPlayerViewController in UIViewControllerRepresentable
491
+ - `load(_:)` clears previous state, creates the item and player, and installs
492
+ the status observation, the `timeControlStatus` observation and a periodic
493
+ time observer.
494
+ - Seeking uses zero tolerance on both sides for frame-accurate jumps, with a
495
+ timescale of 600.
496
+ - `tearDown()` removes the time observer, invalidates each
497
+ `NSKeyValueObservation` and resets the published state; the isolated
498
+ `deinit` removes the time observer as well.
499
+ - Observer closures capture `self` weakly and hop to the main actor with
500
+ `Task { @MainActor in ... }`. Duration is read with `CMTimeGetSeconds` once
501
+ the item is ready; buffering is `.waitingToPlayAtSpecifiedRate`.
608
502
 
609
- Full-featured wrapper exposing delegate callbacks, PiP, and playback speed to
610
- SwiftUI. Prefer this over `VideoPlayer` when you need fine-grained control.
503
+ ## Wrapping AVPlayerViewController for SwiftUI
504
+
505
+ When the app needs the delegate, PiP settings or speed options, a representable
506
+ around `AVPlayerViewController` gives more control than `VideoPlayer`.
611
507
 
612
508
  ```swift
613
- import SwiftUI
614
509
  import AVKit
510
+ import SwiftUI
615
511
 
616
- struct SystemPlayerView: UIViewControllerRepresentable {
512
+ struct FullPlayer: UIViewControllerRepresentable {
617
513
  let player: AVPlayer
618
- var allowsPiP: Bool = true
619
- var autoStartPiPFromInline: Bool = false
620
- var speeds: [AVPlaybackSpeed] = AVPlaybackSpeed.systemDefaultSpeeds
514
+ var speeds = AVPlaybackSpeed.systemDefaultSpeeds
515
+ var allowsPiP = true
621
516
 
622
- func makeUIViewController(context: Context) -> AVPlayerViewController {
623
- let controller = AVPlayerViewController()
624
- controller.player = player
625
- controller.allowsPictureInPicturePlayback = allowsPiP
626
- controller.canStartPictureInPictureAutomaticallyFromInline = autoStartPiPFromInline
627
- controller.speeds = speeds
628
- controller.delegate = context.coordinator
629
- return controller
630
- }
517
+ func makeCoordinator() -> Coordinator { Coordinator() }
631
518
 
632
- func updateUIViewController(_ controller: AVPlayerViewController, context: Context) {
633
- controller.player = player
519
+ func makeUIViewController(context ctx: Context) -> AVPlayerViewController {
520
+ let viewer = AVPlayerViewController()
521
+ viewer.player = player
522
+ viewer.allowsPictureInPicturePlayback = allowsPiP
523
+ viewer.canStartPictureInPictureAutomaticallyFromInline = allowsPiP
524
+ viewer.speeds = speeds
525
+ viewer.delegate = ctx.coordinator
526
+ return viewer
634
527
  }
635
528
 
636
- func makeCoordinator() -> Coordinator { Coordinator() }
529
+ func updateUIViewController(_ viewer: AVPlayerViewController, context: Context) {
530
+ viewer.player = player
531
+ }
637
532
 
638
- final class Coordinator: NSObject, AVPlayerViewControllerDelegate {
639
- func playerViewController(
640
- _ playerViewController: AVPlayerViewController,
641
- willBeginFullScreenPresentationWithAnimationCoordinator coordinator: any UIViewControllerTransitionCoordinator
642
- ) {
643
- // Handle full-screen entry
644
- }
533
+ @MainActor
534
+ final class Coordinator: NSObject, @preconcurrency AVPlayerViewControllerDelegate {
535
+ func playerViewController(_ viewer: AVPlayerViewController,
536
+ willBeginFullScreenPresentationWithAnimationCoordinator transition: any UIViewControllerTransitionCoordinator) {}
645
537
 
646
- func playerViewController(
647
- _ playerViewController: AVPlayerViewController,
648
- willEndFullScreenPresentationWithAnimationCoordinator coordinator: any UIViewControllerTransitionCoordinator
649
- ) {
650
- // Handle full-screen exit
651
- }
538
+ func playerViewController(_ viewer: AVPlayerViewController,
539
+ willEndFullScreenPresentationWithAnimationCoordinator transition: any UIViewControllerTransitionCoordinator) {}
652
540
 
653
- func playerViewController(
654
- _ playerViewController: AVPlayerViewController,
655
- restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
656
- ) {
657
- completionHandler(true)
541
+ func playerViewController(_ viewer: AVPlayerViewController,
542
+ restoreUserInterfaceForPictureInPictureStopWithCompletionHandler done: @escaping (Bool) -> Void) {
543
+ done(true)
658
544
  }
659
545
 
660
- func playerViewController(
661
- _ playerViewController: AVPlayerViewController,
662
- failedToStartPictureInPictureWithError error: any Error
663
- ) {
664
- print("PiP failed: \(error)")
665
- }
546
+ func playerViewController(_ viewer: AVPlayerViewController,
547
+ failedToStartPictureInPictureWithError reason: any Error) {}
666
548
  }
667
549
  }
668
550
  ```