@swiftbrowser/swift 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/Package.swift +200 -9
  2. package/README.md +7 -9
  3. package/Sources/AVFoundation/AVFoundation.swift +131 -0
  4. package/Sources/AVFoundation/Asset.swift +487 -0
  5. package/Sources/AVFoundation/AudioSession.swift +144 -0
  6. package/Sources/AVFoundation/CMTime.swift +303 -0
  7. package/Sources/AVFoundation/Capture.swift +688 -0
  8. package/Sources/AVFoundation/Player.swift +749 -0
  9. package/Sources/AVFoundation/PlayerView.swift +117 -0
  10. package/Sources/AVKit/AVKit.swift +132 -0
  11. package/Sources/Accelerate/Accelerate.swift +122 -0
  12. package/Sources/AppIntents/AppIntents.swift +144 -0
  13. package/Sources/AppIntents/Parameters.swift +220 -0
  14. package/Sources/AppIntents/Shortcuts.swift +159 -0
  15. package/Sources/AudioToolbox/AudioToolbox.swift +55 -0
  16. package/Sources/AuthenticationServices/AuthenticationServices.swift +257 -0
  17. package/Sources/AuthenticationServices/Authorization.swift +251 -0
  18. package/Sources/Charts/Charts.swift +0 -4
  19. package/Sources/Combine/AnyCancellable.swift +65 -0
  20. package/Sources/Combine/AnyPublisher.swift +148 -0
  21. package/Sources/Combine/AnySubscriber.swift +230 -0
  22. package/Sources/Combine/Cancellable.swift +35 -0
  23. package/Sources/Combine/Codable.swift +29 -0
  24. package/Sources/Combine/CombineIdentifier.swift +45 -0
  25. package/Sources/Combine/Concurrency/ConcurrencyHelpers.swift +24 -0
  26. package/Sources/Combine/Concurrency/Future+Concurrency.swift +136 -0
  27. package/Sources/Combine/Concurrency/GENERATED-Publisher+Concurrency.swift +397 -0
  28. package/Sources/Combine/CurrentValueSubject.swift +252 -0
  29. package/Sources/Combine/CustomCombineIdentifierConvertible.swift +28 -0
  30. package/Sources/Combine/Future.swift +228 -0
  31. package/Sources/Combine/GENERATED-RootProtocols.swift +426 -0
  32. package/Sources/Combine/Helpers/ConduitBase.swift +40 -0
  33. package/Sources/Combine/Helpers/ConduitList.swift +59 -0
  34. package/Sources/Combine/Helpers/DebugHook.swift +177 -0
  35. package/Sources/Combine/Helpers/FilterProducer.swift +174 -0
  36. package/Sources/Combine/Helpers/Locking.swift +30 -0
  37. package/Sources/Combine/Helpers/PartialCompletion.swift +26 -0
  38. package/Sources/Combine/Helpers/ReduceProducer.swift +249 -0
  39. package/Sources/Combine/Helpers/SubjectSubscriber.swift +96 -0
  40. package/Sources/Combine/Helpers/SubscriberTap.swift +69 -0
  41. package/Sources/Combine/Helpers/SubscriptionStatus.swift +33 -0
  42. package/Sources/Combine/Helpers/SubscriptionTap.swift +33 -0
  43. package/Sources/Combine/Helpers/Utils.swift +30 -0
  44. package/Sources/Combine/Helpers/Violations.swift +33 -0
  45. package/Sources/Combine/ImmediateScheduler.swift +178 -0
  46. package/Sources/Combine/LICENSE +21 -0
  47. package/Sources/Combine/NotificationPublisher.swift +48 -0
  48. package/Sources/Combine/ObjectWillChange.swift +46 -0
  49. package/Sources/Combine/PassthroughSubject.swift +222 -0
  50. package/Sources/Combine/Publisher+Subscribe.swift +53 -0
  51. package/Sources/Combine/Publishers/Deferred.swift +48 -0
  52. package/Sources/Combine/Publishers/Empty.swift +54 -0
  53. package/Sources/Combine/Publishers/Fail.swift +42 -0
  54. package/Sources/Combine/Publishers/GENERATED-Publishers.Catch.swift +673 -0
  55. package/Sources/Combine/Publishers/GENERATED-Publishers.Encode.swift +388 -0
  56. package/Sources/Combine/Publishers/GENERATED-Publishers.MapKeyPath.swift +384 -0
  57. package/Sources/Combine/Publishers/Just.swift +313 -0
  58. package/Sources/Combine/Publishers/Optional.Publisher.swift +315 -0
  59. package/Sources/Combine/Publishers/Publishers.AllSatisfy.swift +217 -0
  60. package/Sources/Combine/Publishers/Publishers.AssertNoFailure.swift +158 -0
  61. package/Sources/Combine/Publishers/Publishers.Autoconnect.swift +193 -0
  62. package/Sources/Combine/Publishers/Publishers.Breakpoint.swift +231 -0
  63. package/Sources/Combine/Publishers/Publishers.Buffer.swift +356 -0
  64. package/Sources/Combine/Publishers/Publishers.Collect.swift +102 -0
  65. package/Sources/Combine/Publishers/Publishers.CollectByCount.swift +195 -0
  66. package/Sources/Combine/Publishers/Publishers.CompactMap.swift +241 -0
  67. package/Sources/Combine/Publishers/Publishers.Comparison.swift +371 -0
  68. package/Sources/Combine/Publishers/Publishers.Concatenate.swift +474 -0
  69. package/Sources/Combine/Publishers/Publishers.Contains.swift +293 -0
  70. package/Sources/Combine/Publishers/Publishers.Count.swift +87 -0
  71. package/Sources/Combine/Publishers/Publishers.Debounce.swift +332 -0
  72. package/Sources/Combine/Publishers/Publishers.Delay.swift +269 -0
  73. package/Sources/Combine/Publishers/Publishers.Drop.swift +155 -0
  74. package/Sources/Combine/Publishers/Publishers.DropUntilOutput.swift +283 -0
  75. package/Sources/Combine/Publishers/Publishers.DropWhile.swift +378 -0
  76. package/Sources/Combine/Publishers/Publishers.Filter.swift +248 -0
  77. package/Sources/Combine/Publishers/Publishers.First.swift +261 -0
  78. package/Sources/Combine/Publishers/Publishers.FlatMap.swift +533 -0
  79. package/Sources/Combine/Publishers/Publishers.HandleEvents.swift +260 -0
  80. package/Sources/Combine/Publishers/Publishers.IgnoreOutput.swift +109 -0
  81. package/Sources/Combine/Publishers/Publishers.Last.swift +252 -0
  82. package/Sources/Combine/Publishers/Publishers.MakeConnectable.swift +82 -0
  83. package/Sources/Combine/Publishers/Publishers.Map.swift +351 -0
  84. package/Sources/Combine/Publishers/Publishers.MapError.swift +140 -0
  85. package/Sources/Combine/Publishers/Publishers.MeasureInterval.swift +187 -0
  86. package/Sources/Combine/Publishers/Publishers.Multicast.swift +284 -0
  87. package/Sources/Combine/Publishers/Publishers.Output.swift +241 -0
  88. package/Sources/Combine/Publishers/Publishers.PrefixUntilOutput.swift +197 -0
  89. package/Sources/Combine/Publishers/Publishers.PrefixWhile.swift +176 -0
  90. package/Sources/Combine/Publishers/Publishers.Print.swift +206 -0
  91. package/Sources/Combine/Publishers/Publishers.ReceiveOn.swift +227 -0
  92. package/Sources/Combine/Publishers/Publishers.Reduce.swift +204 -0
  93. package/Sources/Combine/Publishers/Publishers.RemoveDuplicates.swift +264 -0
  94. package/Sources/Combine/Publishers/Publishers.ReplaceEmpty.swift +201 -0
  95. package/Sources/Combine/Publishers/Publishers.ReplaceError.swift +211 -0
  96. package/Sources/Combine/Publishers/Publishers.Retry.swift +274 -0
  97. package/Sources/Combine/Publishers/Publishers.Scan.swift +331 -0
  98. package/Sources/Combine/Publishers/Publishers.Sequence.swift +437 -0
  99. package/Sources/Combine/Publishers/Publishers.SetFailureType.swift +120 -0
  100. package/Sources/Combine/Publishers/Publishers.Share.swift +104 -0
  101. package/Sources/Combine/Publishers/Publishers.SubscribeOn.swift +197 -0
  102. package/Sources/Combine/Publishers/Publishers.SwitchToLatest.swift +444 -0
  103. package/Sources/Combine/Publishers/Publishers.Throttle.swift +359 -0
  104. package/Sources/Combine/Publishers/Publishers.Timeout.swift +301 -0
  105. package/Sources/Combine/Publishers/Publishers.Zip.swift +720 -0
  106. package/Sources/Combine/Publishers/Publishers.swift +13 -0
  107. package/Sources/Combine/Publishers/Record.swift +237 -0
  108. package/Sources/Combine/Publishers/Result.Publisher.swift +337 -0
  109. package/Sources/Combine/Result.swift +46 -0
  110. package/Sources/Combine/Scheduler.swift +89 -0
  111. package/Sources/Combine/Schedulers.swift +211 -0
  112. package/Sources/Combine/Subject+Void.swift +17 -0
  113. package/Sources/Combine/Subscriber+Void.swift +20 -0
  114. package/Sources/Combine/Subscribers/Subscribers.Assign.swift +175 -0
  115. package/Sources/Combine/Subscribers/Subscribers.Completion.swift +90 -0
  116. package/Sources/Combine/Subscribers/Subscribers.Demand.swift +476 -0
  117. package/Sources/Combine/Subscribers/Subscribers.Sink.swift +206 -0
  118. package/Sources/Combine/Subscribers/Subscribers.swift +9 -0
  119. package/Sources/Combine/Subscription.swift +22 -0
  120. package/Sources/Combine/Subscriptions.swift +42 -0
  121. package/Sources/Combine/SwiftUIObservation.swift +10 -0
  122. package/Sources/Combine/_Introspection.swift +107 -0
  123. package/Sources/CommonCrypto/CommonCrypto.swift +80 -0
  124. package/Sources/Compression/Compression.swift +365 -0
  125. package/Sources/Compression/Deflate.swift +407 -0
  126. package/Sources/Compression/Zlib.swift +179 -0
  127. package/Sources/CoreFoundationTypes/CoreFoundationTypes.swift +101 -0
  128. package/Sources/CoreGraphics/CoreGraphics.swift +411 -0
  129. package/Sources/CoreGraphics/ProjectionTransforms.swift +28 -0
  130. package/Sources/CoreHaptics/CoreHaptics.swift +399 -0
  131. package/Sources/CoreImage/CIColor.swift +41 -0
  132. package/Sources/CoreImage/CIContext.swift +31 -0
  133. package/Sources/CoreImage/CIDetector.swift +57 -0
  134. package/Sources/CoreImage/CIFilter.swift +243 -0
  135. package/Sources/CoreImage/CIFilterBuiltins.swift +136 -0
  136. package/Sources/CoreImage/CIImage.swift +162 -0
  137. package/Sources/CoreImage/QRCode.swift +393 -0
  138. package/Sources/CoreLocation/Coordinate.swift +30 -0
  139. package/Sources/CoreLocation/Location.swift +65 -0
  140. package/Sources/CoreML/Evaluation.swift +414 -0
  141. package/Sources/CoreML/FeatureValue.swift +266 -0
  142. package/Sources/CoreML/Model.swift +269 -0
  143. package/Sources/CoreML/MultiArray.swift +92 -0
  144. package/Sources/CoreML/Specification.swift +233 -0
  145. package/Sources/CoreText/CoreText.swift +64 -0
  146. package/Sources/CoreTransferable/CoreTransferable.swift +606 -0
  147. package/Sources/CryptoKit/ChaChaPoly.swift +258 -0
  148. package/Sources/CryptoKit/CryptoKit.swift +375 -0
  149. package/Sources/CryptoKit/Curve25519.swift +317 -0
  150. package/Sources/CryptoKit/P256.swift +321 -0
  151. package/Sources/FoundationBridge/AppStorage.swift +165 -0
  152. package/Sources/FoundationBridge/AssetResources.swift +90 -0
  153. package/Sources/FoundationBridge/AsyncBytes.swift +128 -0
  154. package/Sources/FoundationBridge/AsyncImage.swift +116 -0
  155. package/Sources/FoundationBridge/AttributedStrings.swift +16 -0
  156. package/Sources/{_FoundationCommon → FoundationBridge}/Bundle.swift +41 -4
  157. package/Sources/FoundationBridge/CombineExports.swift +8 -0
  158. package/Sources/FoundationBridge/CoreFoundation.swift +77 -0
  159. package/Sources/FoundationBridge/Dispatch.swift +458 -0
  160. package/Sources/FoundationBridge/EnvironmentFoundation.swift +41 -0
  161. package/Sources/FoundationBridge/FileImporter.swift +128 -0
  162. package/Sources/FoundationBridge/FileSystem.swift +68 -0
  163. package/Sources/FoundationBridge/FoundationBridge.swift +24 -72
  164. package/Sources/FoundationBridge/HostResolution.swift +31 -0
  165. package/Sources/FoundationBridge/ItemProvider.swift +655 -0
  166. package/Sources/FoundationBridge/KeyValueObserving.swift +138 -0
  167. package/Sources/FoundationBridge/Links.swift +127 -0
  168. package/Sources/FoundationBridge/ListFormatter.swift +32 -0
  169. package/Sources/FoundationBridge/Localization.swift +149 -0
  170. package/Sources/FoundationBridge/Locks.swift +50 -0
  171. package/Sources/FoundationBridge/Markdown.swift +527 -0
  172. package/Sources/FoundationBridge/MeasurementFormat.swift +95 -0
  173. package/Sources/FoundationBridge/NotificationCenter.swift +154 -0
  174. package/Sources/FoundationBridge/OpenURLHandlers.swift +60 -0
  175. package/Sources/FoundationBridge/Operation.swift +195 -0
  176. package/Sources/FoundationBridge/ProcessInfo.swift +32 -0
  177. package/Sources/FoundationBridge/RelativeDateTimeFormatter.swift +136 -0
  178. package/Sources/FoundationBridge/SceneStorage.swift +78 -0
  179. package/Sources/FoundationBridge/SecurityExports.swift +8 -0
  180. package/Sources/FoundationBridge/Selector.swift +226 -0
  181. package/Sources/FoundationBridge/StringComparators.swift +17 -0
  182. package/Sources/FoundationBridge/Table.swift +211 -0
  183. package/Sources/FoundationBridge/TextAttributes.swift +42 -0
  184. package/Sources/FoundationBridge/Timer.swift +6 -1
  185. package/Sources/FoundationBridge/URLSession.swift +718 -0
  186. package/Sources/{_FoundationCommon → FoundationBridge}/UserDefaults.swift +17 -4
  187. package/Sources/FoundationBridge/WebAuthRedirects.swift +227 -0
  188. package/Sources/FoundationBridge/WebSocket.swift +187 -0
  189. package/Sources/FoundationModels/FoundationModels.swift +1098 -0
  190. package/Sources/ImageIO/GIF.swift +72 -0
  191. package/Sources/ImageIO/ImageIO.swift +248 -0
  192. package/Sources/ImageIO/ImageTypes.swift +28 -0
  193. package/Sources/LinkPresentation/LinkPresentation.swift +249 -0
  194. package/Sources/LocalAuthentication/LAContext.swift +86 -0
  195. package/Sources/MapKit/Geometry.swift +223 -0
  196. package/Sources/MapKit/Map.swift +638 -0
  197. package/Sources/MapKit/MapView.swift +229 -0
  198. package/Sources/MobileCoreServices/MobileCoreServices.swift +28 -0
  199. package/Sources/NaturalLanguage/LanguageIdentifier.swift +317 -0
  200. package/Sources/NaturalLanguage/NaturalLanguage.swift +135 -0
  201. package/Sources/OSLog/OSLog.swift +6 -0
  202. package/Sources/Photos/Photos.swift +572 -0
  203. package/Sources/PhotosUI/PhotosPicker.swift +248 -0
  204. package/Sources/PhotosUI/PhotosPickerItem.swift +261 -0
  205. package/Sources/QuickLook/QuickLook.swift +168 -0
  206. package/Sources/SafariServices/SafariServices.swift +106 -0
  207. package/Sources/Security/Security.swift +332 -0
  208. package/Sources/StoreKit/AppStore.swift +207 -0
  209. package/Sources/StoreKit/Products.swift +280 -0
  210. package/Sources/StoreKit/RequestReview.swift +28 -0
  211. package/Sources/StoreKit/StoreKit1.swift +486 -0
  212. package/Sources/StoreKit/StoreViews.swift +428 -0
  213. package/Sources/StoreKit/Transactions.swift +197 -0
  214. package/Sources/SwiftData/Macros.swift +58 -0
  215. package/Sources/SwiftData/ModelActor.swift +38 -0
  216. package/Sources/SwiftData/ModelContainer.swift +129 -0
  217. package/Sources/SwiftData/ModelContext.swift +451 -0
  218. package/Sources/SwiftData/PersistentModel.swift +238 -0
  219. package/Sources/SwiftData/Properties.swift +261 -0
  220. package/Sources/SwiftData/Query.swift +256 -0
  221. package/Sources/SwiftData/Schema.swift +192 -0
  222. package/Sources/SwiftData/Store.swift +146 -0
  223. package/Sources/SwiftData/StringPredicates.swift +125 -0
  224. package/Sources/SwiftDataMacros/EntryMacro.swift +82 -0
  225. package/Sources/SwiftDataMacros/GenerableMacro.swift +183 -0
  226. package/Sources/SwiftDataMacros/ModelActorMacro.swift +46 -0
  227. package/Sources/SwiftDataMacros/ModelMacro.swift +260 -0
  228. package/Sources/SwiftDataMacros/Plugin.swift +18 -0
  229. package/Sources/SwiftDataMacros/PredicateMacro.swift +1244 -0
  230. package/Sources/SwiftUI/API/Accessibility.swift +55 -6
  231. package/Sources/SwiftUI/API/AccessibilityExtras.swift +33 -0
  232. package/Sources/SwiftUI/API/AccessibilityFocus.swift +63 -0
  233. package/Sources/SwiftUI/API/Alert.swift +1 -1
  234. package/Sources/SwiftUI/API/AlignmentGuides.swift +68 -0
  235. package/Sources/SwiftUI/API/Animation.swift +37 -5
  236. package/Sources/SwiftUI/API/App.swift +31 -1
  237. package/Sources/SwiftUI/API/BindingCollections.swift +35 -0
  238. package/Sources/SwiftUI/API/BindingForEach.swift +27 -0
  239. package/Sources/SwiftUI/API/Bitmaps.swift +254 -0
  240. package/Sources/SwiftUI/API/ButtonStyles.swift +96 -0
  241. package/Sources/SwiftUI/API/Canvas.swift +337 -0
  242. package/Sources/SwiftUI/API/Color.swift +104 -28
  243. package/Sources/SwiftUI/API/ColorEffects.swift +52 -0
  244. package/Sources/SwiftUI/API/ColorPickerAndLabeledContent.swift +138 -0
  245. package/Sources/SwiftUI/API/ColorSpaces.swift +161 -0
  246. package/Sources/SwiftUI/API/Commands.swift +106 -0
  247. package/Sources/SwiftUI/API/ContainerBackground.swift +28 -0
  248. package/Sources/SwiftUI/API/ContentShape.swift +33 -0
  249. package/Sources/SwiftUI/API/ContentSizeCategory.swift +35 -0
  250. package/Sources/SwiftUI/API/ContentUnavailable.swift +101 -0
  251. package/Sources/SwiftUI/API/Controls.swift +59 -27
  252. package/Sources/SwiftUI/API/CustomLayout.swift +49 -10
  253. package/Sources/SwiftUI/API/CustomToolbarContent.swift +46 -0
  254. package/Sources/SwiftUI/API/Decorations.swift +1 -1
  255. package/Sources/SwiftUI/API/EditMode.swift +25 -82
  256. package/Sources/SwiftUI/API/Effects.swift +125 -10
  257. package/Sources/SwiftUI/API/Entry.swift +8 -0
  258. package/Sources/SwiftUI/API/Environment.swift +100 -9
  259. package/Sources/SwiftUI/API/EnvironmentSendable.swift +6 -0
  260. package/Sources/SwiftUI/API/Focus.swift +11 -3
  261. package/Sources/SwiftUI/API/Gauge.swift +26 -10
  262. package/Sources/SwiftUI/API/GeometryEffects.swift +232 -0
  263. package/Sources/SwiftUI/API/GeometryReader.swift +13 -2
  264. package/Sources/SwiftUI/API/GestureState.swift +65 -0
  265. package/Sources/SwiftUI/API/Gestures.swift +1 -1
  266. package/Sources/SwiftUI/API/Glass.swift +127 -0
  267. package/Sources/SwiftUI/API/Grid.swift +1 -1
  268. package/Sources/SwiftUI/API/GroupBoxAndControlGroup.swift +130 -0
  269. package/Sources/SwiftUI/API/KeyPress.swift +54 -0
  270. package/Sources/SwiftUI/API/LabelStyle.swift +13 -6
  271. package/Sources/SwiftUI/API/Layout.swift +71 -12
  272. package/Sources/SwiftUI/API/LazyStacks.swift +49 -0
  273. package/Sources/SwiftUI/API/LegacyAlert.swift +97 -0
  274. package/Sources/SwiftUI/API/Lists.swift +61 -6
  275. package/Sources/SwiftUI/API/Menu.swift +8 -1
  276. package/Sources/SwiftUI/API/Modifiers.swift +88 -12
  277. package/Sources/SwiftUI/API/MoreModifiers.swift +100 -0
  278. package/Sources/SwiftUI/API/Namespace.swift +44 -0
  279. package/Sources/SwiftUI/API/Navigation.swift +24 -9
  280. package/Sources/SwiftUI/API/NavigationDestinationBindings.swift +55 -0
  281. package/Sources/SwiftUI/API/ObservableObject.swift +16 -2
  282. package/Sources/SwiftUI/API/Path.swift +543 -0
  283. package/Sources/SwiftUI/API/Phase8Controls.swift +48 -31
  284. package/Sources/SwiftUI/API/PhaseAnimator.swift +130 -0
  285. package/Sources/SwiftUI/API/Picker.swift +14 -4
  286. package/Sources/SwiftUI/API/PointerAndKeyboard.swift +77 -0
  287. package/Sources/SwiftUI/API/Popover.swift +41 -0
  288. package/Sources/SwiftUI/API/Preferences.swift +262 -0
  289. package/Sources/SwiftUI/API/Presentation.swift +17 -4
  290. package/Sources/SwiftUI/API/PresentationExtras.swift +60 -0
  291. package/Sources/SwiftUI/API/Primitives.swift +168 -39
  292. package/Sources/SwiftUI/API/Publishers.swift +4 -4
  293. package/Sources/SwiftUI/API/Redaction.swift +75 -0
  294. package/Sources/SwiftUI/API/RowActions.swift +3 -2
  295. package/Sources/SwiftUI/API/Scenes.swift +131 -0
  296. package/Sources/SwiftUI/API/ScrollModifiers.swift +132 -0
  297. package/Sources/SwiftUI/API/ScrollTargets.swift +3 -3
  298. package/Sources/SwiftUI/API/ScrollViewReader.swift +144 -0
  299. package/Sources/SwiftUI/API/SectionSupplements.swift +86 -0
  300. package/Sources/SwiftUI/API/Shape.swift +214 -0
  301. package/Sources/SwiftUI/API/ShapeStyle.swift +187 -8
  302. package/Sources/SwiftUI/API/ShapeTrim.swift +78 -0
  303. package/Sources/SwiftUI/API/Sharing.swift +193 -0
  304. package/Sources/SwiftUI/API/StackLayouts.swift +199 -0
  305. package/Sources/SwiftUI/API/State.swift +18 -1
  306. package/Sources/SwiftUI/API/StatusGaps.swift +121 -0
  307. package/Sources/SwiftUI/API/SymbolEffects.swift +203 -0
  308. package/Sources/SwiftUI/API/TabContent.swift +841 -0
  309. package/Sources/SwiftUI/API/Table.swift +382 -0
  310. package/Sources/SwiftUI/API/Tabs.swift +148 -4
  311. package/Sources/SwiftUI/API/Tasks.swift +2 -2
  312. package/Sources/SwiftUI/API/Text.swift +386 -0
  313. package/Sources/SwiftUI/API/TextDates.swift +61 -0
  314. package/Sources/SwiftUI/API/TextDecorations.swift +52 -0
  315. package/Sources/SwiftUI/API/TextFieldAxis.swift +103 -0
  316. package/Sources/SwiftUI/API/TextFieldCommit.swift +52 -0
  317. package/Sources/SwiftUI/API/Toolbar.swift +52 -16
  318. package/Sources/SwiftUI/API/Transfer.swift +145 -0
  319. package/Sources/SwiftUI/API/UnevenRoundedRectangle.swift +66 -0
  320. package/Sources/SwiftUI/API/Video.swift +64 -0
  321. package/Sources/SwiftUI/API/View.swift +46 -2
  322. package/Sources/SwiftUI/API/ViewModifier.swift +4 -1
  323. package/Sources/SwiftUI/API/VisualEffect.swift +74 -1
  324. package/Sources/SwiftUI/API/Windows.swift +25 -0
  325. package/Sources/SwiftUI/Charts/Axis.swift +7 -1
  326. package/Sources/SwiftUI/Charts/AxisFormatsAndRanges.swift +68 -0
  327. package/Sources/SwiftUI/Charts/Chart.swift +17 -3
  328. package/Sources/SwiftUI/Charts/ChartProxy.swift +57 -0
  329. package/Sources/SwiftUI/Localization/Localization.swift +524 -0
  330. package/Sources/SwiftUI/Localization/PluralRules.swift +127 -0
  331. package/Sources/SwiftUI/Runtime/Executor.swift +2 -0
  332. package/Sources/SwiftUI/Runtime/Exports.swift +37 -1
  333. package/Sources/SwiftUI/Runtime/ImageSource.swift +310 -0
  334. package/Sources/SwiftUI/Runtime/JSON.swift +76 -4
  335. package/Sources/SwiftUI/Runtime/LazyRows.swift +99 -0
  336. package/Sources/SwiftUI/Runtime/Measurements.swift +140 -0
  337. package/Sources/SwiftUI/Runtime/Ops.swift +58 -12
  338. package/Sources/SwiftUI/Runtime/Reconciler.swift +10 -1
  339. package/Sources/SwiftUI/Runtime/Replay.swift +382 -0
  340. package/Sources/SwiftUI/Runtime/Runtime.swift +977 -198
  341. package/Sources/SwiftUI/Runtime/Sameness.swift +152 -0
  342. package/Sources/SwiftUI/Runtime/ViewPath.swift +89 -0
  343. package/Sources/SwiftUI/Runtime/ViewTraits.swift +175 -0
  344. package/Sources/SwiftUI/Runtime/WebAuthHeadless.swift +49 -0
  345. package/Sources/Symbols/Symbols.swift +25 -0
  346. package/Sources/Translation/Translation.swift +423 -0
  347. package/Sources/UIKit/Activity.swift +213 -0
  348. package/Sources/UIKit/Alerts.swift +69 -0
  349. package/Sources/UIKit/AppDelegate.swift +222 -0
  350. package/Sources/UIKit/Appearance.swift +84 -0
  351. package/Sources/UIKit/ColorConversions.swift +70 -0
  352. package/Sources/UIKit/Controls.swift +225 -0
  353. package/Sources/UIKit/Device.swift +200 -0
  354. package/Sources/UIKit/Feedback.swift +65 -0
  355. package/Sources/UIKit/Font.swift +307 -0
  356. package/Sources/UIKit/Graphics.swift +204 -0
  357. package/Sources/UIKit/HostingController.swift +185 -0
  358. package/Sources/UIKit/Identity.swift +24 -0
  359. package/Sources/UIKit/Introspectable.swift +510 -0
  360. package/Sources/UIKit/Pasteboard.swift +363 -0
  361. package/Sources/UIKit/PhotoAlbum.swift +81 -0
  362. package/Sources/UIKit/RemoteNotifications.swift +15 -0
  363. package/Sources/UIKit/Representable.swift +288 -0
  364. package/Sources/UIKit/SafeArea.swift +12 -0
  365. package/Sources/UIKit/SceneDelegates.swift +132 -0
  366. package/Sources/UIKit/Scenes.swift +40 -0
  367. package/Sources/UIKit/ScrollView.swift +157 -0
  368. package/Sources/UIKit/ShortcutItems.swift +67 -0
  369. package/Sources/UIKit/SymbolConfiguration.swift +124 -0
  370. package/Sources/UIKit/TargetAction.swift +123 -0
  371. package/Sources/UIKit/TextPaste.swift +161 -0
  372. package/Sources/UIKit/TextView.swift +538 -0
  373. package/Sources/UIKit/UIKit.swift +292 -0
  374. package/Sources/UIKit/UIView.swift +855 -0
  375. package/Sources/UIKit/ViewTranslation.swift +311 -0
  376. package/Sources/UniformTypeIdentifiers/UTType.swift +196 -0
  377. package/Sources/UserNotifications/Content.swift +370 -0
  378. package/Sources/UserNotifications/NotificationCenter.swift +309 -0
  379. package/Sources/WeatherKit/WeatherKit.swift +228 -0
  380. package/Sources/WebKit/WebKit.swift +245 -0
  381. package/Sources/os/os.swift +376 -0
  382. package/Sources/zlib/zlib.swift +6 -0
  383. package/index.d.ts +1 -1
  384. package/index.mjs +2 -2
  385. package/manifest.d.mts +2 -0
  386. package/manifest.mjs +41 -0
  387. package/package.json +5 -3
  388. package/sync-sources.mjs +21 -7
  389. package/Sources/FoundationLite/Common.swift +0 -3
  390. package/Sources/FoundationLite/FoundationLite.swift +0 -171
  391. package/Sources/FoundationLite/Interaction.swift +0 -146
  392. package/Sources/FoundationLite/Timer.swift +0 -48
  393. package/Sources/_FoundationCommon/_FoundationCommon.swift +0 -10
