@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,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
  ```