@swiftbrowser/swift 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Package.swift +200 -9
- package/README.md +7 -9
- package/Sources/AVFoundation/AVFoundation.swift +131 -0
- package/Sources/AVFoundation/Asset.swift +487 -0
- package/Sources/AVFoundation/AudioSession.swift +144 -0
- package/Sources/AVFoundation/CMTime.swift +303 -0
- package/Sources/AVFoundation/Capture.swift +688 -0
- package/Sources/AVFoundation/Player.swift +749 -0
- package/Sources/AVFoundation/PlayerView.swift +117 -0
- package/Sources/AVKit/AVKit.swift +132 -0
- package/Sources/Accelerate/Accelerate.swift +122 -0
- package/Sources/AppIntents/AppIntents.swift +144 -0
- package/Sources/AppIntents/Parameters.swift +220 -0
- package/Sources/AppIntents/Shortcuts.swift +159 -0
- package/Sources/AudioToolbox/AudioToolbox.swift +55 -0
- package/Sources/AuthenticationServices/AuthenticationServices.swift +257 -0
- package/Sources/AuthenticationServices/Authorization.swift +251 -0
- package/Sources/Charts/Charts.swift +0 -4
- package/Sources/Combine/AnyCancellable.swift +65 -0
- package/Sources/Combine/AnyPublisher.swift +148 -0
- package/Sources/Combine/AnySubscriber.swift +230 -0
- package/Sources/Combine/Cancellable.swift +35 -0
- package/Sources/Combine/Codable.swift +29 -0
- package/Sources/Combine/CombineIdentifier.swift +45 -0
- package/Sources/Combine/Concurrency/ConcurrencyHelpers.swift +24 -0
- package/Sources/Combine/Concurrency/Future+Concurrency.swift +136 -0
- package/Sources/Combine/Concurrency/GENERATED-Publisher+Concurrency.swift +397 -0
- package/Sources/Combine/CurrentValueSubject.swift +252 -0
- package/Sources/Combine/CustomCombineIdentifierConvertible.swift +28 -0
- package/Sources/Combine/Future.swift +228 -0
- package/Sources/Combine/GENERATED-RootProtocols.swift +426 -0
- package/Sources/Combine/Helpers/ConduitBase.swift +40 -0
- package/Sources/Combine/Helpers/ConduitList.swift +59 -0
- package/Sources/Combine/Helpers/DebugHook.swift +177 -0
- package/Sources/Combine/Helpers/FilterProducer.swift +174 -0
- package/Sources/Combine/Helpers/Locking.swift +30 -0
- package/Sources/Combine/Helpers/PartialCompletion.swift +26 -0
- package/Sources/Combine/Helpers/ReduceProducer.swift +249 -0
- package/Sources/Combine/Helpers/SubjectSubscriber.swift +96 -0
- package/Sources/Combine/Helpers/SubscriberTap.swift +69 -0
- package/Sources/Combine/Helpers/SubscriptionStatus.swift +33 -0
- package/Sources/Combine/Helpers/SubscriptionTap.swift +33 -0
- package/Sources/Combine/Helpers/Utils.swift +30 -0
- package/Sources/Combine/Helpers/Violations.swift +33 -0
- package/Sources/Combine/ImmediateScheduler.swift +178 -0
- package/Sources/Combine/LICENSE +21 -0
- package/Sources/Combine/NotificationPublisher.swift +48 -0
- package/Sources/Combine/ObjectWillChange.swift +46 -0
- package/Sources/Combine/PassthroughSubject.swift +222 -0
- package/Sources/Combine/Publisher+Subscribe.swift +53 -0
- package/Sources/Combine/Publishers/Deferred.swift +48 -0
- package/Sources/Combine/Publishers/Empty.swift +54 -0
- package/Sources/Combine/Publishers/Fail.swift +42 -0
- package/Sources/Combine/Publishers/GENERATED-Publishers.Catch.swift +673 -0
- package/Sources/Combine/Publishers/GENERATED-Publishers.Encode.swift +388 -0
- package/Sources/Combine/Publishers/GENERATED-Publishers.MapKeyPath.swift +384 -0
- package/Sources/Combine/Publishers/Just.swift +313 -0
- package/Sources/Combine/Publishers/Optional.Publisher.swift +315 -0
- package/Sources/Combine/Publishers/Publishers.AllSatisfy.swift +217 -0
- package/Sources/Combine/Publishers/Publishers.AssertNoFailure.swift +158 -0
- package/Sources/Combine/Publishers/Publishers.Autoconnect.swift +193 -0
- package/Sources/Combine/Publishers/Publishers.Breakpoint.swift +231 -0
- package/Sources/Combine/Publishers/Publishers.Buffer.swift +356 -0
- package/Sources/Combine/Publishers/Publishers.Collect.swift +102 -0
- package/Sources/Combine/Publishers/Publishers.CollectByCount.swift +195 -0
- package/Sources/Combine/Publishers/Publishers.CompactMap.swift +241 -0
- package/Sources/Combine/Publishers/Publishers.Comparison.swift +371 -0
- package/Sources/Combine/Publishers/Publishers.Concatenate.swift +474 -0
- package/Sources/Combine/Publishers/Publishers.Contains.swift +293 -0
- package/Sources/Combine/Publishers/Publishers.Count.swift +87 -0
- package/Sources/Combine/Publishers/Publishers.Debounce.swift +332 -0
- package/Sources/Combine/Publishers/Publishers.Delay.swift +269 -0
- package/Sources/Combine/Publishers/Publishers.Drop.swift +155 -0
- package/Sources/Combine/Publishers/Publishers.DropUntilOutput.swift +283 -0
- package/Sources/Combine/Publishers/Publishers.DropWhile.swift +378 -0
- package/Sources/Combine/Publishers/Publishers.Filter.swift +248 -0
- package/Sources/Combine/Publishers/Publishers.First.swift +261 -0
- package/Sources/Combine/Publishers/Publishers.FlatMap.swift +533 -0
- package/Sources/Combine/Publishers/Publishers.HandleEvents.swift +260 -0
- package/Sources/Combine/Publishers/Publishers.IgnoreOutput.swift +109 -0
- package/Sources/Combine/Publishers/Publishers.Last.swift +252 -0
- package/Sources/Combine/Publishers/Publishers.MakeConnectable.swift +82 -0
- package/Sources/Combine/Publishers/Publishers.Map.swift +351 -0
- package/Sources/Combine/Publishers/Publishers.MapError.swift +140 -0
- package/Sources/Combine/Publishers/Publishers.MeasureInterval.swift +187 -0
- package/Sources/Combine/Publishers/Publishers.Multicast.swift +284 -0
- package/Sources/Combine/Publishers/Publishers.Output.swift +241 -0
- package/Sources/Combine/Publishers/Publishers.PrefixUntilOutput.swift +197 -0
- package/Sources/Combine/Publishers/Publishers.PrefixWhile.swift +176 -0
- package/Sources/Combine/Publishers/Publishers.Print.swift +206 -0
- package/Sources/Combine/Publishers/Publishers.ReceiveOn.swift +227 -0
- package/Sources/Combine/Publishers/Publishers.Reduce.swift +204 -0
- package/Sources/Combine/Publishers/Publishers.RemoveDuplicates.swift +264 -0
- package/Sources/Combine/Publishers/Publishers.ReplaceEmpty.swift +201 -0
- package/Sources/Combine/Publishers/Publishers.ReplaceError.swift +211 -0
- package/Sources/Combine/Publishers/Publishers.Retry.swift +274 -0
- package/Sources/Combine/Publishers/Publishers.Scan.swift +331 -0
- package/Sources/Combine/Publishers/Publishers.Sequence.swift +437 -0
- package/Sources/Combine/Publishers/Publishers.SetFailureType.swift +120 -0
- package/Sources/Combine/Publishers/Publishers.Share.swift +104 -0
- package/Sources/Combine/Publishers/Publishers.SubscribeOn.swift +197 -0
- package/Sources/Combine/Publishers/Publishers.SwitchToLatest.swift +444 -0
- package/Sources/Combine/Publishers/Publishers.Throttle.swift +359 -0
- package/Sources/Combine/Publishers/Publishers.Timeout.swift +301 -0
- package/Sources/Combine/Publishers/Publishers.Zip.swift +720 -0
- package/Sources/Combine/Publishers/Publishers.swift +13 -0
- package/Sources/Combine/Publishers/Record.swift +237 -0
- package/Sources/Combine/Publishers/Result.Publisher.swift +337 -0
- package/Sources/Combine/Result.swift +46 -0
- package/Sources/Combine/Scheduler.swift +89 -0
- package/Sources/Combine/Schedulers.swift +211 -0
- package/Sources/Combine/Subject+Void.swift +17 -0
- package/Sources/Combine/Subscriber+Void.swift +20 -0
- package/Sources/Combine/Subscribers/Subscribers.Assign.swift +175 -0
- package/Sources/Combine/Subscribers/Subscribers.Completion.swift +90 -0
- package/Sources/Combine/Subscribers/Subscribers.Demand.swift +476 -0
- package/Sources/Combine/Subscribers/Subscribers.Sink.swift +206 -0
- package/Sources/Combine/Subscribers/Subscribers.swift +9 -0
- package/Sources/Combine/Subscription.swift +22 -0
- package/Sources/Combine/Subscriptions.swift +42 -0
- package/Sources/Combine/SwiftUIObservation.swift +10 -0
- package/Sources/Combine/_Introspection.swift +107 -0
- package/Sources/CommonCrypto/CommonCrypto.swift +80 -0
- package/Sources/Compression/Compression.swift +365 -0
- package/Sources/Compression/Deflate.swift +407 -0
- package/Sources/Compression/Zlib.swift +179 -0
- package/Sources/CoreFoundationTypes/CoreFoundationTypes.swift +101 -0
- package/Sources/CoreGraphics/CoreGraphics.swift +411 -0
- package/Sources/CoreGraphics/ProjectionTransforms.swift +28 -0
- package/Sources/CoreHaptics/CoreHaptics.swift +399 -0
- package/Sources/CoreImage/CIColor.swift +41 -0
- package/Sources/CoreImage/CIContext.swift +31 -0
- package/Sources/CoreImage/CIDetector.swift +57 -0
- package/Sources/CoreImage/CIFilter.swift +243 -0
- package/Sources/CoreImage/CIFilterBuiltins.swift +136 -0
- package/Sources/CoreImage/CIImage.swift +162 -0
- package/Sources/CoreImage/QRCode.swift +393 -0
- package/Sources/CoreLocation/Coordinate.swift +30 -0
- package/Sources/CoreLocation/Location.swift +65 -0
- package/Sources/CoreML/Evaluation.swift +414 -0
- package/Sources/CoreML/FeatureValue.swift +266 -0
- package/Sources/CoreML/Model.swift +269 -0
- package/Sources/CoreML/MultiArray.swift +92 -0
- package/Sources/CoreML/Specification.swift +233 -0
- package/Sources/CoreText/CoreText.swift +64 -0
- package/Sources/CoreTransferable/CoreTransferable.swift +606 -0
- package/Sources/CryptoKit/ChaChaPoly.swift +258 -0
- package/Sources/CryptoKit/CryptoKit.swift +375 -0
- package/Sources/CryptoKit/Curve25519.swift +317 -0
- package/Sources/CryptoKit/P256.swift +321 -0
- package/Sources/FoundationBridge/AppStorage.swift +165 -0
- package/Sources/FoundationBridge/AssetResources.swift +90 -0
- package/Sources/FoundationBridge/AsyncBytes.swift +128 -0
- package/Sources/FoundationBridge/AsyncImage.swift +116 -0
- package/Sources/FoundationBridge/AttributedStrings.swift +16 -0
- package/Sources/{_FoundationCommon → FoundationBridge}/Bundle.swift +41 -4
- package/Sources/FoundationBridge/CombineExports.swift +8 -0
- package/Sources/FoundationBridge/CoreFoundation.swift +77 -0
- package/Sources/FoundationBridge/Dispatch.swift +458 -0
- package/Sources/FoundationBridge/EnvironmentFoundation.swift +41 -0
- package/Sources/FoundationBridge/FileImporter.swift +128 -0
- package/Sources/FoundationBridge/FileSystem.swift +68 -0
- package/Sources/FoundationBridge/FoundationBridge.swift +24 -72
- package/Sources/FoundationBridge/HostResolution.swift +31 -0
- package/Sources/FoundationBridge/ItemProvider.swift +655 -0
- package/Sources/FoundationBridge/KeyValueObserving.swift +138 -0
- package/Sources/FoundationBridge/Links.swift +127 -0
- package/Sources/FoundationBridge/ListFormatter.swift +32 -0
- package/Sources/FoundationBridge/Localization.swift +149 -0
- package/Sources/FoundationBridge/Locks.swift +50 -0
- package/Sources/FoundationBridge/Markdown.swift +527 -0
- package/Sources/FoundationBridge/MeasurementFormat.swift +95 -0
- package/Sources/FoundationBridge/NotificationCenter.swift +154 -0
- package/Sources/FoundationBridge/OpenURLHandlers.swift +60 -0
- package/Sources/FoundationBridge/Operation.swift +195 -0
- package/Sources/FoundationBridge/ProcessInfo.swift +32 -0
- package/Sources/FoundationBridge/RelativeDateTimeFormatter.swift +136 -0
- package/Sources/FoundationBridge/SceneStorage.swift +78 -0
- package/Sources/FoundationBridge/SecurityExports.swift +8 -0
- package/Sources/FoundationBridge/Selector.swift +226 -0
- package/Sources/FoundationBridge/StringComparators.swift +17 -0
- package/Sources/FoundationBridge/Table.swift +211 -0
- package/Sources/FoundationBridge/TextAttributes.swift +42 -0
- package/Sources/FoundationBridge/Timer.swift +6 -1
- package/Sources/FoundationBridge/URLSession.swift +718 -0
- package/Sources/{_FoundationCommon → FoundationBridge}/UserDefaults.swift +17 -4
- package/Sources/FoundationBridge/WebAuthRedirects.swift +227 -0
- package/Sources/FoundationBridge/WebSocket.swift +187 -0
- package/Sources/FoundationModels/FoundationModels.swift +1098 -0
- package/Sources/ImageIO/GIF.swift +72 -0
- package/Sources/ImageIO/ImageIO.swift +248 -0
- package/Sources/ImageIO/ImageTypes.swift +28 -0
- package/Sources/LinkPresentation/LinkPresentation.swift +249 -0
- package/Sources/LocalAuthentication/LAContext.swift +86 -0
- package/Sources/MapKit/Geometry.swift +223 -0
- package/Sources/MapKit/Map.swift +638 -0
- package/Sources/MapKit/MapView.swift +229 -0
- package/Sources/MobileCoreServices/MobileCoreServices.swift +28 -0
- package/Sources/NaturalLanguage/LanguageIdentifier.swift +317 -0
- package/Sources/NaturalLanguage/NaturalLanguage.swift +135 -0
- package/Sources/OSLog/OSLog.swift +6 -0
- package/Sources/Photos/Photos.swift +572 -0
- package/Sources/PhotosUI/PhotosPicker.swift +248 -0
- package/Sources/PhotosUI/PhotosPickerItem.swift +261 -0
- package/Sources/QuickLook/QuickLook.swift +168 -0
- package/Sources/SafariServices/SafariServices.swift +106 -0
- package/Sources/Security/Security.swift +332 -0
- package/Sources/StoreKit/AppStore.swift +207 -0
- package/Sources/StoreKit/Products.swift +280 -0
- package/Sources/StoreKit/RequestReview.swift +28 -0
- package/Sources/StoreKit/StoreKit1.swift +486 -0
- package/Sources/StoreKit/StoreViews.swift +428 -0
- package/Sources/StoreKit/Transactions.swift +197 -0
- package/Sources/SwiftData/Macros.swift +58 -0
- package/Sources/SwiftData/ModelActor.swift +38 -0
- package/Sources/SwiftData/ModelContainer.swift +129 -0
- package/Sources/SwiftData/ModelContext.swift +451 -0
- package/Sources/SwiftData/PersistentModel.swift +238 -0
- package/Sources/SwiftData/Properties.swift +261 -0
- package/Sources/SwiftData/Query.swift +256 -0
- package/Sources/SwiftData/Schema.swift +192 -0
- package/Sources/SwiftData/Store.swift +146 -0
- package/Sources/SwiftData/StringPredicates.swift +125 -0
- package/Sources/SwiftDataMacros/EntryMacro.swift +82 -0
- package/Sources/SwiftDataMacros/GenerableMacro.swift +183 -0
- package/Sources/SwiftDataMacros/ModelActorMacro.swift +46 -0
- package/Sources/SwiftDataMacros/ModelMacro.swift +260 -0
- package/Sources/SwiftDataMacros/Plugin.swift +18 -0
- package/Sources/SwiftDataMacros/PredicateMacro.swift +1244 -0
- package/Sources/SwiftUI/API/Accessibility.swift +55 -6
- package/Sources/SwiftUI/API/AccessibilityExtras.swift +33 -0
- package/Sources/SwiftUI/API/AccessibilityFocus.swift +63 -0
- package/Sources/SwiftUI/API/Alert.swift +1 -1
- package/Sources/SwiftUI/API/AlignmentGuides.swift +65 -0
- package/Sources/SwiftUI/API/Animation.swift +37 -5
- package/Sources/SwiftUI/API/App.swift +31 -1
- package/Sources/SwiftUI/API/BindingCollections.swift +35 -0
- package/Sources/SwiftUI/API/BindingForEach.swift +27 -0
- package/Sources/SwiftUI/API/Bitmaps.swift +254 -0
- package/Sources/SwiftUI/API/ButtonStyles.swift +96 -0
- package/Sources/SwiftUI/API/Canvas.swift +337 -0
- package/Sources/SwiftUI/API/Color.swift +104 -28
- package/Sources/SwiftUI/API/ColorEffects.swift +52 -0
- package/Sources/SwiftUI/API/ColorPickerAndLabeledContent.swift +138 -0
- package/Sources/SwiftUI/API/ColorSpaces.swift +161 -0
- package/Sources/SwiftUI/API/Commands.swift +106 -0
- package/Sources/SwiftUI/API/ContainerBackground.swift +28 -0
- package/Sources/SwiftUI/API/ContentShape.swift +33 -0
- package/Sources/SwiftUI/API/ContentSizeCategory.swift +35 -0
- package/Sources/SwiftUI/API/ContentUnavailable.swift +101 -0
- package/Sources/SwiftUI/API/Controls.swift +59 -27
- package/Sources/SwiftUI/API/CustomLayout.swift +49 -10
- package/Sources/SwiftUI/API/CustomToolbarContent.swift +46 -0
- package/Sources/SwiftUI/API/Decorations.swift +1 -1
- package/Sources/SwiftUI/API/EditMode.swift +25 -82
- package/Sources/SwiftUI/API/Effects.swift +105 -7
- package/Sources/SwiftUI/API/Entry.swift +8 -0
- package/Sources/SwiftUI/API/Environment.swift +93 -9
- package/Sources/SwiftUI/API/EnvironmentSendable.swift +6 -0
- package/Sources/SwiftUI/API/Focus.swift +11 -3
- package/Sources/SwiftUI/API/Gauge.swift +3 -2
- package/Sources/SwiftUI/API/GeometryEffects.swift +232 -0
- package/Sources/SwiftUI/API/GeometryReader.swift +13 -2
- package/Sources/SwiftUI/API/GestureState.swift +65 -0
- package/Sources/SwiftUI/API/Gestures.swift +1 -1
- package/Sources/SwiftUI/API/Glass.swift +127 -0
- package/Sources/SwiftUI/API/Grid.swift +1 -1
- package/Sources/SwiftUI/API/GroupBoxAndControlGroup.swift +130 -0
- package/Sources/SwiftUI/API/KeyPress.swift +54 -0
- package/Sources/SwiftUI/API/LabelStyle.swift +13 -6
- package/Sources/SwiftUI/API/Layout.swift +67 -12
- package/Sources/SwiftUI/API/LazyStacks.swift +49 -0
- package/Sources/SwiftUI/API/LegacyAlert.swift +97 -0
- package/Sources/SwiftUI/API/Lists.swift +61 -6
- package/Sources/SwiftUI/API/Menu.swift +8 -1
- package/Sources/SwiftUI/API/Modifiers.swift +80 -10
- package/Sources/SwiftUI/API/MoreModifiers.swift +100 -0
- package/Sources/SwiftUI/API/Namespace.swift +44 -0
- package/Sources/SwiftUI/API/Navigation.swift +24 -9
- package/Sources/SwiftUI/API/NavigationDestinationBindings.swift +55 -0
- package/Sources/SwiftUI/API/ObservableObject.swift +16 -2
- package/Sources/SwiftUI/API/Path.swift +543 -0
- package/Sources/SwiftUI/API/Phase8Controls.swift +48 -31
- package/Sources/SwiftUI/API/PhaseAnimator.swift +130 -0
- package/Sources/SwiftUI/API/Picker.swift +11 -2
- package/Sources/SwiftUI/API/PointerAndKeyboard.swift +77 -0
- package/Sources/SwiftUI/API/Popover.swift +41 -0
- package/Sources/SwiftUI/API/Preferences.swift +262 -0
- package/Sources/SwiftUI/API/Presentation.swift +17 -4
- package/Sources/SwiftUI/API/PresentationExtras.swift +60 -0
- package/Sources/SwiftUI/API/Primitives.swift +164 -37
- package/Sources/SwiftUI/API/Publishers.swift +4 -4
- package/Sources/SwiftUI/API/Redaction.swift +75 -0
- package/Sources/SwiftUI/API/RowActions.swift +3 -2
- package/Sources/SwiftUI/API/Scenes.swift +131 -0
- package/Sources/SwiftUI/API/ScrollModifiers.swift +132 -0
- package/Sources/SwiftUI/API/ScrollTargets.swift +3 -3
- package/Sources/SwiftUI/API/ScrollViewReader.swift +144 -0
- package/Sources/SwiftUI/API/SectionSupplements.swift +96 -0
- package/Sources/SwiftUI/API/Shape.swift +214 -0
- package/Sources/SwiftUI/API/ShapeStyle.swift +105 -7
- package/Sources/SwiftUI/API/ShapeTrim.swift +78 -0
- package/Sources/SwiftUI/API/Sharing.swift +193 -0
- package/Sources/SwiftUI/API/StackLayouts.swift +199 -0
- package/Sources/SwiftUI/API/State.swift +18 -1
- package/Sources/SwiftUI/API/StatusGaps.swift +121 -0
- package/Sources/SwiftUI/API/SymbolEffects.swift +203 -0
- package/Sources/SwiftUI/API/TabContent.swift +841 -0
- package/Sources/SwiftUI/API/Table.swift +382 -0
- package/Sources/SwiftUI/API/Tabs.swift +148 -4
- package/Sources/SwiftUI/API/Tasks.swift +2 -2
- package/Sources/SwiftUI/API/Text.swift +386 -0
- package/Sources/SwiftUI/API/TextDates.swift +61 -0
- package/Sources/SwiftUI/API/TextDecorations.swift +52 -0
- package/Sources/SwiftUI/API/TextFieldAxis.swift +103 -0
- package/Sources/SwiftUI/API/TextFieldCommit.swift +52 -0
- package/Sources/SwiftUI/API/Toolbar.swift +52 -16
- package/Sources/SwiftUI/API/Transfer.swift +145 -0
- package/Sources/SwiftUI/API/UnevenRoundedRectangle.swift +66 -0
- package/Sources/SwiftUI/API/Video.swift +64 -0
- package/Sources/SwiftUI/API/View.swift +10 -2
- package/Sources/SwiftUI/API/ViewModifier.swift +4 -1
- package/Sources/SwiftUI/API/VisualEffect.swift +74 -1
- package/Sources/SwiftUI/API/Windows.swift +25 -0
- package/Sources/SwiftUI/Charts/Axis.swift +7 -1
- package/Sources/SwiftUI/Charts/AxisFormatsAndRanges.swift +68 -0
- package/Sources/SwiftUI/Charts/Chart.swift +17 -3
- package/Sources/SwiftUI/Charts/ChartProxy.swift +57 -0
- package/Sources/SwiftUI/Localization/Localization.swift +498 -0
- package/Sources/SwiftUI/Localization/PluralRules.swift +127 -0
- package/Sources/SwiftUI/Runtime/Executor.swift +2 -0
- package/Sources/SwiftUI/Runtime/Exports.swift +37 -1
- package/Sources/SwiftUI/Runtime/ImageSource.swift +310 -0
- package/Sources/SwiftUI/Runtime/JSON.swift +76 -4
- package/Sources/SwiftUI/Runtime/LazyRows.swift +99 -0
- package/Sources/SwiftUI/Runtime/Measurements.swift +140 -0
- package/Sources/SwiftUI/Runtime/Ops.swift +58 -12
- package/Sources/SwiftUI/Runtime/Reconciler.swift +10 -1
- package/Sources/SwiftUI/Runtime/Replay.swift +382 -0
- package/Sources/SwiftUI/Runtime/Runtime.swift +977 -198
- package/Sources/SwiftUI/Runtime/Sameness.swift +152 -0
- package/Sources/SwiftUI/Runtime/ViewPath.swift +89 -0
- package/Sources/SwiftUI/Runtime/ViewTraits.swift +175 -0
- package/Sources/SwiftUI/Runtime/WebAuthHeadless.swift +49 -0
- package/Sources/Symbols/Symbols.swift +25 -0
- package/Sources/Translation/Translation.swift +423 -0
- package/Sources/UIKit/Activity.swift +213 -0
- package/Sources/UIKit/Alerts.swift +69 -0
- package/Sources/UIKit/AppDelegate.swift +222 -0
- package/Sources/UIKit/Appearance.swift +84 -0
- package/Sources/UIKit/ColorConversions.swift +70 -0
- package/Sources/UIKit/Controls.swift +225 -0
- package/Sources/UIKit/Device.swift +200 -0
- package/Sources/UIKit/Feedback.swift +65 -0
- package/Sources/UIKit/Font.swift +307 -0
- package/Sources/UIKit/Graphics.swift +204 -0
- package/Sources/UIKit/HostingController.swift +185 -0
- package/Sources/UIKit/Identity.swift +24 -0
- package/Sources/UIKit/Introspectable.swift +510 -0
- package/Sources/UIKit/Pasteboard.swift +363 -0
- package/Sources/UIKit/PhotoAlbum.swift +81 -0
- package/Sources/UIKit/RemoteNotifications.swift +15 -0
- package/Sources/UIKit/Representable.swift +288 -0
- package/Sources/UIKit/SafeArea.swift +12 -0
- package/Sources/UIKit/SceneDelegates.swift +132 -0
- package/Sources/UIKit/Scenes.swift +40 -0
- package/Sources/UIKit/ScrollView.swift +157 -0
- package/Sources/UIKit/ShortcutItems.swift +67 -0
- package/Sources/UIKit/SymbolConfiguration.swift +124 -0
- package/Sources/UIKit/TargetAction.swift +123 -0
- package/Sources/UIKit/TextPaste.swift +161 -0
- package/Sources/UIKit/TextView.swift +538 -0
- package/Sources/UIKit/UIKit.swift +292 -0
- package/Sources/UIKit/UIView.swift +855 -0
- package/Sources/UIKit/ViewTranslation.swift +311 -0
- package/Sources/UniformTypeIdentifiers/UTType.swift +196 -0
- package/Sources/UserNotifications/Content.swift +370 -0
- package/Sources/UserNotifications/NotificationCenter.swift +309 -0
- package/Sources/WeatherKit/WeatherKit.swift +228 -0
- package/Sources/WebKit/WebKit.swift +245 -0
- package/Sources/os/os.swift +376 -0
- package/Sources/zlib/zlib.swift +6 -0
- package/index.d.ts +1 -1
- package/index.mjs +2 -2
- package/manifest.d.mts +2 -0
- package/manifest.mjs +39 -0
- package/package.json +5 -3
- package/sync-sources.mjs +21 -7
- package/Sources/FoundationLite/Common.swift +0 -3
- package/Sources/FoundationLite/FoundationLite.swift +0 -171
- package/Sources/FoundationLite/Interaction.swift +0 -146
- package/Sources/FoundationLite/Timer.swift +0 -48
- 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
|
+
}
|