@@ -0,0 +1,749 @@
1
+ // `AVPlayer`, `AVPlayerItem`, `AVQueuePlayer` and `AVPlayerLooper` over the
2
+ // page's `video` element (docs/ops-protocol.md, "Video and audio").
3
+ //
4
+ // A player holds what it was told (item, rate, mute, volume, the last seek)
5
+ // in an observed state object: the views that show the player read it in
6
+ // their bodies, so a change draws them again with new props. What the page
7
+ // reports comes back through `_event(_:from:)`: an item's metadata (`ready`),
8
+ // the position (`time`, extrapolated between reports while playing), whether
9
+ // it is really playing (`status`), the end, errors, and what the viewer did
10
+ // with the native controls (`play`, `pause`, `volume`, `rate`).
11
+ //
12
+ // Of the views showing a player, the newest plays it; the others are paused
13
+ // and muted, and the one that takes over seeks to where the player is. A
14
+ // player shown nowhere plays nothing and keeps its position.
15
+
16
+ import Foundation
17
+ import FoundationBridge
18
+ import Observation
19
+ import SwiftUI
20
+ import struct SwiftUI.CGSize
21
+ import struct SwiftUI.CGRect
22
+
23
+ /// What the views showing a player read; a change renders them again.
24
+ @Observable
25
+ final class _AVPlayerState {
26
+ var item: AVPlayerItem?
27
+ var rate: Float = 0
28
+ var isMuted = false
29
+ var volume: Float = 1
30
+ /// The last seek, in seconds; the page performs each serial once.
31
+ var seekTime: Double = 0
32
+ var seekSerial = 0
33
+ /// The views showing the player (`_AVPlayerVideo` surfaces), oldest first; the last plays.
34
+ var surfaces: [ObjectIdentifier] = []
35
+ /// An `AVPlayerLooper` loops the item (the element's `loop`).
36
+ var loops = false
37
+ /// The shortest periodic observer interval below the page's own pace, in seconds.
38
+ var reportInterval: Double?
39
+ var timeControlStatus: AVPlayer.TimeControlStatus = .paused
40
+ }
41
+
42
+ /// What the views showing an item's player read about the item.
43
+ @Observable
44
+ final class _AVItemState {
45
+ var status: AVPlayerItem.Status = .unknown
46
+ /// The URL the page loads (a file's blob URL once the page made it).
47
+ var pageURL: String?
48
+ /// Seconds; nil until known, and for a live stream.
49
+ var duration: Double?
50
+ var presentationSize: CGSize = .zero
51
+ @ObservationIgnored var error: (any Error)?
52
+ }
53
+
54
+ // MARK: - AVPlayerItem
55
+
56
+ /// The state of one playback of an asset: its status, duration, size and position.
57
+ open class AVPlayerItem: @unchecked Sendable {
58
+ public enum Status: Int, Sendable {
59
+ case unknown, readyToPlay, failed
60
+ }
61
+
62
+ public let asset: AVAsset
63
+ let _state = _AVItemState()
64
+ /// The item's position in seconds, as the page last reported it (or a seek set it).
65
+ var _time: Double = 0
66
+ /// The player whose current item this is.
67
+ weak var _player: AVPlayer?
68
+
69
+ open var forwardPlaybackEndTime: CMTime = .invalid
70
+ open var reversePlaybackEndTime: CMTime = .invalid
71
+ open var preferredForwardBufferDuration: TimeInterval = 0
72
+ open var preferredPeakBitRate: Double = 0
73
+ open var preferredMaximumResolution: CGSize = .zero
74
+ open var canUseNetworkResourcesForLiveStreamingWhilePaused = false
75
+ open var appliesPerFrameHDRDisplayMetadata = true
76
+ open var audioTimePitchAlgorithm: String?
77
+
78
+ public convenience init(url URL: URL) {
79
+ self.init(asset: AVURLAsset(url: URL))
80
+ }
81
+
82
+ public init(asset: AVAsset, automaticallyLoadedAssetKeys: [AVPartialAsyncProperty<AVAsset>] = []) {
83
+ self.asset = asset
84
+ resolvePageURL()
85
+ }
86
+
87
+ /// Remote, data and blob URLs load as they are; a file is handed to the page first.
88
+ private func resolvePageURL() {
89
+ guard let url = asset._url else {
90
+ _fail(AVError(.operationNotSupportedForAsset, message: "the asset has no URL"))
91
+ return
92
+ }
93
+ guard url.isFileURL else {
94
+ _state.pageURL = url.absoluteString
95
+ return
96
+ }
97
+ Task { @MainActor [weak self] in
98
+ do {
99
+ let pageURL = try await _AVMediaHost.pageURL(for: url)
100
+ self?._state.pageURL = pageURL
101
+ } catch {
102
+ self?._fail(error)
103
+ }
104
+ }
105
+ }
106
+
107
+ open var status: Status { _state.status }
108
+ open var error: (any Error)? { _state.error }
109
+ /// `.indefinite` until the page has read the metadata (and for a live stream).
110
+ open var duration: CMTime { CMTime(_seconds: _state.duration) }
111
+ /// The video's size in pixels, `.zero` until known (and for audio).
112
+ open var presentationSize: CGSize { _state.presentationSize }
113
+ open var isPlaybackLikelyToKeepUp: Bool { _state.status == .readyToPlay }
114
+ open var isPlaybackBufferEmpty: Bool { _state.status != .readyToPlay }
115
+ open var isPlaybackBufferFull: Bool { false }
116
+ open var canPlayFastForward: Bool { true }
117
+ open var canPlaySlowForward: Bool { true }
118
+ open var canPlayReverse: Bool { false }
119
+ open var seekableTimeRanges: [NSValue] { [] }
120
+ open var loadedTimeRanges: [NSValue] { [] }
121
+
122
+ open func currentTime() -> CMTime {
123
+ CMTime(seconds: _player?.currentItem === self ? _player!._currentSeconds() : _time, preferredTimescale: 600)
124
+ }
125
+
126
+ /// Seeks this item (its player's, when it is current).
127
+ open func seek(to time: CMTime, completionHandler: (@Sendable (Bool) -> Void)? = nil) {
128
+ if let player = _player, player.currentItem === self {
129
+ player._seek(to: time, completion: completionHandler)
130
+ } else {
131
+ _time = _clamp(time.seconds)
132
+ completionHandler?(true)
133
+ }
134
+ }
135
+
136
+ open func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: (@Sendable (Bool) -> Void)? = nil) {
137
+ seek(to: time, completionHandler: completionHandler)
138
+ }
139
+
140
+ open func cancelPendingSeeks() {}
141
+
142
+ open func step(byCount stepCount: Int) {}
143
+
144
+ /// A position within the item: not negative, and not past the end once that is known.
145
+ func _clamp(_ seconds: Double) -> Double {
146
+ guard seconds.isFinite else { return seconds > 0 ? (_state.duration ?? 0) : 0 }
147
+ var value = max(0, seconds)
148
+ if let duration = _state.duration { value = min(value, duration) }
149
+ return value
150
+ }
151
+
152
+ /// The page read the metadata.
153
+ func _ready(_ info: _AVMediaInfo) {
154
+ asset._learn(info)
155
+ if _state.duration != info.duration { _state.duration = info.duration }
156
+ let size = CGSize(width: info.width, height: info.height)
157
+ if _state.presentationSize != size { _state.presentationSize = size }
158
+ if _state.status != .readyToPlay { _state.status = .readyToPlay }
159
+ }
160
+
161
+ func _fail(_ error: any Error) {
162
+ _state.error = error
163
+ _state.status = .failed
164
+ }
165
+
166
+ // Notifications (AVFoundation's names and raw values).
167
+ public static let didPlayToEndTimeNotification = Notification.Name("AVPlayerItemDidPlayToEndTimeNotification")
168
+ public static let failedToPlayToEndTimeNotification = Notification.Name("AVPlayerItemFailedToPlayToEndTimeNotification")
169
+ public static let playbackStalledNotification = Notification.Name("AVPlayerItemPlaybackStalledNotification")
170
+ public static let timeJumpedNotification = Notification.Name("AVPlayerItemTimeJumpedNotification")
171
+ public static let newAccessLogEntryNotification = Notification.Name("AVPlayerItemNewAccessLogEntry")
172
+ public static let newErrorLogEntryNotification = Notification.Name("AVPlayerItemNewErrorLogEntry")
173
+ }
174
+
175
+ extension Notification.Name {
176
+ public static let AVPlayerItemDidPlayToEndTime = AVPlayerItem.didPlayToEndTimeNotification
177
+ public static let AVPlayerItemFailedToPlayToEndTime = AVPlayerItem.failedToPlayToEndTimeNotification
178
+ public static let AVPlayerItemPlaybackStalled = AVPlayerItem.playbackStalledNotification
179
+ public static let AVPlayerItemTimeJumped = AVPlayerItem.timeJumpedNotification
180
+ public static let AVPlayerItemNewAccessLogEntry = AVPlayerItem.newAccessLogEntryNotification
181
+ public static let AVPlayerItemNewErrorLogEntry = AVPlayerItem.newErrorLogEntryNotification
182
+ }
183
+
184
+ /// The `userInfo` key of `failedToPlayToEndTimeNotification`: the error.
185
+ public let AVPlayerItemFailedToPlayToEndTimeErrorKey = "AVPlayerItemFailedToPlayToEndTimeErrorKey"
186
+
187
+ // MARK: - AVPlayer
188
+
189
+ /// Whether a player in a hidden page pauses; kept (the browser decides).
190
+ public enum AVPlayerAudiovisualBackgroundPlaybackPolicy: Int, Sendable {
191
+ case automatic = 1, pauses, continuesIfPossible
192
+ }
193
+
194
+ /// Plays one item at a time in the views that show it (see the top of the file).
195
+ open class AVPlayer: @unchecked Sendable {
196
+ public enum Status: Int, Sendable {
197
+ case unknown, readyToPlay, failed
198
+ }
199
+
200
+ public enum TimeControlStatus: Int, Sendable {
201
+ case paused, waitingToPlayAtSpecifiedRate, playing
202
+ }
203
+
204
+ public enum ActionAtItemEnd: Int, Sendable {
205
+ case advance, pause, none
206
+ }
207
+
208
+ public struct WaitingReason: RawRepresentable, Hashable, Sendable {
209
+ public let rawValue: String
210
+ public init(rawValue: String) { self.rawValue = rawValue }
211
+ public static let toMinimizeStalls = WaitingReason(rawValue: "AVPlayerWaitingToMinimizeStallsReason")
212
+ public static let evaluatingBufferingRate = WaitingReason(rawValue: "AVPlayerWaitingWhileEvaluatingBufferingRateReason")
213
+ public static let noItemToPlay = WaitingReason(rawValue: "AVPlayerWaitingWithNoItemToPlayReason")
214
+ public static let interstitialEvent = WaitingReason(rawValue: "AVPlayerWaitingDuringInterstitialEventReason")
215
+ }
216
+
217
+ public static let rateDidChangeNotification = Notification.Name("AVPlayerRateDidChangeNotification")
218
+ public static let rateDidChangeReasonKey = "AVPlayerRateDidChangeReasonKey"
219
+
220
+ let _state = _AVPlayerState()
221
+ /// When the position was last reported (or frozen), on `_clock`; nil unless playing.
222
+ private var stamp: Double?
223
+ private var periodicObservers: [_PeriodicTimeObserver] = []
224
+ private var pendingSeeks: [Int: @Sendable (Bool) -> Void] = [:]
225
+ weak var _looper: AVPlayerLooper?
226
+
227
+ /// Seconds on a monotonic clock, for extrapolating the position; tests replace it.
228
+ nonisolated(unsafe) static var _clock: () -> Double = {
229
+ let (seconds, attoseconds) = (ContinuousClock.now - _clockOrigin).components
230
+ return Double(seconds) + Double(attoseconds) / 1e18
231
+ }
232
+ private static let _clockOrigin = ContinuousClock.now
233
+
234
+ open var defaultRate: Float = 1
235
+ open var actionAtItemEnd: ActionAtItemEnd = .pause
236
+ open var automaticallyWaitsToMinimizeStalling = true
237
+ open var audiovisualBackgroundPlaybackPolicy: AVPlayerAudiovisualBackgroundPlaybackPolicy = .automatic
238
+ /// Kept; the page does not hold a wake lock.
239
+ open var preventsDisplaySleepDuringVideoPlayback = true
240
+ open var preventsAutomaticBackgroundingDuringVideoPlayback = true
241
+ open var allowsExternalPlayback = true
242
+ open var isExternalPlaybackActive: Bool { false }
243
+ open var usesExternalPlaybackWhileExternalScreenIsActive = false
244
+ open var appliesMediaSelectionCriteriaAutomatically = true
245
+ open var isOutputObscuredDueToInsufficientExternalProtection: Bool { false }
246
+ public static var eligibleForHDRPlayback: Bool { false }
247
+
248
+ public init() {}
249
+
250
+ public convenience init(url URL: URL) {
251
+ self.init(playerItem: AVPlayerItem(url: URL))
252
+ }
253
+
254
+ public init(playerItem item: AVPlayerItem?) {
255
+ _setItem(item)
256
+ }
257
+
258
+ // MARK: Item
259
+
260
+ open var currentItem: AVPlayerItem? { _state.item }
261
+
262
+ open func replaceCurrentItem(with item: AVPlayerItem?) {
263
+ _setItem(item)
264
+ }
265
+
266
+ func _setItem(_ item: AVPlayerItem?) {
267
+ guard item !== _state.item else { return }
268
+ freezeTime()
269
+ _state.item?._player = nil
270
+ item?._player = self
271
+ _state.item = item
272
+ // The new item plays from its own position.
273
+ resync()
274
+ if _state.rate != 0 { _state.timeControlStatus = .waitingToPlayAtSpecifiedRate }
275
+ }
276
+
277
+ /// The item ended with `actionAtItemEnd == .advance`: a plain player has
278
+ /// no next item (`AVQueuePlayer` overrides this).
279
+ func _advance() {
280
+ _setItem(nil)
281
+ }
282
+
283
+ open var status: Status {
284
+ switch _state.item?._state.status {
285
+ case .readyToPlay?: .readyToPlay
286
+ case .failed?: .failed
287
+ default: .unknown
288
+ }
289
+ }
290
+
291
+ open var error: (any Error)? { _state.item?._state.error }
292
+
293
+ // MARK: Rate
294
+
295
+ open var rate: Float {
296
+ get { _state.rate }
297
+ set { _setRate(newValue) }
298
+ }
299
+
300
+ open func play() { _setRate(defaultRate) }
301
+ open func pause() { _setRate(0) }
302
+
303
+ open func playImmediately(atRate rate: Float) { _setRate(rate) }
304
+
305
+ open var timeControlStatus: TimeControlStatus { _state.timeControlStatus }
306
+
307
+ open var reasonForWaitingToPlay: WaitingReason? {
308
+ guard _state.timeControlStatus == .waitingToPlayAtSpecifiedRate else { return nil }
309
+ return _state.item == nil ? .noItemToPlay : .toMinimizeStalls
310
+ }
311
+
312
+ private func _setRate(_ value: Float) {
313
+ guard value != _state.rate else { return }
314
+ freezeTime()
315
+ let wasPlaying = _state.rate != 0
316
+ _state.rate = value
317
+ if value == 0 {
318
+ setControlStatus(.paused)
319
+ } else if !wasPlaying {
320
+ setControlStatus(.waitingToPlayAtSpecifiedRate)
321
+ }
322
+ FoundationBridge.NotificationCenter.default.post(name: AVPlayer.rateDidChangeNotification, object: self)
323
+ }
324
+
325
+ private func setControlStatus(_ status: TimeControlStatus) {
326
+ guard _state.timeControlStatus != status else { return }
327
+ let wasPlaying = _state.timeControlStatus == .playing
328
+ freezeTime()
329
+ _state.timeControlStatus = status
330
+ if status == .playing { stamp = Self._clock() }
331
+ if wasPlaying != (status == .playing) { fireObservers(force: true) }
332
+ }
333
+
334
+ // MARK: Volume
335
+
336
+ open var isMuted: Bool {
337
+ get { _state.isMuted }
338
+ set { if newValue != _state.isMuted { _state.isMuted = newValue } }
339
+ }
340
+
341
+ /// 0…1, applied on top of mute.
342
+ open var volume: Float {
343
+ get { _state.volume }
344
+ set { if newValue != _state.volume { _state.volume = newValue } }
345
+ }
346
+
347
+ // MARK: Time
348
+
349
+ open func currentTime() -> CMTime {
350
+ guard _state.item != nil else { return .zero }
351
+ return CMTime(seconds: _currentSeconds(), preferredTimescale: 600)
352
+ }
353
+
354
+ /// The current item's position, extrapolated from the last report while playing.
355
+ func _currentSeconds() -> Double {
356
+ guard let item = _state.item else { return 0 }
357
+ guard let stamp, _state.timeControlStatus == .playing else { return item._time }
358
+ return item._clamp(item._time + (Self._clock() - stamp) * Double(_state.rate))
359
+ }
360
+
361
+ /// Fixes the extrapolated position as the item's (before a change of rate or status).
362
+ private func freezeTime() {
363
+ if let item = _state.item, stamp != nil { item._time = _currentSeconds() }
364
+ stamp = _state.timeControlStatus == .playing ? Self._clock() : nil
365
+ }
366
+
367
+ open func seek(to time: CMTime) {
368
+ _seek(to: time, completion: nil)
369
+ }
370
+
371
+ open func seek(to time: CMTime, completionHandler: @escaping @Sendable (Bool) -> Void) {
372
+ _seek(to: time, completion: completionHandler)
373
+ }
374
+
375
+ /// The page seeks to the nearest point it can; the tolerances are not applied.
376
+ open func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime) {
377
+ _seek(to: time, completion: nil)
378
+ }
379
+
380
+ open func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: @escaping @Sendable (Bool) -> Void) {
381
+ _seek(to: time, completion: completionHandler)
382
+ }
383
+
384
+ @discardableResult
385
+ open func seek(to time: CMTime) async -> Bool {
386
+ await withCheckedContinuation { continuation in
387
+ _seek(to: time) { continuation.resume(returning: $0) }
388
+ }
389
+ }
390
+
391
+ @discardableResult
392
+ open func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime) async -> Bool {
393
+ await seek(to: time)
394
+ }
395
+
396
+ /// Moves the current item to `time`: the item's position changes at once,
397
+ /// and the page seeks the element that plays it. The completion runs with
398
+ /// true once the page has seeked (at once when no view shows the player),
399
+ /// with false when a later seek replaces it.
400
+ func _seek(to time: CMTime, completion: (@Sendable (Bool) -> Void)?) {
401
+ guard let item = _state.item, time.isValid else {
402
+ if let completion { Task { completion(false) } }
403
+ return
404
+ }
405
+ for (_, superseded) in pendingSeeks { superseded(false) }
406
+ pendingSeeks = [:]
407
+ item._time = item._clamp(time.seconds)
408
+ stamp = _state.timeControlStatus == .playing ? Self._clock() : nil
409
+ _state.seekTime = item._time
410
+ _state.seekSerial += 1
411
+ if let completion {
412
+ if _state.surfaces.isEmpty { Task { completion(true) } } else { pendingSeeks[_state.seekSerial] = completion }
413
+ }
414
+ FoundationBridge.NotificationCenter.default.post(name: AVPlayerItem.timeJumpedNotification, object: item)
415
+ fireObservers(force: true)
416
+ }
417
+
418
+ /// A seek to where the player is, for the view that takes over playing it.
419
+ private func resync() {
420
+ _state.seekTime = _state.item?._time ?? 0
421
+ _state.seekSerial += 1
422
+ }
423
+
424
+ // MARK: Time observers
425
+
426
+ /// Calls `block` every `interval` of playback (as the page reports the
427
+ /// position: about four times a second, or at `interval` when it is
428
+ /// shorter), and when playback starts, stops or seeks. The queue is
429
+ /// ignored: blocks run on the page's one thread.
430
+ open func addPeriodicTimeObserver(forInterval interval: CMTime, queue: FoundationBridge.DispatchQueue?,
431
+ using block: @escaping @Sendable (CMTime) -> Void) -> Any {
432
+ let observer = _PeriodicTimeObserver(interval: interval, block: block)
433
+ periodicObservers.append(observer)
434
+ updateReportInterval()
435
+ return observer
436
+ }
437
+
438
+ open func removeTimeObserver(_ observer: Any) {
439
+ guard let observer = observer as? _PeriodicTimeObserver else { return }
440
+ periodicObservers.removeAll { $0 === observer }
441
+ updateReportInterval()
442
+ }
443
+
444
+ private func updateReportInterval() {
445
+ let shortest = periodicObservers.map(\.seconds).filter { $0 > 0 && $0 < 0.25 }.min()
446
+ if _state.reportInterval != shortest { _state.reportInterval = shortest }
447
+ }
448
+
449
+ private func fireObservers(force: Bool) {
450
+ guard !periodicObservers.isEmpty, _state.item != nil else { return }
451
+ let now = _currentSeconds()
452
+ for observer in periodicObservers {
453
+ if !force, let last = observer.last, abs(now - last) < observer.seconds * 0.999 { continue }
454
+ observer.last = now
455
+ observer.block(CMTime(seconds: now, preferredTimescale: observer.timescale))
456
+ }
457
+ }
458
+
459
+ // MARK: Views
460
+
461
+ /// A view started showing the player: it plays it from here.
462
+ func _attach(_ surface: ObjectIdentifier) {
463
+ let previous = _state.surfaces.last
464
+ _state.surfaces.removeAll { $0 == surface }
465
+ _state.surfaces.append(surface)
466
+ if previous != surface {
467
+ freezeTime()
468
+ resync()
469
+ }
470
+ }
471
+
472
+ /// A view stopped showing the player.
473
+ func _detach(_ surface: ObjectIdentifier) {
474
+ guard let index = _state.surfaces.firstIndex(of: surface) else { return }
475
+ let wasActive = index == _state.surfaces.count - 1
476
+ _state.surfaces.remove(at: index)
477
+ guard wasActive else { return }
478
+ freezeTime()
479
+ for (_, completion) in pendingSeeks { completion(true) }
480
+ pendingSeeks = [:]
481
+ if _state.surfaces.isEmpty {
482
+ setControlStatus(_state.rate == 0 ? .paused : .waitingToPlayAtSpecifiedRate)
483
+ } else {
484
+ resync()
485
+ }
486
+ }
487
+
488
+ /// The `video` element's props for one of the views showing the player
489
+ /// (docs/ops-protocol.md, "Video and audio"). Read in the view's body, so
490
+ /// the view draws again when any of it changes (the position does not).
491
+ func _videoProps(surface: ObjectIdentifier, gravity: AVLayerVideoGravity, controls: Bool) -> _JSON {
492
+ let item = _state.item
493
+ let active = _state.surfaces.last == surface
494
+ let rate = _state.rate
495
+ var props: [String: _JSON] = [
496
+ "url": item?._state.pageURL.map(_JSON.string) ?? .null,
497
+ "playing": .bool(active && rate > 0 && item != nil && item?._state.status != .failed),
498
+ "muted": .bool(_state.isMuted || !active),
499
+ "volume": .number(Double(min(max(_state.volume, 0), 1))),
500
+ "rate": .number(Double(rate > 0 ? rate : max(defaultRate, 0.0625))),
501
+ "loop": .bool(_state.loops),
502
+ "gravity": .string(gravity._pageName),
503
+ "controls": .bool(controls),
504
+ "seek": .object(["time": .number(_state.seekTime), "serial": .number(Double(_state.seekSerial))]),
505
+ ]
506
+ if let interval = _state.reportInterval { props["report"] = .number(interval) }
507
+ if let size = item?._state.presentationSize, size.width > 0, size.height > 0 {
508
+ props["width"] = .number(Double(size.width))
509
+ props["height"] = .number(Double(size.height))
510
+ }
511
+ return .object(props)
512
+ }
513
+
514
+ /// A `video` event from the element of one of the views showing the player.
515
+ func _event(_ event: _JSON, from surface: ObjectIdentifier) {
516
+ guard case .object(let fields) = event, case .string(let kind)? = fields["event"] else { return }
517
+ func number(_ key: String) -> Double? {
518
+ if case .number(let value)? = fields[key], value.isFinite { return value }
519
+ return nil
520
+ }
521
+ let active = _state.surfaces.last == surface
522
+ guard let item = _state.item else { return }
523
+ // Events about another URL (the item changed since) are stale.
524
+ if case .string(let url)? = fields["url"], let pageURL = item._state.pageURL, url != pageURL { return }
525
+
526
+ switch kind {
527
+ case "ready":
528
+ let duration: Double? = if case .number(let value)? = fields["duration"], value.isFinite { value } else { nil }
529
+ item._ready(_AVMediaInfo(duration: duration, width: number("width") ?? 0, height: number("height") ?? 0))
530
+ case "error":
531
+ let error = AVError(Self.errorCode(number("code")), message: {
532
+ if case .string(let message)? = fields["message"], !message.isEmpty { return message }
533
+ return "the page could not play \(item._state.pageURL ?? "the item")"
534
+ }())
535
+ item._fail(error)
536
+ setControlStatus(.paused)
537
+ FoundationBridge.NotificationCenter.default.post(name: AVPlayerItem.failedToPlayToEndTimeNotification, object: item,
538
+ userInfo: [AVPlayerItemFailedToPlayToEndTimeErrorKey: error])
539
+ case _ where !active:
540
+ // A view that does not play the player only reports metadata.
541
+ return
542
+ case "time":
543
+ guard let time = number("time") else { return }
544
+ let previous = item._time
545
+ item._time = item._clamp(time)
546
+ if _state.timeControlStatus == .playing { stamp = Self._clock() }
547
+ if _state.loops, let duration = item._state.duration, previous - time > duration / 2 {
548
+ // The element looped (`loop`): one more pass of the template item.
549
+ _looper?._looped()
550
+ FoundationBridge.NotificationCenter.default.post(name: AVPlayerItem.didPlayToEndTimeNotification, object: item)
551
+ }
552
+ fireObservers(force: false)
553
+ case "status":
554
+ if let time = number("time") { item._time = item._clamp(time) }
555
+ switch fields["status"] {
556
+ case .string("playing")?: if _state.rate != 0 { setControlStatus(.playing) }
557
+ case .string("waiting")?: if _state.rate != 0 { setControlStatus(.waitingToPlayAtSpecifiedRate) }
558
+ default: if _state.rate == 0 { setControlStatus(.paused) }
559
+ }
560
+ case "seeked":
561
+ if let serial = number("serial").map(Int.init), let completion = pendingSeeks.removeValue(forKey: serial) {
562
+ completion(true)
563
+ }
564
+ case "play":
565
+ // The viewer pressed play on the native controls.
566
+ if _state.rate == 0 { _setRate(defaultRate) }
567
+ case "pause":
568
+ // The viewer paused, or the browser refused to start (`blocked`:
569
+ // unmuted playback without a tap first).
570
+ if let time = number("time") { item._time = item._clamp(time) }
571
+ if _state.rate != 0 { _setRate(0) }
572
+ case "volume":
573
+ if case .bool(let muted)? = fields["muted"] { isMuted = muted }
574
+ if let value = number("volume") { volume = Float(value) }
575
+ case "rate":
576
+ if let value = number("rate"), value > 0, _state.rate != 0 { _setRate(Float(value)) }
577
+ case "ended":
578
+ if let duration = item._state.duration { item._time = duration } else if let time = number("time") { item._time = time }
579
+ stamp = nil
580
+ FoundationBridge.NotificationCenter.default.post(name: AVPlayerItem.didPlayToEndTimeNotification, object: item)
581
+ // An observer may have moved on already (seeked, replaced the item).
582
+ guard _state.item === item, item._time >= (item._state.duration ?? item._time) else { return }
583
+ switch actionAtItemEnd {
584
+ case .pause: _setRate(0)
585
+ case .advance: _advance()
586
+ case .none: setControlStatus(.paused)
587
+ }
588
+ default:
589
+ break
590
+ }
591
+ }
592
+
593
+ /// AVFoundation's error for an HTML `MediaError` code.
594
+ static func errorCode(_ code: Double?) -> AVError.Code {
595
+ switch code {
596
+ case 2?: .contentIsUnavailable
597
+ case 3?: .decodeFailed
598
+ case 4?: .fileFormatNotRecognized
599
+ default: .unknown
600
+ }
601
+ }
602
+ }
603
+
604
+ /// What `addPeriodicTimeObserver` returns.
605
+ final class _PeriodicTimeObserver: @unchecked Sendable {
606
+ let seconds: Double
607
+ let timescale: CMTimeScale
608
+ let block: @Sendable (CMTime) -> Void
609
+ var last: Double?
610
+
611
+ init(interval: CMTime, block: @escaping @Sendable (CMTime) -> Void) {
612
+ seconds = interval.isNumeric ? max(interval.seconds, 0.001) : 1
613
+ // Times are reported at Core Media's video timescale at least.
614
+ timescale = max(interval.timescale, 600)
615
+ self.block = block
616
+ }
617
+ }
618
+
619
+ // MARK: - AVQueuePlayer
620
+
621
+ /// A player of a list of items, one after another (`actionAtItemEnd` is `.advance`).
622
+ open class AVQueuePlayer: AVPlayer, @unchecked Sendable {
623
+ private var queue: [AVPlayerItem] = []
624
+
625
+ public override init() {
626
+ super.init()
627
+ actionAtItemEnd = .advance
628
+ }
629
+
630
+ public override init(playerItem item: AVPlayerItem?) {
631
+ super.init(playerItem: item)
632
+ actionAtItemEnd = .advance
633
+ }
634
+
635
+ public init(items: [AVPlayerItem]) {
636
+ super.init(playerItem: items.first)
637
+ queue = Array(items.dropFirst())
638
+ actionAtItemEnd = .advance
639
+ }
640
+
641
+ open func items() -> [AVPlayerItem] {
642
+ (currentItem.map { [$0] } ?? []) + queue
643
+ }
644
+
645
+ open func advanceToNextItem() {
646
+ let next = queue.isEmpty ? nil : queue.removeFirst()
647
+ _setItem(next)
648
+ }
649
+
650
+ override func _advance() {
651
+ advanceToNextItem()
652
+ }
653
+
654
+ open func canInsert(_ item: AVPlayerItem, after afterItem: AVPlayerItem?) -> Bool {
655
+ !items().contains { $0 === item }
656
+ }
657
+
658
+ /// Inserts `item` after `afterItem`, or at the end when that is nil.
659
+ open func insert(_ item: AVPlayerItem, after afterItem: AVPlayerItem?) {
660
+ guard canInsert(item, after: afterItem) else { return }
661
+ if currentItem == nil {
662
+ _setItem(item)
663
+ } else if let afterItem, afterItem !== currentItem, let index = queue.firstIndex(where: { $0 === afterItem }) {
664
+ queue.insert(item, at: index + 1)
665
+ } else if let afterItem, afterItem === currentItem {
666
+ queue.insert(item, at: 0)
667
+ } else {
668
+ queue.append(item)
669
+ }
670
+ }
671
+
672
+ open func remove(_ item: AVPlayerItem) {
673
+ if item === currentItem {
674
+ advanceToNextItem()
675
+ } else {
676
+ queue.removeAll { $0 === item }
677
+ }
678
+ }
679
+
680
+ open func removeAllItems() {
681
+ queue.removeAll()
682
+ _setItem(nil)
683
+ }
684
+ }
685
+
686
+ // MARK: - AVPlayerLooper
687
+
688
+ /// Plays an item over and over in a queue player: the page's element loops
689
+ /// (`loop`), and each pass counts and posts `didPlayToEndTimeNotification`.
690
+ /// Looping stops when the looper is released or disabled. `timeRange` is kept
691
+ /// and not applied (the whole item loops).
692
+ public final class AVPlayerLooper: @unchecked Sendable {
693
+ public enum Status: Int, Sendable {
694
+ case unknown, ready, failed, cancelled
695
+ }
696
+
697
+ private weak var player: AVQueuePlayer?
698
+ private let templateItem: AVPlayerItem
699
+ public let timeRange: CMTimeRange
700
+ public private(set) var loopCount = 0
701
+ private var disabled = false
702
+
703
+ public init(player: AVQueuePlayer, templateItem itemToLoop: AVPlayerItem,
704
+ timeRange loopRange: CMTimeRange = CMTimeRange(start: .zero, duration: .positiveInfinity)) {
705
+ self.player = player
706
+ templateItem = itemToLoop
707
+ timeRange = loopRange
708
+ player.removeAllItems()
709
+ player.insert(itemToLoop, after: nil)
710
+ player._looper = self
711
+ player._state.loops = true
712
+ }
713
+
714
+ public convenience init(player: AVQueuePlayer, templateItem itemToLoop: AVPlayerItem) {
715
+ self.init(player: player, templateItem: itemToLoop, timeRange: CMTimeRange(start: .zero, duration: .positiveInfinity))
716
+ }
717
+
718
+ public var status: Status {
719
+ if disabled { return .cancelled }
720
+ switch templateItem.status {
721
+ case .readyToPlay: return .ready
722
+ case .failed: return .failed
723
+ case .unknown: return .unknown
724
+ }
725
+ }
726
+
727
+ public var error: (any Error)? { templateItem.error }
728
+
729
+ /// The item each pass plays (the page replays the template itself).
730
+ public var loopingPlayerItems: [AVPlayerItem] { [templateItem] }
731
+
732
+ public func disableLooping() {
733
+ disabled = true
734
+ if let player, player._looper === self {
735
+ player._state.loops = false
736
+ player._looper = nil
737
+ }
738
+ }
739
+
740
+ func _looped() {
741
+ loopCount += 1
742
+ }
743
+
744
+ deinit {
745
+ if let player, player._looper == nil || player._looper === self {
746
+ player._state.loops = false
747
+ }
748
+ }
749
+ }