@devo-bmad-custom/agent-orchestration 1.0.2 → 1.0.4
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/lib/installer.js +33 -0
- package/package.json +1 -1
- package/src/.agents/skills/audit-website/README.md +20 -20
- package/src/.agents/skills/audit-website/SKILL.md +470 -470
- package/src/.agents/skills/audit-website/agents/openai.yaml +6 -6
- package/src/.agents/skills/audit-website/assets/icon-small.svg +41 -41
- package/src/.agents/skills/audit-website/references/OUTPUT-FORMAT.md +250 -250
- package/src/.agents/skills/clean-code-standards/SKILL.md +104 -104
- package/src/.agents/skills/excalidraw-dark-standard/SKILL.md +281 -281
- package/src/.agents/skills/frontend-responsive-design-standards/SKILL.md +434 -434
- package/src/.agents/skills/java-fundamentals/SKILL.md +116 -116
- package/src/.agents/skills/java-performance/SKILL.md +119 -119
- package/src/.agents/skills/next-best-practices/SKILL.md +153 -153
- package/src/.agents/skills/next-best-practices/async-patterns.md +87 -87
- package/src/.agents/skills/next-best-practices/bundling.md +180 -180
- package/src/.agents/skills/next-best-practices/data-patterns.md +297 -297
- package/src/.agents/skills/next-best-practices/debug-tricks.md +105 -105
- package/src/.agents/skills/next-best-practices/directives.md +73 -73
- package/src/.agents/skills/next-best-practices/error-handling.md +227 -227
- package/src/.agents/skills/next-best-practices/file-conventions.md +140 -140
- package/src/.agents/skills/next-best-practices/font.md +245 -245
- package/src/.agents/skills/next-best-practices/functions.md +108 -108
- package/src/.agents/skills/next-best-practices/hydration-error.md +91 -91
- package/src/.agents/skills/next-best-practices/image.md +173 -173
- package/src/.agents/skills/next-best-practices/metadata.md +301 -301
- package/src/.agents/skills/next-best-practices/parallel-routes.md +287 -287
- package/src/.agents/skills/next-best-practices/route-handlers.md +146 -146
- package/src/.agents/skills/next-best-practices/rsc-boundaries.md +159 -159
- package/src/.agents/skills/next-best-practices/runtime-selection.md +39 -39
- package/src/.agents/skills/next-best-practices/scripts.md +141 -141
- package/src/.agents/skills/next-best-practices/self-hosting.md +371 -371
- package/src/.agents/skills/next-best-practices/suspense-boundaries.md +67 -67
- package/src/.agents/skills/nextjs-app-router-patterns/SKILL.md +537 -537
- package/src/.agents/skills/postgresql-optimization/SKILL.md +404 -404
- package/src/.agents/skills/python-backend/SKILL.md +153 -153
- package/src/.agents/skills/python-fundamentals/SKILL.md +234 -234
- package/src/.agents/skills/python-performance/SKILL.md +404 -404
- package/src/.agents/skills/react-expert/SKILL.md +335 -335
- package/src/.agents/skills/redis-best-practices/SKILL.md +438 -438
- package/src/.agents/skills/security-best-practices/SKILL.md +288 -288
- package/src/.agents/skills/security-review/LICENSE +22 -22
- package/src/.agents/skills/security-review/SKILL.md +312 -312
- package/src/.agents/skills/security-review/infrastructure/docker.md +432 -432
- package/src/.agents/skills/security-review/languages/javascript.md +388 -388
- package/src/.agents/skills/security-review/languages/python.md +363 -363
- package/src/.agents/skills/security-review/references/api-security.md +519 -519
- package/src/.agents/skills/security-review/references/authentication.md +353 -353
- package/src/.agents/skills/security-review/references/authorization.md +372 -372
- package/src/.agents/skills/security-review/references/business-logic.md +443 -443
- package/src/.agents/skills/security-review/references/cryptography.md +329 -329
- package/src/.agents/skills/security-review/references/csrf.md +398 -398
- package/src/.agents/skills/security-review/references/data-protection.md +378 -378
- package/src/.agents/skills/security-review/references/deserialization.md +410 -410
- package/src/.agents/skills/security-review/references/error-handling.md +436 -436
- package/src/.agents/skills/security-review/references/file-security.md +457 -457
- package/src/.agents/skills/security-review/references/injection.md +259 -259
- package/src/.agents/skills/security-review/references/logging.md +433 -433
- package/src/.agents/skills/security-review/references/misconfiguration.md +435 -435
- package/src/.agents/skills/security-review/references/modern-threats.md +475 -475
- package/src/.agents/skills/security-review/references/ssrf.md +415 -415
- package/src/.agents/skills/security-review/references/supply-chain.md +405 -405
- package/src/.agents/skills/security-review/references/xss.md +336 -336
- package/src/.agents/skills/subagent-driven-development/SKILL.md +275 -275
- package/src/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -26
- package/src/.agents/skills/subagent-driven-development/implementer-prompt.md +113 -113
- package/src/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -61
- package/src/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/src/.agents/skills/systematic-debugging/SKILL.md +296 -296
- package/src/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/src/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/src/.agents/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/src/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/src/.agents/skills/systematic-debugging/test-academic.md +14 -14
- package/src/.agents/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/src/.agents/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/src/.agents/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/src/.agents/skills/typescript-best-practices/SKILL.md +373 -373
- package/src/.agents/skills/ui-ux-pro-custom/SKILL.md +348 -348
- package/src/.agents/skills/ui-ux-pro-custom/data/charts.csv +26 -26
- package/src/.agents/skills/ui-ux-pro-custom/data/colors.csv +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/icons.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/SKILL.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/accessibility.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/animation.md +466 -466
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/composition-locals.md +231 -231
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/deprecated-patterns.md +323 -323
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/lists-scrolling.md +400 -400
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/modifiers.md +331 -331
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/navigation.md +416 -416
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/performance.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/side-effects.md +516 -516
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/foundation-source.md +13327 -13327
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/material3-source.md +19097 -19097
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/navigation-source.md +2947 -2947
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/runtime-source.md +11316 -11316
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/ui-source.md +7896 -7896
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/state-management.md +377 -377
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/styles-experimental.md +470 -470
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/theming-material3.md +349 -349
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/view-composition.md +595 -595
- package/src/.agents/skills/ui-ux-pro-custom/data/landing.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/data/mobile-ui-layout.md +654 -654
- package/src/.agents/skills/ui-ux-pro-custom/data/products.csv +96 -96
- package/src/.agents/skills/ui-ux-pro-custom/data/react-performance.csv +45 -45
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/astro.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/flutter.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/html-tailwind.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/jetpack-compose.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nextjs.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxt-ui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxtjs.csv +59 -59
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react-native.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/shadcn.csv +61 -61
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/svelte.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/swiftui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/vue.csv +50 -50
- package/src/.agents/skills/ui-ux-pro-custom/data/styles.csv +68 -68
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/SKILL.md +438 -438
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/references/alarmkit-patterns.md +584 -584
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-clips/SKILL.md +436 -436
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/SKILL.md +489 -489
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/references/appintents-advanced.md +1076 -1076
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/SKILL.md +340 -340
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/privacy-manifest.md +90 -90
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/review-checklists.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-conversion.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-optimization.md +344 -344
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/foundation-models.md +508 -508
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/mlx-swift.md +285 -285
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/references/keychain-biometric.md +211 -211
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/SKILL.md +499 -499
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/references/background-task-patterns.md +390 -390
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/SKILL.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/references/callkit-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/SKILL.md +492 -492
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/references/cloudkit-patterns.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/codable-patterns/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/SKILL.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/references/contacts-patterns.md +409 -409
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/references/ble-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/SKILL.md +388 -388
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/references/motion-patterns.md +405 -405
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/SKILL.md +495 -495
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/references/nfc-patterns.md +420 -420
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/SKILL.md +459 -459
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/references/coreml-swift-integration.md +765 -765
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/SKILL.md +422 -422
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/instruments-guide.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/lldb-patterns.md +298 -298
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/device-integrity/SKILL.md +477 -477
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/SKILL.md +460 -460
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/references/energykit-patterns.md +541 -541
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/references/eventkit-patterns.md +326 -326
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/references/healthkit-patterns.md +602 -602
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/references/matter-commissioning.md +455 -455
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/SKILL.md +301 -301
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/references/a11y-patterns.md +140 -140
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/SKILL.md +418 -418
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/formatstyle-locale.md +627 -627
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/string-catalogs.md +462 -462
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/SKILL.md +441 -441
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/background-websocket.md +862 -862
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/lightweight-clients.md +93 -93
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/network-framework.md +563 -563
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/urlsession-patterns.md +1116 -1116
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/app-review-guidelines.md +174 -174
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/cryptokit-advanced.md +296 -296
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/file-storage-patterns.md +354 -354
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/privacy-manifest.md +117 -117
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/references/live-activity-patterns.md +868 -868
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/corelocation-patterns.md +730 -730
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/mapkit-patterns.md +748 -748
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/metrickit-diagnostics/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/SKILL.md +395 -395
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/references/musickit-patterns.md +363 -363
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/SKILL.md +412 -412
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/references/translation-patterns.md +311 -311
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/SKILL.md +398 -398
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/references/wallet-passes.md +254 -254
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/SKILL.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/paperkit-integration.md +376 -376
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/pencilkit-patterns.md +302 -302
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/SKILL.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/references/permissionkit-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/av-playback.md +701 -701
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/camera-capture.md +774 -774
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/image-loading-caching.md +869 -869
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/photospicker-patterns.md +597 -597
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/notification-patterns.md +677 -677
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/rich-notifications.md +745 -745
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/references/realitykit-patterns.md +480 -480
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/references/shareplay-patterns.md +544 -544
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/speech-recognition/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/SKILL.md +478 -478
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/app-review-guidelines.md +58 -58
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/storekit-advanced.md +755 -755
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/SKILL.md +487 -487
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/references/charts-patterns.md +895 -895
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/SKILL.md +408 -408
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/approachable-concurrency.md +80 -80
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swift-6-2-concurrency.md +233 -233
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swiftui-concurrency.md +187 -187
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/synchronization-primitives.md +341 -341
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/references/swift-patterns-extended.md +505 -505
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/references/testing-patterns.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/SKILL.md +334 -334
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/core-data-coexistence.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-advanced.md +975 -975
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-queries.md +675 -675
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/SKILL.md +481 -481
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/animation-advanced.md +804 -804
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/core-animation-bridge.md +553 -553
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/SKILL.md +450 -450
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/references/gesture-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/SKILL.md +336 -336
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/form.md +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/grids.md +69 -69
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/list.md +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/scrollview.md +147 -147
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/SKILL.md +325 -325
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/references/liquid-glass.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/SKILL.md +262 -262
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/deeplinks.md +207 -207
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/navigationstack.md +177 -177
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/sheets.md +169 -169
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/tabview.md +178 -178
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/SKILL.md +381 -381
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/architecture-patterns.md +486 -486
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/deprecated-migration.md +1097 -1097
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/design-polish.md +780 -780
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/platform-and-sharing.md +696 -696
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +46 -46
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +29 -29
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-hangs-in-your-app.md +33 -33
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-improving-swiftui-performance.md +52 -52
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/SKILL.md +428 -428
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/hosting-migration.md +534 -534
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/representable-recipes.md +1133 -1133
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/SKILL.md +494 -494
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/references/tipkit-patterns.md +782 -782
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -736
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -738
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -410
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -567
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -497
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -871
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +57 -57
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -253
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -1067
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -114
- package/src/.agents/skills/ux-audit/SKILL.md +150 -150
- package/src/.agents/skills/websocket-engineer/SKILL.md +168 -168
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -391
- package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -400
- package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -195
- package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -333
- package/src/.agents/skills/websocket-engineer/references/security.md +474 -474
- package/src/.agents/skills/writing-skills/SKILL.md +655 -655
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -1150
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -187
- package/src/.agents/skills/writing-skills/render-graphs.js +168 -168
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -384
- package/src/.claude/commands/master-orchestrator.md +15 -0
- package/src/_memory/config.yaml +11 -11
- package/src/_memory/master-orchestrator-sidecar/instructions.md +97 -47
- package/src/_memory/skills/nimbalyst-tracking/SKILL.md +103 -103
- package/src/_memory/skills/writing-skills/SKILL.md +655 -655
- package/src/bmb/agents/agent-builder.md +59 -59
- package/src/bmb/agents/module-builder.md +60 -60
- package/src/bmb/agents/workflow-builder.md +61 -61
- package/src/bmb/config.yaml +12 -12
- package/src/bmb/module-help.csv +13 -13
- package/src/bmb/workflows/agent/data/agent-architecture.md +258 -258
- package/src/bmb/workflows/agent/data/agent-compilation.md +185 -185
- package/src/bmb/workflows/agent/data/agent-menu-patterns.md +189 -189
- package/src/bmb/workflows/agent/data/agent-metadata.md +133 -133
- package/src/bmb/workflows/agent/data/agent-validation.md +111 -111
- package/src/bmb/workflows/agent/data/brainstorm-context.md +96 -96
- package/src/bmb/workflows/agent/data/communication-presets.csv +61 -61
- package/src/bmb/workflows/agent/data/critical-actions.md +75 -75
- package/src/bmb/workflows/agent/data/persona-properties.md +252 -252
- package/src/bmb/workflows/agent/data/principles-crafting.md +142 -142
- package/src/bmb/workflows/agent/data/reference/module-examples/architect.md +68 -68
- package/src/bmb/workflows/agent/data/reference/with-sidecar/journal-keeper/journal-keeper-sidecar/entries/yy-mm-dd-entry-template.md +16 -16
- package/src/bmb/workflows/agent/data/understanding-agent-types.md +126 -126
- package/src/bmb/workflows/agent/steps-c/step-01-brainstorm.md +129 -129
- package/src/bmb/workflows/agent/steps-c/step-02-discovery.md +170 -170
- package/src/bmb/workflows/agent/steps-c/step-03-sidecar-metadata.md +309 -309
- package/src/bmb/workflows/agent/steps-c/step-04-persona.md +213 -213
- package/src/bmb/workflows/agent/steps-c/step-05-commands-menu.md +179 -179
- package/src/bmb/workflows/agent/steps-c/step-06-activation.md +278 -278
- package/src/bmb/workflows/agent/steps-c/step-07-build-agent.md +316 -316
- package/src/bmb/workflows/agent/steps-c/step-08-celebrate.md +247 -247
- package/src/bmb/workflows/agent/steps-e/e-01-load-existing.md +221 -221
- package/src/bmb/workflows/agent/steps-e/e-02-discover-edits.md +195 -195
- package/src/bmb/workflows/agent/steps-e/e-04-sidecar-metadata.md +126 -126
- package/src/bmb/workflows/agent/steps-e/e-05-persona.md +135 -135
- package/src/bmb/workflows/agent/steps-e/e-06-commands-menu.md +123 -123
- package/src/bmb/workflows/agent/steps-e/e-07-activation.md +124 -124
- package/src/bmb/workflows/agent/steps-e/e-08-edit-agent.md +197 -197
- package/src/bmb/workflows/agent/steps-e/e-09-celebrate.md +155 -155
- package/src/bmb/workflows/agent/steps-v/v-01-load-review.md +137 -137
- package/src/bmb/workflows/agent/steps-v/v-02a-validate-metadata.md +116 -116
- package/src/bmb/workflows/agent/steps-v/v-02b-validate-persona.md +124 -124
- package/src/bmb/workflows/agent/steps-v/v-02c-validate-menu.md +127 -127
- package/src/bmb/workflows/agent/steps-v/v-02d-validate-structure.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-02e-validate-sidecar.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-03-summary.md +104 -104
- package/src/bmb/workflows/agent/templates/agent-plan.template.md +5 -5
- package/src/bmb/workflows/agent/templates/agent-template.md +89 -89
- package/src/bmb/workflows/agent/workflow-create-agent.md +72 -72
- package/src/bmb/workflows/agent/workflow-edit-agent.md +75 -75
- package/src/bmb/workflows/agent/workflow-validate-agent.md +73 -73
- package/src/bmb/workflows/module/data/agent-architecture.md +179 -179
- package/src/bmb/workflows/module/data/agent-spec-template.md +79 -79
- package/src/bmb/workflows/module/data/module-standards.md +263 -263
- package/src/bmb/workflows/module/data/module-yaml-conventions.md +392 -392
- package/src/bmb/workflows/module/module-help-generate.md +254 -254
- package/src/bmb/workflows/module/steps-b/step-01-welcome.md +148 -148
- package/src/bmb/workflows/module/steps-b/step-02-spark.md +141 -141
- package/src/bmb/workflows/module/steps-b/step-03-module-type.md +149 -149
- package/src/bmb/workflows/module/steps-b/step-04-vision.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-05-identity.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-06-users.md +86 -86
- package/src/bmb/workflows/module/steps-b/step-07-value.md +76 -76
- package/src/bmb/workflows/module/steps-b/step-08-agents.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-09-workflows.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-10-tools.md +91 -91
- package/src/bmb/workflows/module/steps-b/step-11-scenarios.md +84 -84
- package/src/bmb/workflows/module/steps-b/step-12-creative.md +95 -95
- package/src/bmb/workflows/module/steps-b/step-13-review.md +105 -105
- package/src/bmb/workflows/module/steps-b/step-14-finalize.md +117 -117
- package/src/bmb/workflows/module/steps-c/step-01-load-brief.md +179 -179
- package/src/bmb/workflows/module/steps-c/step-01b-continue.md +82 -82
- package/src/bmb/workflows/module/steps-c/step-02-structure.md +105 -105
- package/src/bmb/workflows/module/steps-c/step-03-config.md +119 -119
- package/src/bmb/workflows/module/steps-c/step-04-agents.md +168 -168
- package/src/bmb/workflows/module/steps-c/step-05-workflows.md +184 -184
- package/src/bmb/workflows/module/steps-c/step-06-docs.md +401 -401
- package/src/bmb/workflows/module/steps-c/step-07-complete.md +152 -152
- package/src/bmb/workflows/module/steps-e/step-01-load-target.md +81 -81
- package/src/bmb/workflows/module/steps-e/step-02-select-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-03-apply-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-04-review.md +80 -80
- package/src/bmb/workflows/module/steps-e/step-05-confirm.md +75 -75
- package/src/bmb/workflows/module/steps-v/step-01-load-target.md +96 -96
- package/src/bmb/workflows/module/steps-v/step-02-file-structure.md +93 -93
- package/src/bmb/workflows/module/steps-v/step-03-module-yaml.md +99 -99
- package/src/bmb/workflows/module/steps-v/step-04-agent-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-05-workflow-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-06-documentation.md +143 -143
- package/src/bmb/workflows/module/steps-v/step-07-installation.md +102 -102
- package/src/bmb/workflows/module/steps-v/step-08-report.md +197 -197
- package/src/bmb/workflows/module/templates/brief-template.md +154 -154
- package/src/bmb/workflows/module/templates/workflow-spec-template.md +96 -96
- package/src/bmb/workflows/module/workflow-create-module-brief.md +71 -71
- package/src/bmb/workflows/module/workflow-create-module.md +86 -86
- package/src/bmb/workflows/module/workflow-edit-module.md +66 -66
- package/src/bmb/workflows/module/workflow-validate-module.md +66 -66
- package/src/bmb/workflows/workflow/data/architecture.md +150 -150
- package/src/bmb/workflows/workflow/data/common-workflow-tools.csv +19 -19
- package/src/bmb/workflows/workflow/data/csv-data-file-standards.md +53 -53
- package/src/bmb/workflows/workflow/data/frontmatter-standards.md +184 -184
- package/src/bmb/workflows/workflow/data/input-discovery-standards.md +191 -191
- package/src/bmb/workflows/workflow/data/intent-vs-prescriptive-spectrum.md +44 -44
- package/src/bmb/workflows/workflow/data/menu-handling-standards.md +133 -133
- package/src/bmb/workflows/workflow/data/output-format-standards.md +135 -135
- package/src/bmb/workflows/workflow/data/step-file-rules.md +235 -235
- package/src/bmb/workflows/workflow/data/step-type-patterns.md +257 -257
- package/src/bmb/workflows/workflow/data/subprocess-optimization-patterns.md +188 -188
- package/src/bmb/workflows/workflow/data/trimodal-workflow-structure.md +164 -164
- package/src/bmb/workflows/workflow/data/workflow-chaining-standards.md +222 -222
- package/src/bmb/workflows/workflow/data/workflow-examples.md +232 -232
- package/src/bmb/workflows/workflow/data/workflow-type-criteria.md +134 -134
- package/src/bmb/workflows/workflow/steps-c/step-00-conversion.md +263 -263
- package/src/bmb/workflows/workflow/steps-c/step-01-discovery.md +194 -194
- package/src/bmb/workflows/workflow/steps-c/step-01b-continuation.md +3 -3
- package/src/bmb/workflows/workflow/steps-c/step-02-classification.md +270 -270
- package/src/bmb/workflows/workflow/steps-c/step-03-requirements.md +283 -283
- package/src/bmb/workflows/workflow/steps-c/step-04-tools.md +282 -282
- package/src/bmb/workflows/workflow/steps-c/step-05-plan-review.md +243 -243
- package/src/bmb/workflows/workflow/steps-c/step-06-design.md +330 -330
- package/src/bmb/workflows/workflow/steps-c/step-07-foundation.md +239 -239
- package/src/bmb/workflows/workflow/steps-c/step-08-build-step-01.md +379 -379
- package/src/bmb/workflows/workflow/steps-c/step-09-build-next-step.md +350 -350
- package/src/bmb/workflows/workflow/steps-c/step-10-confirmation.md +322 -322
- package/src/bmb/workflows/workflow/steps-c/step-11-completion.md +191 -191
- package/src/bmb/workflows/workflow/steps-e/step-e-01-assess-workflow.md +237 -237
- package/src/bmb/workflows/workflow/steps-e/step-e-02-discover-edits.md +251 -251
- package/src/bmb/workflows/workflow/steps-e/step-e-03-fix-validation.md +254 -254
- package/src/bmb/workflows/workflow/steps-e/step-e-04-direct-edit.md +277 -277
- package/src/bmb/workflows/workflow/steps-e/step-e-05-apply-edit.md +154 -154
- package/src/bmb/workflows/workflow/steps-e/step-e-06-validate-after.md +190 -190
- package/src/bmb/workflows/workflow/steps-e/step-e-07-complete.md +206 -206
- package/src/bmb/workflows/workflow/steps-v/step-01-validate-max-mode.md +109 -109
- package/src/bmb/workflows/workflow/steps-v/step-01-validate.md +221 -221
- package/src/bmb/workflows/workflow/steps-v/step-01b-structure.md +152 -152
- package/src/bmb/workflows/workflow/steps-v/step-02-frontmatter-validation.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-02b-path-violations.md +265 -265
- package/src/bmb/workflows/workflow/steps-v/step-03-menu-validation.md +164 -164
- package/src/bmb/workflows/workflow/steps-v/step-04-step-type-validation.md +211 -211
- package/src/bmb/workflows/workflow/steps-v/step-05-output-format-validation.md +200 -200
- package/src/bmb/workflows/workflow/steps-v/step-06-validation-design-check.md +195 -195
- package/src/bmb/workflows/workflow/steps-v/step-07-instruction-style-check.md +209 -209
- package/src/bmb/workflows/workflow/steps-v/step-08-collaborative-experience-check.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-08b-subprocess-optimization.md +179 -179
- package/src/bmb/workflows/workflow/steps-v/step-09-cohesive-review.md +186 -186
- package/src/bmb/workflows/workflow/steps-v/step-10-report-complete.md +154 -154
- package/src/bmb/workflows/workflow/steps-v/step-11-plan-validation.md +237 -237
- package/src/bmb/workflows/workflow/templates/minimal-output-template.md +11 -11
- package/src/bmb/workflows/workflow/templates/step-01-init-continuable-template.md +241 -241
- package/src/bmb/workflows/workflow/templates/step-1b-template.md +224 -224
- package/src/bmb/workflows/workflow/templates/step-template.md +294 -294
- package/src/bmb/workflows/workflow/templates/workflow-template.md +102 -102
- package/src/bmb/workflows/workflow/workflow-create-workflow.md +79 -79
- package/src/bmb/workflows/workflow/workflow-edit-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-rework-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-validate-max-parallel-workflow.md +66 -66
- package/src/bmb/workflows/workflow/workflow-validate-workflow.md +65 -65
- package/src/bmm/agents/analyst.md +104 -104
- package/src/bmm/agents/dev.md +100 -100
- package/src/bmm/agents/qa.md +100 -90
- package/src/bmm/agents/tech-writer/tech-writer.md +94 -94
- package/src/bmm/module-help.csv +31 -31
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +115 -115
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +107 -107
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +141 -141
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +144 -144
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +147 -147
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +161 -161
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +99 -99
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -57
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +156 -156
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +165 -165
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +140 -140
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +152 -152
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +345 -345
- package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +92 -92
- package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +164 -164
- package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +174 -174
- package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +184 -184
- package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +105 -105
- package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +360 -360
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +165 -165
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +174 -174
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +141 -141
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +159 -159
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +387 -387
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -54
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +100 -100
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +160 -160
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +88 -88
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +99 -99
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +169 -169
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +156 -156
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +176 -176
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +184 -184
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +174 -174
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +175 -175
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +189 -189
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +162 -162
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +79 -79
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +183 -183
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +149 -149
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +187 -187
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +108 -108
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +166 -166
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +131 -131
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +150 -150
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +155 -155
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +170 -170
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +158 -158
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +147 -147
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +182 -182
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +202 -202
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +148 -148
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +201 -201
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +179 -179
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +164 -164
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +106 -106
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +111 -111
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +115 -115
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +127 -127
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +167 -167
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +143 -143
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +154 -154
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +165 -165
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +135 -135
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +101 -101
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +45 -45
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +185 -185
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +130 -130
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +93 -93
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +196 -196
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -54
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +82 -82
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +106 -106
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +138 -138
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +129 -129
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +166 -166
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +186 -186
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +163 -163
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +38 -38
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -49
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +124 -124
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +122 -122
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +84 -84
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -58
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +43 -43
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +53 -53
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +159 -159
- package/src/bmm/workflows/4-implementation/create-story/template.md +79 -79
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +20 -20
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -25
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +158 -158
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +122 -122
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +87 -87
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -146
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -50
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +152 -152
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +123 -123
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +201 -201
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -79
- package/src/bmm/workflows/document-project/workflow.yaml +22 -22
- package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -184
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +322 -322
- package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +235 -235
- package/src/bmm/workflows/generate-project-context/workflow.md +49 -49
- package/src/bmm/workflows/qa/automate/workflow.yaml +233 -233
- package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +42 -42
- package/src/core/config.yaml +9 -9
- package/src/core/module-help.csv +10 -10
- package/src/core/scripts/generate-loop-report.py +72 -72
- package/src/core/tasks/editorial-review-prose.xml +101 -101
- package/src/core/tasks/editorial-review-structure.xml +207 -207
- package/src/core/tasks/help.md +86 -86
- package/src/core/tasks/index-docs.xml +64 -64
- package/src/core/tasks/review-adversarial-general.xml +66 -66
- package/src/core/tasks/review-adversarial-loop.xml +46 -46
- package/src/core/tasks/review-edge-case-hunter.xml +63 -63
- package/src/core/tasks/review-party-loop.xml +46 -46
- package/src/core/tasks/shard-doc.xml +107 -107
- package/src/core/tasks/workflow.xml +235 -235
- package/src/core/templates/review-loop-report.html +88 -88
- package/src/core/templates/review-loop-report.md +5 -5
- package/src/core/workflows/advanced-elicitation/workflow.xml +117 -117
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +212 -212
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -122
- package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -225
- package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -237
- package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -209
- package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -264
- package/src/core/workflows/brainstorming/steps/step-02e-deep-dive.md +68 -68
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +403 -403
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -303
- package/src/core/workflows/brainstorming/workflow.md +60 -60
- package/src/core/workflows/extract-trackers/workflow.md +45 -45
- package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +142 -142
- package/src/core/workflows/party-mode/workflow.md +194 -194
- package/src/docs/dev/tmux/actions_popup.py +291 -291
- package/src/docs/dev/tmux/tmux-setup.md +62 -1
|
@@ -1,701 +1,701 @@
|
|
|
1
|
-
# AV Playback
|
|
2
|
-
|
|
3
|
-
Patterns for media playback with AVPlayer, streaming HLS content, audio
|
|
4
|
-
session configuration, background audio, Now Playing integration, remote
|
|
5
|
-
command handling, and Picture-in-Picture.
|
|
6
|
-
|
|
7
|
-
## Contents
|
|
8
|
-
|
|
9
|
-
- [AVPlayer and AVPlayerViewController Setup](#avplayer-and-avplayerviewcontroller-setup)
|
|
10
|
-
- [AVPlayerItem and AVAsset Loading](#avplayeritem-and-avasset-loading)
|
|
11
|
-
- [Playback Controls](#playback-controls)
|
|
12
|
-
- [Observing Player State and Time](#observing-player-state-and-time)
|
|
13
|
-
- [Streaming HLS Content](#streaming-hls-content)
|
|
14
|
-
- [AVAudioSession Configuration](#avaudiosession-configuration)
|
|
15
|
-
- [Background Audio Setup](#background-audio-setup)
|
|
16
|
-
- [Now Playing Info Center](#now-playing-info-center)
|
|
17
|
-
- [MPRemoteCommandCenter](#mpremotecommandcenter)
|
|
18
|
-
- [Picture-in-Picture](#picture-in-picture)
|
|
19
|
-
|
|
20
|
-
## AVPlayer and AVPlayerViewController Setup
|
|
21
|
-
|
|
22
|
-
`AVPlayer` manages playback of a single media asset. Use
|
|
23
|
-
`AVPlayerViewController` (AVKit) for the system-standard playback UI with
|
|
24
|
-
transport controls, or `AVPlayerLayer` for a custom player interface.
|
|
25
|
-
|
|
26
|
-
Docs: [AVPlayer](https://sosumi.ai/documentation/avfoundation/avplayer),
|
|
27
|
-
[AVPlayerViewController](https://sosumi.ai/documentation/avkit/avplayerviewcontroller)
|
|
28
|
-
|
|
29
|
-
### AVPlayerViewController in SwiftUI
|
|
30
|
-
|
|
31
|
-
```swift
|
|
32
|
-
import SwiftUI
|
|
33
|
-
import AVKit
|
|
34
|
-
|
|
35
|
-
struct VideoPlayerView: View {
|
|
36
|
-
let url: URL
|
|
37
|
-
@State private var player: AVPlayer?
|
|
38
|
-
|
|
39
|
-
var body: some View {
|
|
40
|
-
VideoPlayer(player: player)
|
|
41
|
-
.onAppear {
|
|
42
|
-
player = AVPlayer(url: url)
|
|
43
|
-
player?.play()
|
|
44
|
-
}
|
|
45
|
-
.onDisappear {
|
|
46
|
-
player?.pause()
|
|
47
|
-
player = nil
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### AVPlayerViewController in UIKit
|
|
54
|
-
|
|
55
|
-
```swift
|
|
56
|
-
import UIKit
|
|
57
|
-
import AVKit
|
|
58
|
-
|
|
59
|
-
final class VideoViewController: UIViewController {
|
|
60
|
-
private var player: AVPlayer?
|
|
61
|
-
|
|
62
|
-
func presentVideo(url: URL) {
|
|
63
|
-
player = AVPlayer(url: url)
|
|
64
|
-
let controller = AVPlayerViewController()
|
|
65
|
-
controller.player = player
|
|
66
|
-
present(controller, animated: true) {
|
|
67
|
-
self.player?.play()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Custom Player with AVPlayerLayer
|
|
74
|
-
|
|
75
|
-
For full control over the player UI, embed an `AVPlayerLayer`:
|
|
76
|
-
|
|
77
|
-
```swift
|
|
78
|
-
import AVFoundation
|
|
79
|
-
import UIKit
|
|
80
|
-
|
|
81
|
-
final class PlayerView: UIView {
|
|
82
|
-
override class var layerClass: AnyClass { AVPlayerLayer.self }
|
|
83
|
-
|
|
84
|
-
var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
|
|
85
|
-
|
|
86
|
-
var player: AVPlayer? {
|
|
87
|
-
get { playerLayer.player }
|
|
88
|
-
set { playerLayer.player = newValue }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
func configure() {
|
|
92
|
-
playerLayer.videoGravity = .resizeAspect
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## AVPlayerItem and AVAsset Loading
|
|
98
|
-
|
|
99
|
-
`AVAsset` represents the static media (duration, tracks, metadata).
|
|
100
|
-
`AVPlayerItem` adds the dynamic state (current time, buffering status) needed
|
|
101
|
-
for playback.
|
|
102
|
-
|
|
103
|
-
Docs: [AVPlayerItem](https://sosumi.ai/documentation/avfoundation/avplayeritem),
|
|
104
|
-
[AVAsset](https://sosumi.ai/documentation/avfoundation/avasset)
|
|
105
|
-
|
|
106
|
-
```swift
|
|
107
|
-
import AVFoundation
|
|
108
|
-
|
|
109
|
-
// Local file
|
|
110
|
-
let localURL = Bundle.main.url(forResource: "intro", withExtension: "mp4")!
|
|
111
|
-
let localItem = AVPlayerItem(url: localURL)
|
|
112
|
-
|
|
113
|
-
// Remote file
|
|
114
|
-
let remoteURL = URL(string: "https://example.com/video.mp4")!
|
|
115
|
-
let remoteItem = AVPlayerItem(url: remoteURL)
|
|
116
|
-
|
|
117
|
-
// From an existing AVAsset (for more control)
|
|
118
|
-
let asset = AVURLAsset(url: remoteURL, options: [
|
|
119
|
-
AVURLAssetPreferPreciseDurationAndTimingKey: true
|
|
120
|
-
])
|
|
121
|
-
|
|
122
|
-
// Load properties asynchronously before playback (iOS 15+)
|
|
123
|
-
let duration = try await asset.load(.duration)
|
|
124
|
-
let tracks = try await asset.load(.tracks)
|
|
125
|
-
let isPlayable = try await asset.load(.isPlayable)
|
|
126
|
-
|
|
127
|
-
let item = AVPlayerItem(asset: asset)
|
|
128
|
-
let player = AVPlayer(playerItem: item)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Replacing the Current Item
|
|
132
|
-
|
|
133
|
-
Reuse a single `AVPlayer` and swap items:
|
|
134
|
-
|
|
135
|
-
```swift
|
|
136
|
-
let nextItem = AVPlayerItem(url: nextVideoURL)
|
|
137
|
-
player.replaceCurrentItem(with: nextItem)
|
|
138
|
-
player.play()
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Queue Playback with AVQueuePlayer
|
|
142
|
-
|
|
143
|
-
```swift
|
|
144
|
-
let items = videoURLs.map { AVPlayerItem(url: $0) }
|
|
145
|
-
let queuePlayer = AVQueuePlayer(items: items)
|
|
146
|
-
queuePlayer.play()
|
|
147
|
-
// Automatically advances to the next item
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Playback Controls
|
|
151
|
-
|
|
152
|
-
```swift
|
|
153
|
-
// Play
|
|
154
|
-
player.play()
|
|
155
|
-
|
|
156
|
-
// Pause
|
|
157
|
-
player.pause()
|
|
158
|
-
|
|
159
|
-
// Set playback rate (1.0 = normal, 2.0 = 2x, 0.5 = half speed)
|
|
160
|
-
player.rate = 1.5
|
|
161
|
-
|
|
162
|
-
// Seek to a specific time
|
|
163
|
-
let targetTime = CMTime(seconds: 30, preferredTimescale: 600)
|
|
164
|
-
await player.seek(to: targetTime)
|
|
165
|
-
|
|
166
|
-
// Seek with tolerance (for precise seeking, e.g., scrubbing)
|
|
167
|
-
await player.seek(
|
|
168
|
-
to: targetTime,
|
|
169
|
-
toleranceBefore: .zero,
|
|
170
|
-
toleranceAfter: .zero
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
// Seek to a percentage of duration
|
|
174
|
-
func seekToPercentage(_ percentage: Double) async {
|
|
175
|
-
guard let duration = player.currentItem?.duration,
|
|
176
|
-
duration.isNumeric else { return }
|
|
177
|
-
let targetSeconds = duration.seconds * percentage
|
|
178
|
-
let target = CMTime(seconds: targetSeconds, preferredTimescale: 600)
|
|
179
|
-
await player.seek(to: target)
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## Observing Player State and Time
|
|
184
|
-
|
|
185
|
-
### Periodic Time Observer
|
|
186
|
-
|
|
187
|
-
Use `addPeriodicTimeObserver` to update UI elements like a progress bar:
|
|
188
|
-
|
|
189
|
-
```swift
|
|
190
|
-
import AVFoundation
|
|
191
|
-
|
|
192
|
-
@Observable
|
|
193
|
-
@MainActor
|
|
194
|
-
final class PlayerManager {
|
|
195
|
-
let player = AVPlayer()
|
|
196
|
-
var currentTime: Double = 0
|
|
197
|
-
var duration: Double = 0
|
|
198
|
-
var isPlaying = false
|
|
199
|
-
|
|
200
|
-
private var timeObserver: Any?
|
|
201
|
-
|
|
202
|
-
func startObserving() {
|
|
203
|
-
// Fire every 0.5 seconds on the main queue
|
|
204
|
-
let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
|
|
205
|
-
timeObserver = player.addPeriodicTimeObserver(
|
|
206
|
-
forInterval: interval,
|
|
207
|
-
queue: .main
|
|
208
|
-
) { [weak self] time in
|
|
209
|
-
guard let self else { return }
|
|
210
|
-
self.currentTime = time.seconds
|
|
211
|
-
self.duration = self.player.currentItem?.duration.seconds ?? 0
|
|
212
|
-
self.isPlaying = self.player.timeControlStatus == .playing
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
func stopObserving() {
|
|
217
|
-
if let observer = timeObserver {
|
|
218
|
-
player.removeTimeObserver(observer)
|
|
219
|
-
timeObserver = nil
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
deinit {
|
|
224
|
-
if let observer = timeObserver {
|
|
225
|
-
player.removeTimeObserver(observer)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Observing Player Status with KVO
|
|
232
|
-
|
|
233
|
-
Check player and item readiness before playing:
|
|
234
|
-
|
|
235
|
-
```swift
|
|
236
|
-
import AVFoundation
|
|
237
|
-
import Combine
|
|
238
|
-
|
|
239
|
-
// Using Combine
|
|
240
|
-
var cancellables = Set<AnyCancellable>()
|
|
241
|
-
|
|
242
|
-
player.publisher(for: \.status)
|
|
243
|
-
.sink { status in
|
|
244
|
-
switch status {
|
|
245
|
-
case .readyToPlay:
|
|
246
|
-
print("Ready to play")
|
|
247
|
-
case .failed:
|
|
248
|
-
print("Failed: \(player.error?.localizedDescription ?? "")")
|
|
249
|
-
case .unknown:
|
|
250
|
-
print("Status unknown")
|
|
251
|
-
@unknown default:
|
|
252
|
-
break
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
.store(in: &cancellables)
|
|
256
|
-
|
|
257
|
-
// Observe buffering state
|
|
258
|
-
player.publisher(for: \.timeControlStatus)
|
|
259
|
-
.sink { status in
|
|
260
|
-
switch status {
|
|
261
|
-
case .playing: print("Playing")
|
|
262
|
-
case .paused: print("Paused")
|
|
263
|
-
case .waitingToPlayAtSpecifiedRate:
|
|
264
|
-
print("Buffering: \(player.reasonForWaitingToPlay?.rawValue ?? "")")
|
|
265
|
-
@unknown default: break
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
.store(in: &cancellables)
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Detecting Playback End
|
|
272
|
-
|
|
273
|
-
```swift
|
|
274
|
-
NotificationCenter.default.addObserver(
|
|
275
|
-
forName: .AVPlayerItemDidPlayToEndTime,
|
|
276
|
-
object: player.currentItem,
|
|
277
|
-
queue: .main
|
|
278
|
-
) { _ in
|
|
279
|
-
// Playback finished -- loop, show replay button, or advance
|
|
280
|
-
player.seek(to: .zero) // Loop
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
## Streaming HLS Content
|
|
285
|
-
|
|
286
|
-
HTTP Live Streaming (HLS) works directly with `AVPlayer`. Pass the `.m3u8`
|
|
287
|
-
URL and AVFoundation handles adaptive bitrate selection, buffering, and
|
|
288
|
-
failover.
|
|
289
|
-
|
|
290
|
-
```swift
|
|
291
|
-
let hlsURL = URL(string: "https://example.com/stream/master.m3u8")!
|
|
292
|
-
let player = AVPlayer(url: hlsURL)
|
|
293
|
-
player.play()
|
|
294
|
-
|
|
295
|
-
// AVPlayer automatically selects the best variant based on:
|
|
296
|
-
// - Network bandwidth
|
|
297
|
-
// - Device capabilities
|
|
298
|
-
// - Display resolution
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Preferred Bitrate and Resolution
|
|
302
|
-
|
|
303
|
-
```swift
|
|
304
|
-
let item = AVPlayerItem(url: hlsURL)
|
|
305
|
-
|
|
306
|
-
// Limit maximum resolution (e.g., for cellular)
|
|
307
|
-
item.preferredMaximumResolution = CGSize(width: 1280, height: 720)
|
|
308
|
-
|
|
309
|
-
// Limit peak bitrate (bits per second)
|
|
310
|
-
item.preferredPeakBitRate = 2_000_000 // 2 Mbps
|
|
311
|
-
|
|
312
|
-
// For forward buffering duration
|
|
313
|
-
item.preferredForwardBufferDuration = 5 // seconds; 0 = system default
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## AVAudioSession Configuration
|
|
317
|
-
|
|
318
|
-
Configure `AVAudioSession` to tell the system how your app intends to use
|
|
319
|
-
audio. This affects audio routing, mixing behavior, and background playback.
|
|
320
|
-
|
|
321
|
-
Docs: [AVAudioSession](https://sosumi.ai/documentation/avfaudio/avaudiosession),
|
|
322
|
-
[AVAudioSession.Category](https://sosumi.ai/documentation/avfaudio/avaudiosession/category-swift.struct)
|
|
323
|
-
|
|
324
|
-
### Categories and Modes
|
|
325
|
-
|
|
326
|
-
| Category | Behavior | Common Use |
|
|
327
|
-
|---|---|---|
|
|
328
|
-
| `.playback` | Audio plays even with silent switch on; can play in background | Music, podcasts, video |
|
|
329
|
-
| `.playAndRecord` | Simultaneous input and output | Voice/video calls, recording with monitoring |
|
|
330
|
-
| `.ambient` | Mixes with other audio; silenced by switch | Game sound effects, casual audio |
|
|
331
|
-
| `.soloAmbient` | Default; silences other audio; silenced by switch | Default app behavior |
|
|
332
|
-
|
|
333
|
-
```swift
|
|
334
|
-
import AVFAudio
|
|
335
|
-
|
|
336
|
-
func configureAudioSession(forPlayback: Bool = true) throws {
|
|
337
|
-
let session = AVAudioSession.sharedInstance()
|
|
338
|
-
|
|
339
|
-
if forPlayback {
|
|
340
|
-
// Media playback: audio continues with silent switch, supports background
|
|
341
|
-
try session.setCategory(
|
|
342
|
-
.playback,
|
|
343
|
-
mode: .default,
|
|
344
|
-
options: []
|
|
345
|
-
)
|
|
346
|
-
} else {
|
|
347
|
-
// Mix with other apps (e.g., game sounds over user's music)
|
|
348
|
-
try session.setCategory(
|
|
349
|
-
.ambient,
|
|
350
|
-
mode: .default,
|
|
351
|
-
options: [.mixWithOthers]
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
try session.setActive(true)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// For video calls
|
|
359
|
-
try AVAudioSession.sharedInstance().setCategory(
|
|
360
|
-
.playAndRecord,
|
|
361
|
-
mode: .videoChat,
|
|
362
|
-
options: [.defaultToSpeaker, .allowBluetooth]
|
|
363
|
-
)
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Handling Audio Interruptions
|
|
367
|
-
|
|
368
|
-
```swift
|
|
369
|
-
NotificationCenter.default.addObserver(
|
|
370
|
-
forName: AVAudioSession.interruptionNotification,
|
|
371
|
-
object: AVAudioSession.sharedInstance(),
|
|
372
|
-
queue: .main
|
|
373
|
-
) { notification in
|
|
374
|
-
guard let info = notification.userInfo,
|
|
375
|
-
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
376
|
-
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
377
|
-
return
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
switch type {
|
|
381
|
-
case .began:
|
|
382
|
-
// Pause playback -- system has interrupted audio
|
|
383
|
-
player.pause()
|
|
384
|
-
case .ended:
|
|
385
|
-
let options = info[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0
|
|
386
|
-
if AVAudioSession.InterruptionOptions(rawValue: options).contains(.shouldResume) {
|
|
387
|
-
player.play()
|
|
388
|
-
}
|
|
389
|
-
@unknown default:
|
|
390
|
-
break
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### Handling Route Changes
|
|
396
|
-
|
|
397
|
-
```swift
|
|
398
|
-
NotificationCenter.default.addObserver(
|
|
399
|
-
forName: AVAudioSession.routeChangeNotification,
|
|
400
|
-
object: AVAudioSession.sharedInstance(),
|
|
401
|
-
queue: .main
|
|
402
|
-
) { notification in
|
|
403
|
-
guard let info = notification.userInfo,
|
|
404
|
-
let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
405
|
-
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
|
406
|
-
return
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if reason == .oldDeviceUnavailable {
|
|
410
|
-
// Headphones unplugged -- pause playback (Apple HIG requirement)
|
|
411
|
-
player.pause()
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
## Background Audio Setup
|
|
417
|
-
|
|
418
|
-
To play audio when the app is in the background, two things are required:
|
|
419
|
-
|
|
420
|
-
1. Enable the `audio` background mode in your app's capabilities.
|
|
421
|
-
2. Configure `AVAudioSession` with the `.playback` category.
|
|
422
|
-
|
|
423
|
-
### Info.plist Configuration
|
|
424
|
-
|
|
425
|
-
```xml
|
|
426
|
-
<key>UIBackgroundModes</key>
|
|
427
|
-
<array>
|
|
428
|
-
<string>audio</string>
|
|
429
|
-
</array>
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
Or enable "Audio, AirPlay, and Picture in Picture" in Xcode's Signing &
|
|
433
|
-
Capabilities tab.
|
|
434
|
-
|
|
435
|
-
### Activating Background Audio
|
|
436
|
-
|
|
437
|
-
```swift
|
|
438
|
-
import AVFAudio
|
|
439
|
-
|
|
440
|
-
func enableBackgroundAudio() throws {
|
|
441
|
-
let session = AVAudioSession.sharedInstance()
|
|
442
|
-
try session.setCategory(.playback, mode: .default)
|
|
443
|
-
try session.setActive(true)
|
|
444
|
-
}
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
**Rules:**
|
|
448
|
-
- Call `setCategory` before `setActive`.
|
|
449
|
-
- The `.playback` category is required; `.ambient` and `.soloAmbient` do not
|
|
450
|
-
support background audio.
|
|
451
|
-
- Deactivate the session when playback ends to let other apps use audio:
|
|
452
|
-
|
|
453
|
-
```swift
|
|
454
|
-
func deactivateAudioSession() {
|
|
455
|
-
try? AVAudioSession.sharedInstance().setActive(
|
|
456
|
-
false,
|
|
457
|
-
options: .notifyOthersOnDeactivation
|
|
458
|
-
)
|
|
459
|
-
}
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
## Now Playing Info Center
|
|
463
|
-
|
|
464
|
-
Update `MPNowPlayingInfoCenter` so the system displays track information on
|
|
465
|
-
the lock screen, Control Center, and connected accessories (CarPlay, AirPods).
|
|
466
|
-
|
|
467
|
-
Docs: [MPNowPlayingInfoCenter](https://sosumi.ai/documentation/mediaplayer/mpnowplayinginfocenter)
|
|
468
|
-
|
|
469
|
-
```swift
|
|
470
|
-
import MediaPlayer
|
|
471
|
-
|
|
472
|
-
func updateNowPlayingInfo(
|
|
473
|
-
title: String,
|
|
474
|
-
artist: String,
|
|
475
|
-
albumTitle: String? = nil,
|
|
476
|
-
duration: TimeInterval,
|
|
477
|
-
currentTime: TimeInterval,
|
|
478
|
-
artwork: UIImage? = nil
|
|
479
|
-
) {
|
|
480
|
-
var info: [String: Any] = [
|
|
481
|
-
MPMediaItemPropertyTitle: title,
|
|
482
|
-
MPMediaItemPropertyArtist: artist,
|
|
483
|
-
MPMediaItemPropertyPlaybackDuration: duration,
|
|
484
|
-
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime,
|
|
485
|
-
MPNowPlayingInfoPropertyPlaybackRate: 1.0,
|
|
486
|
-
MPNowPlayingInfoPropertyMediaType: MPNowPlayingInfoMediaType.audio.rawValue
|
|
487
|
-
]
|
|
488
|
-
|
|
489
|
-
if let albumTitle {
|
|
490
|
-
info[MPMediaItemPropertyAlbumTitle] = albumTitle
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if let artwork {
|
|
494
|
-
info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(
|
|
495
|
-
boundsSize: artwork.size
|
|
496
|
-
) { _ in artwork }
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Update elapsed time during playback
|
|
503
|
-
func updateElapsedTime(_ seconds: TimeInterval, rate: Float = 1.0) {
|
|
504
|
-
var info = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
|
|
505
|
-
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = seconds
|
|
506
|
-
info[MPNowPlayingInfoPropertyPlaybackRate] = rate
|
|
507
|
-
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
## MPRemoteCommandCenter
|
|
512
|
-
|
|
513
|
-
Register handlers for lock screen, Control Center, and accessory controls
|
|
514
|
-
(play, pause, skip, seek). Without these, the system controls won't work.
|
|
515
|
-
|
|
516
|
-
Docs: [MPRemoteCommandCenter](https://sosumi.ai/documentation/mediaplayer/mpremotecommandcenter)
|
|
517
|
-
|
|
518
|
-
```swift
|
|
519
|
-
import MediaPlayer
|
|
520
|
-
|
|
521
|
-
func setupRemoteCommands(player: AVPlayer) {
|
|
522
|
-
let commandCenter = MPRemoteCommandCenter.shared()
|
|
523
|
-
|
|
524
|
-
// Play
|
|
525
|
-
commandCenter.playCommand.isEnabled = true
|
|
526
|
-
commandCenter.playCommand.addTarget { _ in
|
|
527
|
-
player.play()
|
|
528
|
-
return .success
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Pause
|
|
532
|
-
commandCenter.pauseCommand.isEnabled = true
|
|
533
|
-
commandCenter.pauseCommand.addTarget { _ in
|
|
534
|
-
player.pause()
|
|
535
|
-
return .success
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Toggle play/pause (headphone button, etc.)
|
|
539
|
-
commandCenter.togglePlayPauseCommand.isEnabled = true
|
|
540
|
-
commandCenter.togglePlayPauseCommand.addTarget { _ in
|
|
541
|
-
if player.timeControlStatus == .playing {
|
|
542
|
-
player.pause()
|
|
543
|
-
} else {
|
|
544
|
-
player.play()
|
|
545
|
-
}
|
|
546
|
-
return .success
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Skip forward (e.g., 15 seconds)
|
|
550
|
-
commandCenter.skipForwardCommand.isEnabled = true
|
|
551
|
-
commandCenter.skipForwardCommand.preferredIntervals = [15]
|
|
552
|
-
commandCenter.skipForwardCommand.addTarget { event in
|
|
553
|
-
guard let skipEvent = event as? MPSkipIntervalCommandEvent else {
|
|
554
|
-
return .commandFailed
|
|
555
|
-
}
|
|
556
|
-
let currentTime = player.currentTime().seconds
|
|
557
|
-
let target = CMTime(
|
|
558
|
-
seconds: currentTime + skipEvent.interval,
|
|
559
|
-
preferredTimescale: 600
|
|
560
|
-
)
|
|
561
|
-
player.seek(to: target)
|
|
562
|
-
return .success
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Skip backward (e.g., 15 seconds)
|
|
566
|
-
commandCenter.skipBackwardCommand.isEnabled = true
|
|
567
|
-
commandCenter.skipBackwardCommand.preferredIntervals = [15]
|
|
568
|
-
commandCenter.skipBackwardCommand.addTarget { event in
|
|
569
|
-
guard let skipEvent = event as? MPSkipIntervalCommandEvent else {
|
|
570
|
-
return .commandFailed
|
|
571
|
-
}
|
|
572
|
-
let currentTime = player.currentTime().seconds
|
|
573
|
-
let target = CMTime(
|
|
574
|
-
seconds: max(0, currentTime - skipEvent.interval),
|
|
575
|
-
preferredTimescale: 600
|
|
576
|
-
)
|
|
577
|
-
player.seek(to: target)
|
|
578
|
-
return .success
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Scrubbing (seek bar on lock screen)
|
|
582
|
-
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
|
583
|
-
commandCenter.changePlaybackPositionCommand.addTarget { event in
|
|
584
|
-
guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent else {
|
|
585
|
-
return .commandFailed
|
|
586
|
-
}
|
|
587
|
-
let target = CMTime(
|
|
588
|
-
seconds: positionEvent.positionTime,
|
|
589
|
-
preferredTimescale: 600
|
|
590
|
-
)
|
|
591
|
-
player.seek(to: target)
|
|
592
|
-
return .success
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// Disable unsupported commands to remove them from UI
|
|
596
|
-
commandCenter.nextTrackCommand.isEnabled = false
|
|
597
|
-
commandCenter.previousTrackCommand.isEnabled = false
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
### Cleanup
|
|
602
|
-
|
|
603
|
-
```swift
|
|
604
|
-
func teardownRemoteCommands() {
|
|
605
|
-
let commandCenter = MPRemoteCommandCenter.shared()
|
|
606
|
-
commandCenter.playCommand.removeTarget(nil)
|
|
607
|
-
commandCenter.pauseCommand.removeTarget(nil)
|
|
608
|
-
commandCenter.togglePlayPauseCommand.removeTarget(nil)
|
|
609
|
-
commandCenter.skipForwardCommand.removeTarget(nil)
|
|
610
|
-
commandCenter.skipBackwardCommand.removeTarget(nil)
|
|
611
|
-
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
|
|
612
|
-
}
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
## Picture-in-Picture
|
|
616
|
-
|
|
617
|
-
`AVPictureInPictureController` enables floating video playback that continues
|
|
618
|
-
when the user navigates away. Requires the `audio` background mode.
|
|
619
|
-
|
|
620
|
-
Docs: [AVPictureInPictureController](https://sosumi.ai/documentation/avkit/avpictureinpicturecontroller),
|
|
621
|
-
[Adopting Picture in Picture in a Custom Player](https://sosumi.ai/documentation/avkit/adopting-picture-in-picture-in-a-custom-player)
|
|
622
|
-
|
|
623
|
-
### With AVPlayerViewController (Automatic)
|
|
624
|
-
|
|
625
|
-
`AVPlayerViewController` supports PiP automatically when the background audio
|
|
626
|
-
capability is enabled. No extra code needed.
|
|
627
|
-
|
|
628
|
-
```swift
|
|
629
|
-
let controller = AVPlayerViewController()
|
|
630
|
-
controller.player = player
|
|
631
|
-
controller.allowsPictureInPicturePlayback = true // true by default
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
### With a Custom Player
|
|
635
|
-
|
|
636
|
-
```swift
|
|
637
|
-
import AVKit
|
|
638
|
-
|
|
639
|
-
@Observable
|
|
640
|
-
@MainActor
|
|
641
|
-
final class PiPManager: NSObject, AVPictureInPictureControllerDelegate {
|
|
642
|
-
private var pipController: AVPictureInPictureController?
|
|
643
|
-
var isPiPActive = false
|
|
644
|
-
var isPiPPossible = false
|
|
645
|
-
|
|
646
|
-
func setup(playerLayer: AVPlayerLayer) {
|
|
647
|
-
guard AVPictureInPictureController.isPictureInPictureSupported() else {
|
|
648
|
-
return
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
pipController = AVPictureInPictureController(playerLayer: playerLayer)
|
|
652
|
-
pipController?.delegate = self
|
|
653
|
-
|
|
654
|
-
// Auto-start PiP when app goes to background
|
|
655
|
-
pipController?.canStartPictureInPictureAutomaticallyFromInline = true
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
func togglePiP() {
|
|
659
|
-
guard let pipController else { return }
|
|
660
|
-
if pipController.isPictureInPictureActive {
|
|
661
|
-
pipController.stopPictureInPicture()
|
|
662
|
-
} else {
|
|
663
|
-
pipController.startPictureInPicture()
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// MARK: - AVPictureInPictureControllerDelegate
|
|
668
|
-
|
|
669
|
-
nonisolated func pictureInPictureControllerWillStartPictureInPicture(
|
|
670
|
-
_ controller: AVPictureInPictureController
|
|
671
|
-
) {
|
|
672
|
-
Task { @MainActor in isPiPActive = true }
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
nonisolated func pictureInPictureControllerDidStopPictureInPicture(
|
|
676
|
-
_ controller: AVPictureInPictureController
|
|
677
|
-
) {
|
|
678
|
-
Task { @MainActor in isPiPActive = false }
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
nonisolated func pictureInPictureController(
|
|
682
|
-
_ controller: AVPictureInPictureController,
|
|
683
|
-
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
|
|
684
|
-
completionHandler: @escaping (Bool) -> Void
|
|
685
|
-
) {
|
|
686
|
-
// Restore your player UI here, then call the handler
|
|
687
|
-
Task { @MainActor in
|
|
688
|
-
// Navigate back to the player view
|
|
689
|
-
completionHandler(true)
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
### PiP Requirements Checklist
|
|
696
|
-
|
|
697
|
-
- [ ] `UIBackgroundModes` includes `audio` in Info.plist
|
|
698
|
-
- [ ] `AVAudioSession` category set to `.playback`
|
|
699
|
-
- [ ] Check `AVPictureInPictureController.isPictureInPictureSupported()` before setup
|
|
700
|
-
- [ ] Implement `restoreUserInterfaceForPictureInPictureStop` delegate method
|
|
701
|
-
- [ ] Call completion handler in the restore delegate method (failure to call causes hangs)
|
|
1
|
+
# AV Playback
|
|
2
|
+
|
|
3
|
+
Patterns for media playback with AVPlayer, streaming HLS content, audio
|
|
4
|
+
session configuration, background audio, Now Playing integration, remote
|
|
5
|
+
command handling, and Picture-in-Picture.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [AVPlayer and AVPlayerViewController Setup](#avplayer-and-avplayerviewcontroller-setup)
|
|
10
|
+
- [AVPlayerItem and AVAsset Loading](#avplayeritem-and-avasset-loading)
|
|
11
|
+
- [Playback Controls](#playback-controls)
|
|
12
|
+
- [Observing Player State and Time](#observing-player-state-and-time)
|
|
13
|
+
- [Streaming HLS Content](#streaming-hls-content)
|
|
14
|
+
- [AVAudioSession Configuration](#avaudiosession-configuration)
|
|
15
|
+
- [Background Audio Setup](#background-audio-setup)
|
|
16
|
+
- [Now Playing Info Center](#now-playing-info-center)
|
|
17
|
+
- [MPRemoteCommandCenter](#mpremotecommandcenter)
|
|
18
|
+
- [Picture-in-Picture](#picture-in-picture)
|
|
19
|
+
|
|
20
|
+
## AVPlayer and AVPlayerViewController Setup
|
|
21
|
+
|
|
22
|
+
`AVPlayer` manages playback of a single media asset. Use
|
|
23
|
+
`AVPlayerViewController` (AVKit) for the system-standard playback UI with
|
|
24
|
+
transport controls, or `AVPlayerLayer` for a custom player interface.
|
|
25
|
+
|
|
26
|
+
Docs: [AVPlayer](https://sosumi.ai/documentation/avfoundation/avplayer),
|
|
27
|
+
[AVPlayerViewController](https://sosumi.ai/documentation/avkit/avplayerviewcontroller)
|
|
28
|
+
|
|
29
|
+
### AVPlayerViewController in SwiftUI
|
|
30
|
+
|
|
31
|
+
```swift
|
|
32
|
+
import SwiftUI
|
|
33
|
+
import AVKit
|
|
34
|
+
|
|
35
|
+
struct VideoPlayerView: View {
|
|
36
|
+
let url: URL
|
|
37
|
+
@State private var player: AVPlayer?
|
|
38
|
+
|
|
39
|
+
var body: some View {
|
|
40
|
+
VideoPlayer(player: player)
|
|
41
|
+
.onAppear {
|
|
42
|
+
player = AVPlayer(url: url)
|
|
43
|
+
player?.play()
|
|
44
|
+
}
|
|
45
|
+
.onDisappear {
|
|
46
|
+
player?.pause()
|
|
47
|
+
player = nil
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### AVPlayerViewController in UIKit
|
|
54
|
+
|
|
55
|
+
```swift
|
|
56
|
+
import UIKit
|
|
57
|
+
import AVKit
|
|
58
|
+
|
|
59
|
+
final class VideoViewController: UIViewController {
|
|
60
|
+
private var player: AVPlayer?
|
|
61
|
+
|
|
62
|
+
func presentVideo(url: URL) {
|
|
63
|
+
player = AVPlayer(url: url)
|
|
64
|
+
let controller = AVPlayerViewController()
|
|
65
|
+
controller.player = player
|
|
66
|
+
present(controller, animated: true) {
|
|
67
|
+
self.player?.play()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Custom Player with AVPlayerLayer
|
|
74
|
+
|
|
75
|
+
For full control over the player UI, embed an `AVPlayerLayer`:
|
|
76
|
+
|
|
77
|
+
```swift
|
|
78
|
+
import AVFoundation
|
|
79
|
+
import UIKit
|
|
80
|
+
|
|
81
|
+
final class PlayerView: UIView {
|
|
82
|
+
override class var layerClass: AnyClass { AVPlayerLayer.self }
|
|
83
|
+
|
|
84
|
+
var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
|
|
85
|
+
|
|
86
|
+
var player: AVPlayer? {
|
|
87
|
+
get { playerLayer.player }
|
|
88
|
+
set { playerLayer.player = newValue }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func configure() {
|
|
92
|
+
playerLayer.videoGravity = .resizeAspect
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## AVPlayerItem and AVAsset Loading
|
|
98
|
+
|
|
99
|
+
`AVAsset` represents the static media (duration, tracks, metadata).
|
|
100
|
+
`AVPlayerItem` adds the dynamic state (current time, buffering status) needed
|
|
101
|
+
for playback.
|
|
102
|
+
|
|
103
|
+
Docs: [AVPlayerItem](https://sosumi.ai/documentation/avfoundation/avplayeritem),
|
|
104
|
+
[AVAsset](https://sosumi.ai/documentation/avfoundation/avasset)
|
|
105
|
+
|
|
106
|
+
```swift
|
|
107
|
+
import AVFoundation
|
|
108
|
+
|
|
109
|
+
// Local file
|
|
110
|
+
let localURL = Bundle.main.url(forResource: "intro", withExtension: "mp4")!
|
|
111
|
+
let localItem = AVPlayerItem(url: localURL)
|
|
112
|
+
|
|
113
|
+
// Remote file
|
|
114
|
+
let remoteURL = URL(string: "https://example.com/video.mp4")!
|
|
115
|
+
let remoteItem = AVPlayerItem(url: remoteURL)
|
|
116
|
+
|
|
117
|
+
// From an existing AVAsset (for more control)
|
|
118
|
+
let asset = AVURLAsset(url: remoteURL, options: [
|
|
119
|
+
AVURLAssetPreferPreciseDurationAndTimingKey: true
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
// Load properties asynchronously before playback (iOS 15+)
|
|
123
|
+
let duration = try await asset.load(.duration)
|
|
124
|
+
let tracks = try await asset.load(.tracks)
|
|
125
|
+
let isPlayable = try await asset.load(.isPlayable)
|
|
126
|
+
|
|
127
|
+
let item = AVPlayerItem(asset: asset)
|
|
128
|
+
let player = AVPlayer(playerItem: item)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Replacing the Current Item
|
|
132
|
+
|
|
133
|
+
Reuse a single `AVPlayer` and swap items:
|
|
134
|
+
|
|
135
|
+
```swift
|
|
136
|
+
let nextItem = AVPlayerItem(url: nextVideoURL)
|
|
137
|
+
player.replaceCurrentItem(with: nextItem)
|
|
138
|
+
player.play()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Queue Playback with AVQueuePlayer
|
|
142
|
+
|
|
143
|
+
```swift
|
|
144
|
+
let items = videoURLs.map { AVPlayerItem(url: $0) }
|
|
145
|
+
let queuePlayer = AVQueuePlayer(items: items)
|
|
146
|
+
queuePlayer.play()
|
|
147
|
+
// Automatically advances to the next item
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Playback Controls
|
|
151
|
+
|
|
152
|
+
```swift
|
|
153
|
+
// Play
|
|
154
|
+
player.play()
|
|
155
|
+
|
|
156
|
+
// Pause
|
|
157
|
+
player.pause()
|
|
158
|
+
|
|
159
|
+
// Set playback rate (1.0 = normal, 2.0 = 2x, 0.5 = half speed)
|
|
160
|
+
player.rate = 1.5
|
|
161
|
+
|
|
162
|
+
// Seek to a specific time
|
|
163
|
+
let targetTime = CMTime(seconds: 30, preferredTimescale: 600)
|
|
164
|
+
await player.seek(to: targetTime)
|
|
165
|
+
|
|
166
|
+
// Seek with tolerance (for precise seeking, e.g., scrubbing)
|
|
167
|
+
await player.seek(
|
|
168
|
+
to: targetTime,
|
|
169
|
+
toleranceBefore: .zero,
|
|
170
|
+
toleranceAfter: .zero
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// Seek to a percentage of duration
|
|
174
|
+
func seekToPercentage(_ percentage: Double) async {
|
|
175
|
+
guard let duration = player.currentItem?.duration,
|
|
176
|
+
duration.isNumeric else { return }
|
|
177
|
+
let targetSeconds = duration.seconds * percentage
|
|
178
|
+
let target = CMTime(seconds: targetSeconds, preferredTimescale: 600)
|
|
179
|
+
await player.seek(to: target)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Observing Player State and Time
|
|
184
|
+
|
|
185
|
+
### Periodic Time Observer
|
|
186
|
+
|
|
187
|
+
Use `addPeriodicTimeObserver` to update UI elements like a progress bar:
|
|
188
|
+
|
|
189
|
+
```swift
|
|
190
|
+
import AVFoundation
|
|
191
|
+
|
|
192
|
+
@Observable
|
|
193
|
+
@MainActor
|
|
194
|
+
final class PlayerManager {
|
|
195
|
+
let player = AVPlayer()
|
|
196
|
+
var currentTime: Double = 0
|
|
197
|
+
var duration: Double = 0
|
|
198
|
+
var isPlaying = false
|
|
199
|
+
|
|
200
|
+
private var timeObserver: Any?
|
|
201
|
+
|
|
202
|
+
func startObserving() {
|
|
203
|
+
// Fire every 0.5 seconds on the main queue
|
|
204
|
+
let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
|
|
205
|
+
timeObserver = player.addPeriodicTimeObserver(
|
|
206
|
+
forInterval: interval,
|
|
207
|
+
queue: .main
|
|
208
|
+
) { [weak self] time in
|
|
209
|
+
guard let self else { return }
|
|
210
|
+
self.currentTime = time.seconds
|
|
211
|
+
self.duration = self.player.currentItem?.duration.seconds ?? 0
|
|
212
|
+
self.isPlaying = self.player.timeControlStatus == .playing
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func stopObserving() {
|
|
217
|
+
if let observer = timeObserver {
|
|
218
|
+
player.removeTimeObserver(observer)
|
|
219
|
+
timeObserver = nil
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
deinit {
|
|
224
|
+
if let observer = timeObserver {
|
|
225
|
+
player.removeTimeObserver(observer)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Observing Player Status with KVO
|
|
232
|
+
|
|
233
|
+
Check player and item readiness before playing:
|
|
234
|
+
|
|
235
|
+
```swift
|
|
236
|
+
import AVFoundation
|
|
237
|
+
import Combine
|
|
238
|
+
|
|
239
|
+
// Using Combine
|
|
240
|
+
var cancellables = Set<AnyCancellable>()
|
|
241
|
+
|
|
242
|
+
player.publisher(for: \.status)
|
|
243
|
+
.sink { status in
|
|
244
|
+
switch status {
|
|
245
|
+
case .readyToPlay:
|
|
246
|
+
print("Ready to play")
|
|
247
|
+
case .failed:
|
|
248
|
+
print("Failed: \(player.error?.localizedDescription ?? "")")
|
|
249
|
+
case .unknown:
|
|
250
|
+
print("Status unknown")
|
|
251
|
+
@unknown default:
|
|
252
|
+
break
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
.store(in: &cancellables)
|
|
256
|
+
|
|
257
|
+
// Observe buffering state
|
|
258
|
+
player.publisher(for: \.timeControlStatus)
|
|
259
|
+
.sink { status in
|
|
260
|
+
switch status {
|
|
261
|
+
case .playing: print("Playing")
|
|
262
|
+
case .paused: print("Paused")
|
|
263
|
+
case .waitingToPlayAtSpecifiedRate:
|
|
264
|
+
print("Buffering: \(player.reasonForWaitingToPlay?.rawValue ?? "")")
|
|
265
|
+
@unknown default: break
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
.store(in: &cancellables)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Detecting Playback End
|
|
272
|
+
|
|
273
|
+
```swift
|
|
274
|
+
NotificationCenter.default.addObserver(
|
|
275
|
+
forName: .AVPlayerItemDidPlayToEndTime,
|
|
276
|
+
object: player.currentItem,
|
|
277
|
+
queue: .main
|
|
278
|
+
) { _ in
|
|
279
|
+
// Playback finished -- loop, show replay button, or advance
|
|
280
|
+
player.seek(to: .zero) // Loop
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Streaming HLS Content
|
|
285
|
+
|
|
286
|
+
HTTP Live Streaming (HLS) works directly with `AVPlayer`. Pass the `.m3u8`
|
|
287
|
+
URL and AVFoundation handles adaptive bitrate selection, buffering, and
|
|
288
|
+
failover.
|
|
289
|
+
|
|
290
|
+
```swift
|
|
291
|
+
let hlsURL = URL(string: "https://example.com/stream/master.m3u8")!
|
|
292
|
+
let player = AVPlayer(url: hlsURL)
|
|
293
|
+
player.play()
|
|
294
|
+
|
|
295
|
+
// AVPlayer automatically selects the best variant based on:
|
|
296
|
+
// - Network bandwidth
|
|
297
|
+
// - Device capabilities
|
|
298
|
+
// - Display resolution
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Preferred Bitrate and Resolution
|
|
302
|
+
|
|
303
|
+
```swift
|
|
304
|
+
let item = AVPlayerItem(url: hlsURL)
|
|
305
|
+
|
|
306
|
+
// Limit maximum resolution (e.g., for cellular)
|
|
307
|
+
item.preferredMaximumResolution = CGSize(width: 1280, height: 720)
|
|
308
|
+
|
|
309
|
+
// Limit peak bitrate (bits per second)
|
|
310
|
+
item.preferredPeakBitRate = 2_000_000 // 2 Mbps
|
|
311
|
+
|
|
312
|
+
// For forward buffering duration
|
|
313
|
+
item.preferredForwardBufferDuration = 5 // seconds; 0 = system default
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## AVAudioSession Configuration
|
|
317
|
+
|
|
318
|
+
Configure `AVAudioSession` to tell the system how your app intends to use
|
|
319
|
+
audio. This affects audio routing, mixing behavior, and background playback.
|
|
320
|
+
|
|
321
|
+
Docs: [AVAudioSession](https://sosumi.ai/documentation/avfaudio/avaudiosession),
|
|
322
|
+
[AVAudioSession.Category](https://sosumi.ai/documentation/avfaudio/avaudiosession/category-swift.struct)
|
|
323
|
+
|
|
324
|
+
### Categories and Modes
|
|
325
|
+
|
|
326
|
+
| Category | Behavior | Common Use |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| `.playback` | Audio plays even with silent switch on; can play in background | Music, podcasts, video |
|
|
329
|
+
| `.playAndRecord` | Simultaneous input and output | Voice/video calls, recording with monitoring |
|
|
330
|
+
| `.ambient` | Mixes with other audio; silenced by switch | Game sound effects, casual audio |
|
|
331
|
+
| `.soloAmbient` | Default; silences other audio; silenced by switch | Default app behavior |
|
|
332
|
+
|
|
333
|
+
```swift
|
|
334
|
+
import AVFAudio
|
|
335
|
+
|
|
336
|
+
func configureAudioSession(forPlayback: Bool = true) throws {
|
|
337
|
+
let session = AVAudioSession.sharedInstance()
|
|
338
|
+
|
|
339
|
+
if forPlayback {
|
|
340
|
+
// Media playback: audio continues with silent switch, supports background
|
|
341
|
+
try session.setCategory(
|
|
342
|
+
.playback,
|
|
343
|
+
mode: .default,
|
|
344
|
+
options: []
|
|
345
|
+
)
|
|
346
|
+
} else {
|
|
347
|
+
// Mix with other apps (e.g., game sounds over user's music)
|
|
348
|
+
try session.setCategory(
|
|
349
|
+
.ambient,
|
|
350
|
+
mode: .default,
|
|
351
|
+
options: [.mixWithOthers]
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try session.setActive(true)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// For video calls
|
|
359
|
+
try AVAudioSession.sharedInstance().setCategory(
|
|
360
|
+
.playAndRecord,
|
|
361
|
+
mode: .videoChat,
|
|
362
|
+
options: [.defaultToSpeaker, .allowBluetooth]
|
|
363
|
+
)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Handling Audio Interruptions
|
|
367
|
+
|
|
368
|
+
```swift
|
|
369
|
+
NotificationCenter.default.addObserver(
|
|
370
|
+
forName: AVAudioSession.interruptionNotification,
|
|
371
|
+
object: AVAudioSession.sharedInstance(),
|
|
372
|
+
queue: .main
|
|
373
|
+
) { notification in
|
|
374
|
+
guard let info = notification.userInfo,
|
|
375
|
+
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
376
|
+
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
switch type {
|
|
381
|
+
case .began:
|
|
382
|
+
// Pause playback -- system has interrupted audio
|
|
383
|
+
player.pause()
|
|
384
|
+
case .ended:
|
|
385
|
+
let options = info[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0
|
|
386
|
+
if AVAudioSession.InterruptionOptions(rawValue: options).contains(.shouldResume) {
|
|
387
|
+
player.play()
|
|
388
|
+
}
|
|
389
|
+
@unknown default:
|
|
390
|
+
break
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Handling Route Changes
|
|
396
|
+
|
|
397
|
+
```swift
|
|
398
|
+
NotificationCenter.default.addObserver(
|
|
399
|
+
forName: AVAudioSession.routeChangeNotification,
|
|
400
|
+
object: AVAudioSession.sharedInstance(),
|
|
401
|
+
queue: .main
|
|
402
|
+
) { notification in
|
|
403
|
+
guard let info = notification.userInfo,
|
|
404
|
+
let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
405
|
+
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if reason == .oldDeviceUnavailable {
|
|
410
|
+
// Headphones unplugged -- pause playback (Apple HIG requirement)
|
|
411
|
+
player.pause()
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Background Audio Setup
|
|
417
|
+
|
|
418
|
+
To play audio when the app is in the background, two things are required:
|
|
419
|
+
|
|
420
|
+
1. Enable the `audio` background mode in your app's capabilities.
|
|
421
|
+
2. Configure `AVAudioSession` with the `.playback` category.
|
|
422
|
+
|
|
423
|
+
### Info.plist Configuration
|
|
424
|
+
|
|
425
|
+
```xml
|
|
426
|
+
<key>UIBackgroundModes</key>
|
|
427
|
+
<array>
|
|
428
|
+
<string>audio</string>
|
|
429
|
+
</array>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Or enable "Audio, AirPlay, and Picture in Picture" in Xcode's Signing &
|
|
433
|
+
Capabilities tab.
|
|
434
|
+
|
|
435
|
+
### Activating Background Audio
|
|
436
|
+
|
|
437
|
+
```swift
|
|
438
|
+
import AVFAudio
|
|
439
|
+
|
|
440
|
+
func enableBackgroundAudio() throws {
|
|
441
|
+
let session = AVAudioSession.sharedInstance()
|
|
442
|
+
try session.setCategory(.playback, mode: .default)
|
|
443
|
+
try session.setActive(true)
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Rules:**
|
|
448
|
+
- Call `setCategory` before `setActive`.
|
|
449
|
+
- The `.playback` category is required; `.ambient` and `.soloAmbient` do not
|
|
450
|
+
support background audio.
|
|
451
|
+
- Deactivate the session when playback ends to let other apps use audio:
|
|
452
|
+
|
|
453
|
+
```swift
|
|
454
|
+
func deactivateAudioSession() {
|
|
455
|
+
try? AVAudioSession.sharedInstance().setActive(
|
|
456
|
+
false,
|
|
457
|
+
options: .notifyOthersOnDeactivation
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Now Playing Info Center
|
|
463
|
+
|
|
464
|
+
Update `MPNowPlayingInfoCenter` so the system displays track information on
|
|
465
|
+
the lock screen, Control Center, and connected accessories (CarPlay, AirPods).
|
|
466
|
+
|
|
467
|
+
Docs: [MPNowPlayingInfoCenter](https://sosumi.ai/documentation/mediaplayer/mpnowplayinginfocenter)
|
|
468
|
+
|
|
469
|
+
```swift
|
|
470
|
+
import MediaPlayer
|
|
471
|
+
|
|
472
|
+
func updateNowPlayingInfo(
|
|
473
|
+
title: String,
|
|
474
|
+
artist: String,
|
|
475
|
+
albumTitle: String? = nil,
|
|
476
|
+
duration: TimeInterval,
|
|
477
|
+
currentTime: TimeInterval,
|
|
478
|
+
artwork: UIImage? = nil
|
|
479
|
+
) {
|
|
480
|
+
var info: [String: Any] = [
|
|
481
|
+
MPMediaItemPropertyTitle: title,
|
|
482
|
+
MPMediaItemPropertyArtist: artist,
|
|
483
|
+
MPMediaItemPropertyPlaybackDuration: duration,
|
|
484
|
+
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime,
|
|
485
|
+
MPNowPlayingInfoPropertyPlaybackRate: 1.0,
|
|
486
|
+
MPNowPlayingInfoPropertyMediaType: MPNowPlayingInfoMediaType.audio.rawValue
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
if let albumTitle {
|
|
490
|
+
info[MPMediaItemPropertyAlbumTitle] = albumTitle
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if let artwork {
|
|
494
|
+
info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(
|
|
495
|
+
boundsSize: artwork.size
|
|
496
|
+
) { _ in artwork }
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Update elapsed time during playback
|
|
503
|
+
func updateElapsedTime(_ seconds: TimeInterval, rate: Float = 1.0) {
|
|
504
|
+
var info = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
|
|
505
|
+
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = seconds
|
|
506
|
+
info[MPNowPlayingInfoPropertyPlaybackRate] = rate
|
|
507
|
+
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## MPRemoteCommandCenter
|
|
512
|
+
|
|
513
|
+
Register handlers for lock screen, Control Center, and accessory controls
|
|
514
|
+
(play, pause, skip, seek). Without these, the system controls won't work.
|
|
515
|
+
|
|
516
|
+
Docs: [MPRemoteCommandCenter](https://sosumi.ai/documentation/mediaplayer/mpremotecommandcenter)
|
|
517
|
+
|
|
518
|
+
```swift
|
|
519
|
+
import MediaPlayer
|
|
520
|
+
|
|
521
|
+
func setupRemoteCommands(player: AVPlayer) {
|
|
522
|
+
let commandCenter = MPRemoteCommandCenter.shared()
|
|
523
|
+
|
|
524
|
+
// Play
|
|
525
|
+
commandCenter.playCommand.isEnabled = true
|
|
526
|
+
commandCenter.playCommand.addTarget { _ in
|
|
527
|
+
player.play()
|
|
528
|
+
return .success
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Pause
|
|
532
|
+
commandCenter.pauseCommand.isEnabled = true
|
|
533
|
+
commandCenter.pauseCommand.addTarget { _ in
|
|
534
|
+
player.pause()
|
|
535
|
+
return .success
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Toggle play/pause (headphone button, etc.)
|
|
539
|
+
commandCenter.togglePlayPauseCommand.isEnabled = true
|
|
540
|
+
commandCenter.togglePlayPauseCommand.addTarget { _ in
|
|
541
|
+
if player.timeControlStatus == .playing {
|
|
542
|
+
player.pause()
|
|
543
|
+
} else {
|
|
544
|
+
player.play()
|
|
545
|
+
}
|
|
546
|
+
return .success
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Skip forward (e.g., 15 seconds)
|
|
550
|
+
commandCenter.skipForwardCommand.isEnabled = true
|
|
551
|
+
commandCenter.skipForwardCommand.preferredIntervals = [15]
|
|
552
|
+
commandCenter.skipForwardCommand.addTarget { event in
|
|
553
|
+
guard let skipEvent = event as? MPSkipIntervalCommandEvent else {
|
|
554
|
+
return .commandFailed
|
|
555
|
+
}
|
|
556
|
+
let currentTime = player.currentTime().seconds
|
|
557
|
+
let target = CMTime(
|
|
558
|
+
seconds: currentTime + skipEvent.interval,
|
|
559
|
+
preferredTimescale: 600
|
|
560
|
+
)
|
|
561
|
+
player.seek(to: target)
|
|
562
|
+
return .success
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Skip backward (e.g., 15 seconds)
|
|
566
|
+
commandCenter.skipBackwardCommand.isEnabled = true
|
|
567
|
+
commandCenter.skipBackwardCommand.preferredIntervals = [15]
|
|
568
|
+
commandCenter.skipBackwardCommand.addTarget { event in
|
|
569
|
+
guard let skipEvent = event as? MPSkipIntervalCommandEvent else {
|
|
570
|
+
return .commandFailed
|
|
571
|
+
}
|
|
572
|
+
let currentTime = player.currentTime().seconds
|
|
573
|
+
let target = CMTime(
|
|
574
|
+
seconds: max(0, currentTime - skipEvent.interval),
|
|
575
|
+
preferredTimescale: 600
|
|
576
|
+
)
|
|
577
|
+
player.seek(to: target)
|
|
578
|
+
return .success
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Scrubbing (seek bar on lock screen)
|
|
582
|
+
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
|
583
|
+
commandCenter.changePlaybackPositionCommand.addTarget { event in
|
|
584
|
+
guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent else {
|
|
585
|
+
return .commandFailed
|
|
586
|
+
}
|
|
587
|
+
let target = CMTime(
|
|
588
|
+
seconds: positionEvent.positionTime,
|
|
589
|
+
preferredTimescale: 600
|
|
590
|
+
)
|
|
591
|
+
player.seek(to: target)
|
|
592
|
+
return .success
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Disable unsupported commands to remove them from UI
|
|
596
|
+
commandCenter.nextTrackCommand.isEnabled = false
|
|
597
|
+
commandCenter.previousTrackCommand.isEnabled = false
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Cleanup
|
|
602
|
+
|
|
603
|
+
```swift
|
|
604
|
+
func teardownRemoteCommands() {
|
|
605
|
+
let commandCenter = MPRemoteCommandCenter.shared()
|
|
606
|
+
commandCenter.playCommand.removeTarget(nil)
|
|
607
|
+
commandCenter.pauseCommand.removeTarget(nil)
|
|
608
|
+
commandCenter.togglePlayPauseCommand.removeTarget(nil)
|
|
609
|
+
commandCenter.skipForwardCommand.removeTarget(nil)
|
|
610
|
+
commandCenter.skipBackwardCommand.removeTarget(nil)
|
|
611
|
+
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## Picture-in-Picture
|
|
616
|
+
|
|
617
|
+
`AVPictureInPictureController` enables floating video playback that continues
|
|
618
|
+
when the user navigates away. Requires the `audio` background mode.
|
|
619
|
+
|
|
620
|
+
Docs: [AVPictureInPictureController](https://sosumi.ai/documentation/avkit/avpictureinpicturecontroller),
|
|
621
|
+
[Adopting Picture in Picture in a Custom Player](https://sosumi.ai/documentation/avkit/adopting-picture-in-picture-in-a-custom-player)
|
|
622
|
+
|
|
623
|
+
### With AVPlayerViewController (Automatic)
|
|
624
|
+
|
|
625
|
+
`AVPlayerViewController` supports PiP automatically when the background audio
|
|
626
|
+
capability is enabled. No extra code needed.
|
|
627
|
+
|
|
628
|
+
```swift
|
|
629
|
+
let controller = AVPlayerViewController()
|
|
630
|
+
controller.player = player
|
|
631
|
+
controller.allowsPictureInPicturePlayback = true // true by default
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### With a Custom Player
|
|
635
|
+
|
|
636
|
+
```swift
|
|
637
|
+
import AVKit
|
|
638
|
+
|
|
639
|
+
@Observable
|
|
640
|
+
@MainActor
|
|
641
|
+
final class PiPManager: NSObject, AVPictureInPictureControllerDelegate {
|
|
642
|
+
private var pipController: AVPictureInPictureController?
|
|
643
|
+
var isPiPActive = false
|
|
644
|
+
var isPiPPossible = false
|
|
645
|
+
|
|
646
|
+
func setup(playerLayer: AVPlayerLayer) {
|
|
647
|
+
guard AVPictureInPictureController.isPictureInPictureSupported() else {
|
|
648
|
+
return
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
pipController = AVPictureInPictureController(playerLayer: playerLayer)
|
|
652
|
+
pipController?.delegate = self
|
|
653
|
+
|
|
654
|
+
// Auto-start PiP when app goes to background
|
|
655
|
+
pipController?.canStartPictureInPictureAutomaticallyFromInline = true
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
func togglePiP() {
|
|
659
|
+
guard let pipController else { return }
|
|
660
|
+
if pipController.isPictureInPictureActive {
|
|
661
|
+
pipController.stopPictureInPicture()
|
|
662
|
+
} else {
|
|
663
|
+
pipController.startPictureInPicture()
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// MARK: - AVPictureInPictureControllerDelegate
|
|
668
|
+
|
|
669
|
+
nonisolated func pictureInPictureControllerWillStartPictureInPicture(
|
|
670
|
+
_ controller: AVPictureInPictureController
|
|
671
|
+
) {
|
|
672
|
+
Task { @MainActor in isPiPActive = true }
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
nonisolated func pictureInPictureControllerDidStopPictureInPicture(
|
|
676
|
+
_ controller: AVPictureInPictureController
|
|
677
|
+
) {
|
|
678
|
+
Task { @MainActor in isPiPActive = false }
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
nonisolated func pictureInPictureController(
|
|
682
|
+
_ controller: AVPictureInPictureController,
|
|
683
|
+
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
|
|
684
|
+
completionHandler: @escaping (Bool) -> Void
|
|
685
|
+
) {
|
|
686
|
+
// Restore your player UI here, then call the handler
|
|
687
|
+
Task { @MainActor in
|
|
688
|
+
// Navigate back to the player view
|
|
689
|
+
completionHandler(true)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### PiP Requirements Checklist
|
|
696
|
+
|
|
697
|
+
- [ ] `UIBackgroundModes` includes `audio` in Info.plist
|
|
698
|
+
- [ ] `AVAudioSession` category set to `.playback`
|
|
699
|
+
- [ ] Check `AVPictureInPictureController.isPictureInPictureSupported()` before setup
|
|
700
|
+
- [ ] Implement `restoreUserInterfaceForPictureInPictureStop` delegate method
|
|
701
|
+
- [ ] Call completion handler in the restore delegate method (failure to call causes hangs)
|