@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.
- package/CHANGELOG.md +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/council/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -3,41 +3,42 @@
|
|
|
3
3
|
"evals": [
|
|
4
4
|
{
|
|
5
5
|
"id": 1,
|
|
6
|
-
"prompt": "
|
|
7
|
-
"expected_output": "A
|
|
6
|
+
"prompt": "We are adding Apple Music to a running-coach app. What do we have to configure in the portal and in the project before MusicKit works, how should we ask for permission, and do we need anything extra so music keeps going when the screen locks? A teammate says we also need the com.apple.developer.musickit entitlement or authorization will crash.",
|
|
7
|
+
"expected_output": "A setup answer that names the App Service and usage string, explains request() versus currentStatus, treats background audio as conditional, and rejects the entitlement claim.",
|
|
8
8
|
"files": [],
|
|
9
9
|
"assertions": [
|
|
10
|
-
"
|
|
11
|
-
"Requires NSAppleMusicUsageDescription
|
|
12
|
-
"Explains that MusicAuthorization.request()
|
|
13
|
-
"Does not claim
|
|
14
|
-
"
|
|
10
|
+
"Says to enable the MusicKit App Service on the app's explicit App ID so developer tokens are generated automatically",
|
|
11
|
+
"Requires NSAppleMusicUsageDescription in Info.plist",
|
|
12
|
+
"Explains that MusicAuthorization.request() prompts only when needed and MusicAuthorization.currentStatus reads the state without prompting",
|
|
13
|
+
"Does not claim a com.apple.developer.musickit entitlement is required on iOS or that its absence crashes authorization",
|
|
14
|
+
"Adds audio to UIBackgroundModes only because playback must continue in the background"
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"id": 2,
|
|
19
|
-
"prompt": "
|
|
20
|
-
"expected_output": "
|
|
19
|
+
"prompt": "Build the flow where a user searches the Apple Music catalog, taps a song and it plays inside our app. Users without Apple Music should be invited to subscribe instead of seeing a broken play button.",
|
|
20
|
+
"expected_output": "Catalog search, a subscription gate before playback, app-scoped playback, and a correctly gated subscription offer.",
|
|
21
21
|
"files": [],
|
|
22
22
|
"assertions": [
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"Checks canBecomeSubscriber before
|
|
27
|
-
"
|
|
23
|
+
"Searches with MusicCatalogSearchRequest and does not require a subscription for searching",
|
|
24
|
+
"Gates playback on MusicSubscription.current canPlayCatalogContent",
|
|
25
|
+
"Plays through ApplicationMusicPlayer.shared by setting the queue and calling play()",
|
|
26
|
+
"Checks canBecomeSubscriber before showing the offer",
|
|
27
|
+
"Presents the offer with musicSubscriptionOffer(isPresented:options:onLoadCompletion:)"
|
|
28
28
|
]
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"id": 3,
|
|
32
|
-
"prompt": "
|
|
33
|
-
"expected_output": "
|
|
32
|
+
"prompt": "Add a Top Charts screen, a button that saves a song to the user's library, and make sure the Lock Screen shows what is playing. Should we also build a CarPlay scene and an AVKit player while we are at it?",
|
|
33
|
+
"expected_output": "Charts with an explicit genre argument, guarded library writes, correct Now Playing ownership, and no unrelated AVKit or CarPlay work.",
|
|
34
34
|
"files": [],
|
|
35
35
|
"assertions": [
|
|
36
|
-
"Uses
|
|
37
|
-
"
|
|
38
|
-
"Checks
|
|
39
|
-
"
|
|
40
|
-
"
|
|
36
|
+
"Uses MusicCatalogChartsRequest(genre:kinds:types:) and passes genre explicitly, for example nil",
|
|
37
|
+
"Confirms authorization before library mutations",
|
|
38
|
+
"Checks hasCloudLibraryEnabled before adding to the library",
|
|
39
|
+
"States that ApplicationMusicPlayer publishes Now Playing automatically for Apple Music content",
|
|
40
|
+
"Limits MediaPlayer guidance to custom audio metadata and remote commands",
|
|
41
|
+
"Does not add AVKit player or CarPlay scene implementation"
|
|
41
42
|
]
|
|
42
43
|
}
|
|
43
44
|
]
|
|
@@ -1,383 +1,294 @@
|
|
|
1
|
-
# MusicKit
|
|
1
|
+
# MusicKit Patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Longer examples for the `musickit-audio` skill. Its SKILL.md covers setup,
|
|
4
|
+
authorization, subscription checks and the basic player; this file builds on
|
|
5
|
+
that.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## SwiftUI integration
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- [Genre and Chart Browsing](#genre-and-chart-browsing)
|
|
9
|
-
- [Library Management](#library-management)
|
|
10
|
-
- [Playlist Access](#playlist-access)
|
|
11
|
-
- [Now Playing Session](#now-playing-session)
|
|
12
|
-
- [Background Audio Configuration](#background-audio-configuration)
|
|
9
|
+
### A shared player model
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Music Player Manager with `@Observable`
|
|
11
|
+
One `@Observable` object on the main actor owns access and subscription state
|
|
12
|
+
and drives the player. Views read it from the environment.
|
|
17
13
|
|
|
18
14
|
```swift
|
|
19
15
|
import MusicKit
|
|
20
|
-
import
|
|
16
|
+
import Observation
|
|
21
17
|
|
|
22
|
-
@Observable
|
|
23
18
|
@MainActor
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let status = await MusicAuthorization.request()
|
|
36
|
-
guard status == .authorized else { return }
|
|
37
|
-
|
|
38
|
-
// Check subscription
|
|
39
|
-
if let subscription = try? await MusicSubscription.current {
|
|
40
|
-
hasSubscription = subscription.canPlayCatalogContent
|
|
41
|
-
}
|
|
19
|
+
@Observable
|
|
20
|
+
final class ListeningSession {
|
|
21
|
+
private(set) var accessGranted = false
|
|
22
|
+
private(set) var catalogPlayable = false
|
|
23
|
+
private var membershipWatcher: Task<Void, Never>?
|
|
24
|
+
|
|
25
|
+
func prepare() async {
|
|
26
|
+
accessGranted = await MusicAuthorization.request() == .authorized
|
|
27
|
+
guard accessGranted else { return }
|
|
28
|
+
|
|
29
|
+
catalogPlayable = (try? await MusicSubscription.current.canPlayCatalogContent) ?? false
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for await
|
|
46
|
-
|
|
31
|
+
membershipWatcher = Task { [weak self] in
|
|
32
|
+
let updates = MusicSubscription.subscriptionUpdates
|
|
33
|
+
for await membership in updates {
|
|
34
|
+
self?.catalogPlayable = membership.canPlayCatalogContent
|
|
47
35
|
}
|
|
48
36
|
}
|
|
49
37
|
}
|
|
50
38
|
|
|
51
|
-
func play(_
|
|
52
|
-
guard
|
|
53
|
-
player
|
|
39
|
+
func play(_ track: Song) async throws {
|
|
40
|
+
guard catalogPlayable else { return }
|
|
41
|
+
let player = ApplicationMusicPlayer.shared
|
|
42
|
+
player.queue = [track]
|
|
54
43
|
try await player.play()
|
|
55
|
-
currentSong = song
|
|
56
|
-
isPlaying = true
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
func
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} else {
|
|
64
|
-
Task {
|
|
65
|
-
try? await player.play()
|
|
66
|
-
isPlaying = true
|
|
67
|
-
}
|
|
68
|
-
}
|
|
46
|
+
func playOrPause() async throws {
|
|
47
|
+
let player = ApplicationMusicPlayer.shared
|
|
48
|
+
let isRunning = player.state.playbackStatus == .playing
|
|
49
|
+
if isRunning { player.pause() } else { try await player.play() }
|
|
69
50
|
}
|
|
70
51
|
|
|
71
|
-
func
|
|
72
|
-
try
|
|
52
|
+
func next() async throws {
|
|
53
|
+
try await ApplicationMusicPlayer.shared.skipToNextEntry()
|
|
73
54
|
}
|
|
74
55
|
}
|
|
75
56
|
```
|
|
76
57
|
|
|
77
|
-
|
|
58
|
+
Install it once near the root with `.environment(session)` and call
|
|
59
|
+
`prepare()` from a `.task`. The model does not keep the player in a property:
|
|
60
|
+
`ApplicationMusicPlayer` is not `Sendable`, and Swift 6 refuses an async call
|
|
61
|
+
such as `play()` on one reached through main-actor state. Each method reads
|
|
62
|
+
`ApplicationMusicPlayer.shared` into a local instead.
|
|
78
63
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
### Mini player bar
|
|
65
|
+
|
|
66
|
+
`ArtworkImage` renders MusicKit artwork at a requested size.
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
```swift
|
|
69
|
+
struct NowPlayingBar: View {
|
|
70
|
+
@Environment(ListeningSession.self) private var session
|
|
71
|
+
let track: Song
|
|
85
72
|
|
|
86
73
|
var body: some View {
|
|
87
|
-
HStack {
|
|
88
|
-
if let
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.clipShape(.rect(cornerRadius: 6))
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
VStack(alignment: .leading) {
|
|
95
|
-
Text(song.title)
|
|
96
|
-
.font(.subheadline)
|
|
97
|
-
.lineLimit(1)
|
|
98
|
-
Text(song.artistName)
|
|
99
|
-
.font(.caption)
|
|
100
|
-
.foregroundStyle(.secondary)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
Spacer()
|
|
104
|
-
|
|
105
|
-
Button {
|
|
106
|
-
manager.togglePlayPause()
|
|
107
|
-
} label: {
|
|
108
|
-
Image(systemName: manager.isPlaying ? "pause.fill" : "play.fill")
|
|
109
|
-
.font(.title2)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
Button {
|
|
113
|
-
Task { await manager.skip() }
|
|
114
|
-
} label: {
|
|
115
|
-
Image(systemName: "forward.fill")
|
|
116
|
-
}
|
|
74
|
+
HStack(alignment: .center, spacing: 10) {
|
|
75
|
+
if let cover = track.artwork {
|
|
76
|
+
ArtworkImage(cover, width: 48, height: 48)
|
|
77
|
+
.clipShape(.rect(cornerRadius: 8))
|
|
117
78
|
}
|
|
79
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
80
|
+
Text(track.title).font(.subheadline.bold())
|
|
81
|
+
Text(track.artistName).foregroundStyle(.secondary).font(.caption)
|
|
82
|
+
}
|
|
83
|
+
Spacer(minLength: 8)
|
|
84
|
+
Button("Play or pause", systemImage: "playpause.fill") {
|
|
85
|
+
Task { try? await session.playOrPause() }
|
|
86
|
+
}
|
|
87
|
+
.labelStyle(.iconOnly)
|
|
118
88
|
}
|
|
119
89
|
.padding(.horizontal)
|
|
120
|
-
.frame(height: 60)
|
|
121
90
|
}
|
|
122
91
|
}
|
|
123
92
|
```
|
|
124
93
|
|
|
125
|
-
### Search
|
|
94
|
+
### Search screen
|
|
95
|
+
|
|
96
|
+
Search as the text changes and clear the list when the field is empty.
|
|
126
97
|
|
|
127
98
|
```swift
|
|
128
|
-
struct
|
|
129
|
-
@State private var
|
|
130
|
-
@State private var
|
|
131
|
-
@Environment(MusicPlayerManager.self) private var manager
|
|
99
|
+
struct CatalogFinder: View {
|
|
100
|
+
@State private var phrase = ""
|
|
101
|
+
@State private var hits: MusicItemCollection<Song> = []
|
|
132
102
|
|
|
133
103
|
var body: some View {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if let artwork = song.artwork {
|
|
141
|
-
ArtworkImage(artwork, width: 50, height: 50)
|
|
142
|
-
.clipShape(.rect(cornerRadius: 4))
|
|
143
|
-
}
|
|
144
|
-
VStack(alignment: .leading) {
|
|
145
|
-
Text(song.title)
|
|
146
|
-
Text(song.artistName)
|
|
147
|
-
.font(.caption)
|
|
148
|
-
.foregroundStyle(.secondary)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
.searchable(text: $searchText, prompt: "Search Apple Music")
|
|
154
|
-
.onChange(of: searchText) {
|
|
155
|
-
Task { await search() }
|
|
156
|
-
}
|
|
157
|
-
.navigationTitle("Search")
|
|
104
|
+
List(hits) { song in
|
|
105
|
+
LabeledContent(song.title, value: song.artistName)
|
|
106
|
+
}
|
|
107
|
+
.searchable(text: $phrase, prompt: "Artists, songs, albums")
|
|
108
|
+
.onChange(of: phrase) { _, newPhrase in
|
|
109
|
+
Task { await refresh(for: newPhrase) }
|
|
158
110
|
}
|
|
159
111
|
}
|
|
160
112
|
|
|
161
|
-
private func
|
|
162
|
-
guard !
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
var request = MusicCatalogSearchRequest(term: searchText, types: [Song.self])
|
|
167
|
-
request.limit = 25
|
|
168
|
-
if let response = try? await request.response() {
|
|
169
|
-
results = response.songs
|
|
170
|
-
}
|
|
113
|
+
private func refresh(for text: String) async {
|
|
114
|
+
guard !text.isEmpty else { hits = []; return }
|
|
115
|
+
var search = MusicCatalogSearchRequest(term: text, types: [Song.self])
|
|
116
|
+
search.limit = 25
|
|
117
|
+
hits = (try? await search.response().songs) ?? []
|
|
171
118
|
}
|
|
172
119
|
}
|
|
173
120
|
```
|
|
174
121
|
|
|
175
|
-
## Genre and
|
|
122
|
+
## Genre and chart browsing
|
|
176
123
|
|
|
177
|
-
|
|
124
|
+
Pass `genre` explicitly, even when you want all genres, so the intent is clear
|
|
125
|
+
at the call site.
|
|
178
126
|
|
|
179
127
|
```swift
|
|
180
|
-
func
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let response = try await request.response()
|
|
188
|
-
return response.songCharts.first?.items ?? []
|
|
128
|
+
func topTracks() async throws -> MusicItemCollection<Song> {
|
|
129
|
+
let wanted: [MusicCatalogChartKind] = [.mostPlayed]
|
|
130
|
+
var charts = MusicCatalogChartsRequest(genre: nil, kinds: wanted, types: [Song.self])
|
|
131
|
+
charts.limit = 50
|
|
132
|
+
let result = try await charts.response()
|
|
133
|
+
guard let chart = result.songCharts.first else { return [] }
|
|
134
|
+
return chart.items
|
|
189
135
|
}
|
|
190
136
|
```
|
|
191
137
|
|
|
192
|
-
|
|
138
|
+
A simple way to browse by genre is to search with the genre's name:
|
|
193
139
|
|
|
194
140
|
```swift
|
|
195
|
-
func
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return response.songs
|
|
141
|
+
func tracks(tagged style: Genre) async throws -> MusicItemCollection<Song> {
|
|
142
|
+
let label = style.name
|
|
143
|
+
let search = MusicCatalogSearchRequest(term: label, types: [Song.self])
|
|
144
|
+
return try await search.response().songs
|
|
200
145
|
}
|
|
201
146
|
```
|
|
202
147
|
|
|
203
|
-
## Library
|
|
204
|
-
|
|
205
|
-
Library writes require MusicKit authorization and an enabled iCloud Music Library.
|
|
206
|
-
Check `MusicSubscription.current.hasCloudLibraryEnabled` before adding items or
|
|
207
|
-
modifying playlists.
|
|
148
|
+
## Library management
|
|
208
149
|
|
|
209
|
-
|
|
150
|
+
Writing to the user's library needs two things: authorization, and iCloud Music
|
|
151
|
+
Library turned on for the account.
|
|
210
152
|
|
|
211
153
|
```swift
|
|
212
|
-
func
|
|
213
|
-
|
|
214
|
-
let
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
try await MusicLibrary.shared.add(song)
|
|
154
|
+
func canWriteLibrary() async -> Bool {
|
|
155
|
+
if MusicAuthorization.currentStatus != .authorized { return false }
|
|
156
|
+
let membership = try? await MusicSubscription.current
|
|
157
|
+
return membership?.hasCloudLibraryEnabled ?? false
|
|
218
158
|
}
|
|
219
159
|
|
|
220
|
-
func
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
160
|
+
func save(_ track: Song) async throws {
|
|
161
|
+
let library = MusicLibrary.shared
|
|
162
|
+
if await canWriteLibrary() { try await library.add(track) }
|
|
163
|
+
}
|
|
224
164
|
|
|
225
|
-
|
|
165
|
+
func save(_ record: Album) async throws {
|
|
166
|
+
let library = MusicLibrary.shared
|
|
167
|
+
if await canWriteLibrary() { try await library.add(record) }
|
|
226
168
|
}
|
|
227
169
|
```
|
|
228
170
|
|
|
229
|
-
|
|
171
|
+
Reading from the library uses `MusicLibraryRequest`:
|
|
230
172
|
|
|
231
173
|
```swift
|
|
232
|
-
func
|
|
233
|
-
var
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return response.items
|
|
174
|
+
func lastListened() async throws -> MusicItemCollection<Song> {
|
|
175
|
+
var query = MusicLibraryRequest<Song>()
|
|
176
|
+
query.sort(by: \.lastPlayedDate, ascending: false)
|
|
177
|
+
query.limit = 20
|
|
178
|
+
return try await query.response().items
|
|
238
179
|
}
|
|
239
180
|
```
|
|
240
181
|
|
|
241
|
-
## Playlist
|
|
182
|
+
## Playlist access
|
|
242
183
|
|
|
243
|
-
|
|
184
|
+
A library request for playlists sorts only by `lastPlayedDate`,
|
|
185
|
+
`libraryAddedDate` or `name`; `Playlist.lastModifiedDate` exists but is not a
|
|
186
|
+
sort key.
|
|
244
187
|
|
|
245
188
|
```swift
|
|
246
|
-
func
|
|
247
|
-
var
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return response.items
|
|
189
|
+
func recentPlaylists() async throws -> MusicItemCollection<Playlist> {
|
|
190
|
+
var query = MusicLibraryRequest<Playlist>()
|
|
191
|
+
query.sort(by: \.lastPlayedDate, ascending: false)
|
|
192
|
+
return try await query.response().items
|
|
251
193
|
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Adding Tracks to a Playlist
|
|
255
194
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
try await MusicLibrary.shared.add(songs, to: playlist)
|
|
195
|
+
func append(_ tracks: [Song], to list: Playlist) async throws {
|
|
196
|
+
guard await canWriteLibrary() else { return }
|
|
197
|
+
for track in tracks {
|
|
198
|
+
_ = try await MusicLibrary.shared.add(track, to: list)
|
|
199
|
+
}
|
|
263
200
|
}
|
|
264
201
|
```
|
|
265
202
|
|
|
266
|
-
## Now Playing
|
|
203
|
+
## Now Playing session
|
|
267
204
|
|
|
268
|
-
|
|
269
|
-
|
|
205
|
+
When an app runs more than one audio source at once (for example a
|
|
206
|
+
Picture in Picture video next to a music stream), give each its own
|
|
207
|
+
`MPNowPlayingSession`. Each session carries its own command center and info
|
|
208
|
+
center, and the app says which one should be the system's Now Playing entry.
|
|
270
209
|
|
|
271
210
|
```swift
|
|
272
|
-
import MediaPlayer
|
|
273
211
|
import AVFoundation
|
|
212
|
+
import MediaPlayer
|
|
274
213
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return .success
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
session.remoteCommandCenter.pauseCommand.addTarget { _ in
|
|
285
|
-
player.pause()
|
|
214
|
+
@MainActor
|
|
215
|
+
func makeVideoSession(for videoPlayer: AVPlayer) -> MPNowPlayingSession {
|
|
216
|
+
let clipSession = MPNowPlayingSession(players: [videoPlayer])
|
|
217
|
+
let controls = clipSession.remoteCommandCenter
|
|
218
|
+
controls.playCommand.addTarget { event in
|
|
219
|
+
videoPlayer.play()
|
|
286
220
|
return .success
|
|
287
221
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
session.nowPlayingInfoCenter.nowPlayingInfo = [
|
|
291
|
-
MPMediaItemPropertyTitle: "Track Title",
|
|
292
|
-
MPMediaItemPropertyArtist: "Artist Name"
|
|
222
|
+
clipSession.nowPlayingInfoCenter.nowPlayingInfo = [
|
|
223
|
+
MPMediaItemPropertyTitle: "Match highlights"
|
|
293
224
|
]
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
session.becomeActiveIfPossible { success in
|
|
297
|
-
print("Now playing session active: \(success)")
|
|
225
|
+
clipSession.becomeActiveIfPossible { didBecomeActive in
|
|
226
|
+
// didBecomeActive is false if another app currently owns Now Playing.
|
|
298
227
|
}
|
|
299
|
-
|
|
300
|
-
return session
|
|
228
|
+
return clipSession
|
|
301
229
|
}
|
|
302
230
|
```
|
|
303
231
|
|
|
304
|
-
## Background
|
|
232
|
+
## Background audio configuration
|
|
305
233
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
Configure the audio session before starting playback to enable background audio.
|
|
234
|
+
Set up the audio session before playback starts:
|
|
309
235
|
|
|
310
236
|
```swift
|
|
311
|
-
import AVFoundation
|
|
312
|
-
|
|
313
237
|
func configureAudioSession() throws {
|
|
314
|
-
let
|
|
315
|
-
try
|
|
316
|
-
try
|
|
238
|
+
let audio = AVAudioSession.sharedInstance()
|
|
239
|
+
try audio.setCategory(.playback, mode: .default)
|
|
240
|
+
try audio.setActive(true)
|
|
317
241
|
}
|
|
318
242
|
```
|
|
319
243
|
|
|
320
|
-
|
|
244
|
+
Then list `audio` under the `UIBackgroundModes` key in Info.plist (in Xcode:
|
|
245
|
+
Signing and Capabilities, Background Modes, "Audio, AirPlay, and Picture in
|
|
246
|
+
Picture").
|
|
321
247
|
|
|
322
|
-
|
|
248
|
+
### Interruptions
|
|
323
249
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<string>audio</string>
|
|
328
|
-
</array>
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Handling Interruptions
|
|
250
|
+
Calls, alarms and Siri interrupt playback. Pause when it starts; resume only if
|
|
251
|
+
the system says so when it ends. The async notification sequence keeps the
|
|
252
|
+
handler on the main actor.
|
|
332
253
|
|
|
333
254
|
```swift
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
break
|
|
349
|
-
case .ended:
|
|
350
|
-
let options = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt
|
|
351
|
-
if let options, AVAudioSession.InterruptionOptions(rawValue: options).contains(.shouldResume) {
|
|
352
|
-
// Resume playback
|
|
353
|
-
}
|
|
354
|
-
@unknown default:
|
|
355
|
-
break
|
|
255
|
+
@MainActor
|
|
256
|
+
func followInterruptions(for engine: EpisodeEngine) async {
|
|
257
|
+
let feed = NotificationCenter.default.notifications(named: AVAudioSession.interruptionNotification)
|
|
258
|
+
for await note in feed {
|
|
259
|
+
let info = note.userInfo ?? [:]
|
|
260
|
+
guard let kindValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
261
|
+
let kind = AVAudioSession.InterruptionType(rawValue: kindValue) else { continue }
|
|
262
|
+
|
|
263
|
+
if kind == .began {
|
|
264
|
+
engine.suspend() // also remember the position
|
|
265
|
+
} else if kind == .ended {
|
|
266
|
+
let flags = AVAudioSession.InterruptionOptions(
|
|
267
|
+
rawValue: info[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0)
|
|
268
|
+
if flags.contains(.shouldResume) { engine.resume() }
|
|
356
269
|
}
|
|
357
270
|
}
|
|
358
271
|
}
|
|
359
272
|
```
|
|
360
273
|
|
|
361
|
-
### Route
|
|
274
|
+
### Route changes
|
|
362
275
|
|
|
363
|
-
|
|
276
|
+
Pulling out headphones should stop the audio instead of moving it to the
|
|
277
|
+
speaker. The system reports that as `.oldDeviceUnavailable`.
|
|
364
278
|
|
|
365
279
|
```swift
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
|
374
|
-
return
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if reason == .oldDeviceUnavailable {
|
|
378
|
-
// Headphones were unplugged -- pause playback
|
|
379
|
-
pausePlayback()
|
|
280
|
+
@MainActor
|
|
281
|
+
func followRouteChanges(for engine: EpisodeEngine) async {
|
|
282
|
+
let feed = NotificationCenter.default.notifications(named: AVAudioSession.routeChangeNotification)
|
|
283
|
+
for await note in feed {
|
|
284
|
+
let reasonValue = note.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt ?? 0
|
|
285
|
+
if AVAudioSession.RouteChangeReason(rawValue: reasonValue) == .oldDeviceUnavailable {
|
|
286
|
+
engine.suspend()
|
|
380
287
|
}
|
|
381
288
|
}
|
|
382
289
|
}
|
|
383
290
|
```
|
|
291
|
+
|
|
292
|
+
Start both from a long-lived `.task` or from the engine's own setup.
|
|
293
|
+
`EpisodeEngine` stands for whatever `@MainActor` type drives the app's custom
|
|
294
|
+
audio.
|