@legendapp/spark 0.0.1-next.2 → 0.0.1-next.3
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/LICENSE +21 -0
- package/README.md +10 -4
- package/ai-codex.ts +1 -0
- package/ai.ts +1 -0
- package/app-documents.ts +1 -0
- package/config-plugin.d.cts +2 -0
- package/contracts.ts +2 -0
- package/diagnostics.ts +1 -0
- package/expo-config.d.cts +1 -0
- package/metro.cjs +4 -1
- package/metro.d.cts +12 -0
- package/native.cjs +3 -1
- package/native.d.cts +6 -0
- package/package.json +82 -14
- package/schema.json +276 -58
- package/settings-observable.ts +1 -0
- package/settings-window.ts +1 -0
- package/shortcuts-commands.ts +1 -0
- package/shortcuts-keyboard.ts +3 -0
- package/ui-classnames.ts +1 -0
- package/ui-glass.ts +1 -0
- package/ui-search.ts +1 -0
- package/ui-sidebar.ts +1 -0
- package/ui-split-view.ts +1 -0
- package/ui-symbol.ts +1 -0
- package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
- package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
- package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
- package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
- package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
- package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
- package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
- package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
- package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
- package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
- package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
- package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
- package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
- package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
- package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
- package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
- package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
- package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
- package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
- package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
- package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
- package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
- package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
- package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
- package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
- package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
- package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
- package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
- package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
- package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
- package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
- package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
- package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
- package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
- package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
- package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
- package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
- package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
- package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
- package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
- package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
- package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
- package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
- package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
- package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
- package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
- package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
- package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
- package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
- package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
- package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
- package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
- package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
- package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
- package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
- package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
- package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
- package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
- package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
- package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
- package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
- package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
- package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
- package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
- package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
- package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
- package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
- package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
- package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
- package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
- package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
- package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
- package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
- package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
- package/windows-macos.ts +1 -0
- package/expo-metro.cjs +0 -1
- package/message-dialog.ts +0 -1
- package/universal.cjs +0 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
|
@@ -1,25 +1,58 @@
|
|
|
1
1
|
import Native from "./NativeSparkAudio";
|
|
2
|
-
import {
|
|
2
|
+
import { onAudioEvent } from "./events";
|
|
3
|
+
import { SparkError, invokeNative, parseNativeResult } from "@legendapp/spark-desktop-app/src/contracts";
|
|
4
|
+
import { sessionOptions, sessionSnapshot, validMediaCommand, type MediaCommand, type MediaSession, type MediaSessionOptions } from "./media-types";
|
|
3
5
|
let owner: (() => void) | undefined, sequence = 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
let queue: Promise<unknown> = Promise.resolve();
|
|
7
|
+
function enqueue<T>(operation: () => Promise<T>): Promise<T> {
|
|
8
|
+
const result = queue.catch(() => {}).then(operation); queue = result; return result;
|
|
9
|
+
}
|
|
10
|
+
/** One explicit session owns system controls. New sessions replace the previous owner. */
|
|
11
|
+
export async function createMediaSession(options: MediaSessionOptions): Promise<MediaSession> {
|
|
12
|
+
const state = sessionOptions(options);
|
|
13
|
+
const { onCommand, onError = console.error } = options;
|
|
14
|
+
if (!Native) throw new SparkError("E_MODULE_UNAVAILABLE", "Media sessions require @legendapp/spark-audio");
|
|
8
15
|
const native = Native, id = `media-${Date.now()}-${++sequence}`;
|
|
9
|
-
let removed = false,
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
await call(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
let stopped = false, removed = false, ready = false, removal: Promise<void> | undefined;
|
|
17
|
+
const buffered: MediaCommand[] = [];
|
|
18
|
+
const call = async <T>(method: string, args: object, validate: (value: unknown) => value is T) => parseNativeResult(await invokeNative(() => native.call(method, JSON.stringify({ id, ...args }))), validate);
|
|
19
|
+
const command = async (method: string, args = {}) => { await call(method, args, (value): value is null => value === null); };
|
|
20
|
+
const stop = () => { stopped = true; buffered.length = 0; subscription.remove(); };
|
|
21
|
+
const deliver = (value: unknown) => {
|
|
22
|
+
if (stopped) return;
|
|
23
|
+
if (!validMediaCommand(value)) { stop(); onError(new SparkError("E_INVALID_DATA", "Invalid media command")); return; }
|
|
24
|
+
if (!ready) { if (buffered.length < 128) buffered.push(value); return; }
|
|
25
|
+
if (owner !== stop) return;
|
|
26
|
+
try { onCommand(value); } catch (error) { onError(error); }
|
|
27
|
+
};
|
|
28
|
+
const subscription = onAudioEvent("sparkMediaCommand", id, deliver);
|
|
29
|
+
try { await enqueue(async () => {
|
|
30
|
+
try { await command("sessionCreate", state); }
|
|
31
|
+
catch (cause) {
|
|
32
|
+
try { await command("sessionRemove"); } catch (cleanup) { throw new SparkError("E_NATIVE", "Media creation and cleanup failed", { cause: new AggregateError([cause, cleanup]) }); }
|
|
33
|
+
throw cause;
|
|
34
|
+
}
|
|
35
|
+
owner?.(); owner = stop; ready = true;
|
|
36
|
+
for (const value of buffered.splice(0)) deliver(value);
|
|
37
|
+
}); } catch (error) { stop(); throw error; }
|
|
21
38
|
return {
|
|
22
|
-
async update(patch) {
|
|
23
|
-
|
|
39
|
+
async update(patch) {
|
|
40
|
+
if (stopped || owner !== stop) throw new SparkError("E_CLOSED", "Media session has been removed or replaced");
|
|
41
|
+
const snapshot = sessionSnapshot(patch);
|
|
42
|
+
await enqueue(async () => {
|
|
43
|
+
if (owner !== stop) throw new SparkError("E_CLOSED", "Media session has been replaced");
|
|
44
|
+
await command("sessionUpdate", snapshot);
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
remove() {
|
|
48
|
+
if (removed) return Promise.resolve();
|
|
49
|
+
if (removal) return removal;
|
|
50
|
+
stop();
|
|
51
|
+
removal = enqueue(async () => {
|
|
52
|
+
if (owner === stop) { await command("sessionRemove"); owner = undefined; }
|
|
53
|
+
removed = true;
|
|
54
|
+
}).finally(() => { removal = undefined; });
|
|
55
|
+
return removal;
|
|
56
|
+
},
|
|
24
57
|
};
|
|
25
58
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
|
|
2
|
+
import { sessionOptions, sessionSnapshot, type MediaCommandName, type MediaSession, type MediaSessionOptions, type MediaSessionUpdate } from "./media-types";
|
|
2
3
|
let owner: object | undefined, explicit = false;
|
|
3
4
|
export const hasExplicitMediaSession = () => explicit;
|
|
4
5
|
export const claimPlayerMediaSession = (player: object) => { owner = player; };
|
|
5
6
|
export const ownsPlayerMediaSession = (player: object) => owner === player;
|
|
6
7
|
export const releasePlayerMediaSession = (player: object) => { if (owner === player) owner = undefined; };
|
|
7
8
|
const actions: Record<MediaCommandName, MediaSessionAction> = { play: "play", pause: "pause", nextTrack: "nexttrack", previousTrack: "previoustrack", seekTo: "seekto" };
|
|
8
|
-
export async function createMediaSession(options: MediaSessionOptions
|
|
9
|
-
|
|
9
|
+
export async function createMediaSession(options: MediaSessionOptions): Promise<MediaSession> {
|
|
10
|
+
const initial = sessionOptions(options);
|
|
11
|
+
const { onCommand, onError = console.error } = options;
|
|
10
12
|
const available = typeof navigator === "undefined" ? undefined : navigator.mediaSession;
|
|
11
|
-
if (!available) throw
|
|
13
|
+
if (!available) throw new SparkError("E_UNSUPPORTED_PLATFORM", "Browser media sessions are unavailable");
|
|
12
14
|
const media = available;
|
|
13
|
-
const id = {}; let removed = false, state:
|
|
15
|
+
const id = {}; let removed = false, state: MediaSessionUpdate = { commands: ["play", "pause"] };
|
|
14
16
|
const clearActions = () => { for (const action of Object.values(actions)) { try { media.setActionHandler(action, null); } catch {} } };
|
|
15
|
-
function apply(patch:
|
|
17
|
+
function apply(patch: MediaSessionUpdate) {
|
|
16
18
|
state = { ...state, ...patch };
|
|
17
19
|
const metadata = state.metadata ?? {};
|
|
18
20
|
media.metadata = new MediaMetadata({ title: metadata.title, artist: metadata.artist, album: metadata.albumTitle, artwork: metadata.artworkUrl ? [{ src: metadata.artworkUrl }] : [] });
|
|
@@ -23,13 +25,19 @@ export async function createMediaSession(options: MediaSessionOptions, onCommand
|
|
|
23
25
|
}
|
|
24
26
|
clearActions();
|
|
25
27
|
for (const command of state.commands ?? []) media.setActionHandler(actions[command], details => {
|
|
26
|
-
if (owner
|
|
28
|
+
if (owner !== id || removed) return;
|
|
29
|
+
try {
|
|
30
|
+
if (command === "seekTo") {
|
|
31
|
+
if (typeof details.seekTime !== "number" || !Number.isFinite(details.seekTime) || details.seekTime < 0) throw new SparkError("E_INVALID_DATA", "Invalid seek command");
|
|
32
|
+
onCommand({ command, position: details.seekTime });
|
|
33
|
+
} else onCommand({ command });
|
|
34
|
+
} catch (error) { onError(error); }
|
|
27
35
|
});
|
|
28
36
|
}
|
|
29
37
|
owner = id; explicit = true;
|
|
30
|
-
try { apply(
|
|
38
|
+
try { apply(initial); } catch (error) { clearActions(); media.metadata = null; media.playbackState = "none"; owner = undefined; explicit = false; throw error; }
|
|
31
39
|
return {
|
|
32
|
-
async update(patch) { if (removed || owner !== id) throw new
|
|
33
|
-
async remove() { if (
|
|
40
|
+
async update(patch) { if (removed || owner !== id) throw new SparkError("E_CLOSED", "Media session has been removed or replaced"); apply(sessionSnapshot(patch)); },
|
|
41
|
+
async remove() { if (owner !== id) { removed = true; return; } removed = true; clearActions(); media.metadata = null; media.playbackState = "none"; media.setPositionState?.(); owner = undefined; explicit = false; },
|
|
34
42
|
};
|
|
35
43
|
}
|
|
@@ -1,32 +1,59 @@
|
|
|
1
|
+
import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
|
|
1
2
|
export type AudioMetadata = { title?: string; artist?: string; albumTitle?: string; artworkUrl?: string };
|
|
2
3
|
export type MediaCommandName = "play" | "pause" | "nextTrack" | "previousTrack" | "seekTo";
|
|
3
|
-
export type MediaCommand = { command:
|
|
4
|
-
export type
|
|
5
|
-
metadata?: AudioMetadata;
|
|
4
|
+
export type MediaCommand = { command: "seekTo"; position: number } | { command: Exclude<MediaCommandName, "seekTo">; position?: never };
|
|
5
|
+
export type MediaSessionUpdate = {
|
|
6
|
+
metadata?: AudioMetadata | null;
|
|
6
7
|
playbackState?: "playing" | "paused" | "stopped";
|
|
7
8
|
position?: number;
|
|
8
9
|
duration?: number;
|
|
9
10
|
playbackRate?: number;
|
|
10
|
-
commands?: MediaCommandName[];
|
|
11
|
+
commands?: readonly MediaCommandName[];
|
|
11
12
|
};
|
|
13
|
+
export interface MediaSessionOptions extends MediaSessionUpdate {
|
|
14
|
+
onCommand(command: MediaCommand): void;
|
|
15
|
+
onError?(error: unknown): void;
|
|
16
|
+
}
|
|
12
17
|
export interface MediaSession {
|
|
13
|
-
update(options:
|
|
18
|
+
update(options: MediaSessionUpdate): Promise<void>;
|
|
14
19
|
remove(): Promise<void>;
|
|
15
20
|
}
|
|
16
21
|
export function validateVolume(value: number) {
|
|
17
|
-
if (!Number.isFinite(value) || value < 0 || value > 1) throw new
|
|
22
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) throw new SparkError("E_INVALID_ARGUMENT", "Volume must be between 0 and 1");
|
|
18
23
|
}
|
|
19
24
|
export function validateMetadata(metadata: AudioMetadata) {
|
|
25
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) throw new SparkError("E_INVALID_ARGUMENT", "Expected audio metadata");
|
|
20
26
|
for (const [key, value] of Object.entries(metadata)) {
|
|
21
|
-
if (!["title", "artist", "albumTitle", "artworkUrl"].includes(key) || typeof value !== "string") throw new
|
|
27
|
+
if (!["title", "artist", "albumTitle", "artworkUrl"].includes(key) || typeof value !== "string") throw new SparkError("E_INVALID_ARGUMENT", "Invalid audio metadata");
|
|
22
28
|
}
|
|
23
|
-
if (metadata.artworkUrl && !/^(https?:\/\/|file:\/\/|\/)/.test(metadata.artworkUrl)) throw new
|
|
29
|
+
if (metadata.artworkUrl && !/^(https?:\/\/|file:\/\/|\/)/.test(metadata.artworkUrl)) throw new SparkError("E_INVALID_ARGUMENT", "Artwork requires an HTTP URL, file URL or absolute path");
|
|
24
30
|
}
|
|
25
|
-
export function validateSession(options:
|
|
26
|
-
if (options.
|
|
31
|
+
export function validateSession(options: MediaSessionUpdate) {
|
|
32
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected media session options");
|
|
33
|
+
for (const key of Object.keys(options)) if (!["metadata", "position", "duration", "playbackRate", "playbackState", "commands"].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported media option: ${key}`);
|
|
34
|
+
if (options.metadata !== undefined && options.metadata !== null) validateMetadata(options.metadata);
|
|
27
35
|
for (const key of ["position", "duration", "playbackRate"] as const) {
|
|
28
|
-
const value = options[key]; if (value !== undefined && (!Number.isFinite(value) || value < 0)) throw new
|
|
36
|
+
const value = options[key]; if (value !== undefined && (!Number.isFinite(value) || value < 0)) throw new SparkError("E_INVALID_ARGUMENT", `Invalid media ${key}`);
|
|
29
37
|
}
|
|
30
|
-
if (options.playbackState !== undefined && !["playing", "paused", "stopped"].includes(options.playbackState)) throw new
|
|
31
|
-
if (options.commands !== undefined && (!Array.isArray(options.commands) || options.commands.some(command => !["play", "pause", "nextTrack", "previousTrack", "seekTo"].includes(command)) || new Set(options.commands).size !== options.commands.length)) throw new
|
|
38
|
+
if (options.playbackState !== undefined && !["playing", "paused", "stopped"].includes(options.playbackState)) throw new SparkError("E_INVALID_ARGUMENT", "Invalid playback state");
|
|
39
|
+
if (options.commands !== undefined && (!Array.isArray(options.commands) || options.commands.some(command => !["play", "pause", "nextTrack", "previousTrack", "seekTo"].includes(command)) || new Set(options.commands).size !== options.commands.length)) throw new SparkError("E_INVALID_ARGUMENT", "Invalid media commands");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function sessionOptions(options: MediaSessionOptions): MediaSessionUpdate {
|
|
43
|
+
if (!options || typeof options.onCommand !== "function" || (options.onError !== undefined && typeof options.onError !== "function")) throw new SparkError("E_INVALID_ARGUMENT", "Expected media session callbacks");
|
|
44
|
+
const { onCommand, onError, ...state } = options;
|
|
45
|
+
return sessionSnapshot(state);
|
|
46
|
+
}
|
|
47
|
+
export function sessionSnapshot(options: MediaSessionUpdate): MediaSessionUpdate {
|
|
48
|
+
validateSession(options);
|
|
49
|
+
return { ...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined)),
|
|
50
|
+
...(options.metadata !== undefined ? { metadata: options.metadata === null ? {} : { ...options.metadata } } : {}),
|
|
51
|
+
...(options.commands !== undefined ? { commands: [...options.commands] } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function validMediaCommand(value: unknown): value is MediaCommand {
|
|
55
|
+
if (!value || typeof value !== "object") return false;
|
|
56
|
+
const command = value as MediaCommand;
|
|
57
|
+
return command.command === "seekTo" ? Number.isFinite(command.position) && command.position >= 0
|
|
58
|
+
: ["play", "pause", "nextTrack", "previousTrack"].includes(command.command) && command.position === undefined;
|
|
32
59
|
}
|
|
@@ -1,38 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { SparkError, nativeError } from "@legendapp/spark-desktop-app/src/contracts";
|
|
2
|
+
import { playerHandle, validatePlayerOptions, waitForAudio } from "./player";
|
|
3
3
|
export type * from "./media-types";
|
|
4
4
|
export { createMediaSession } from "./media-session-unavailable";
|
|
5
5
|
import { createAudioPlayer as createExpoPlayer, setAudioModeAsync } from "expo-audio";
|
|
6
|
-
import { validateSource,
|
|
7
|
-
export type { AudioPlayer, AudioSource, AudioStatus } from "./types";
|
|
8
|
-
export async function createAudioPlayer(source: AudioSource): Promise<AudioPlayer> {
|
|
9
|
-
validateSource(source);
|
|
6
|
+
import { validateSource, type AudioPlayer, type AudioSource, type AudioPlayerOptions, type AudioStatus } from "./types";
|
|
7
|
+
export type { AudioPlayer, AudioSource, AudioStatus, AudioPlayerOptions, Subscription } from "./types";
|
|
8
|
+
export async function createAudioPlayer(source: AudioSource, options: AudioPlayerOptions = {}): Promise<AudioPlayer> {
|
|
9
|
+
validateSource(source); validatePlayerOptions(options);
|
|
10
|
+
options = { ...options };
|
|
11
|
+
source = { ...source };
|
|
12
|
+
const input = source;
|
|
10
13
|
await setAudioModeAsync({ playsInSilentMode: true, shouldPlayInBackground: true, interruptionMode: "doNotMix" });
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let ended = false;
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
14
|
+
validatePlayerOptions(options);
|
|
15
|
+
const player = createExpoPlayer(input.uri);
|
|
16
|
+
let ended = false, failed = false, notifyReady: (() => void) | undefined;
|
|
17
|
+
const listeners = new Set<(status: AudioStatus) => void>();
|
|
18
|
+
const statusValue = (status = player.currentStatus): AudioStatus => ({ playing: status.playing, currentTime: status.currentTime, duration: status.duration, didJustFinish: ended, error: failed ? "Audio could not be decoded or loaded" : null, volume: player.volume });
|
|
19
|
+
const subscription = player.addListener("playbackStatusUpdate", status => {
|
|
20
|
+
if (status.didJustFinish) ended = true; failed = status.playbackState === "error";
|
|
21
|
+
notifyReady?.();
|
|
22
|
+
if (listeners.size) { const value = statusValue(status); for (const listener of [...listeners]) { try { listener(value); } catch (error) { console.error(error); } } }
|
|
23
|
+
});
|
|
17
24
|
try {
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
async
|
|
30
|
-
async
|
|
31
|
-
|
|
32
|
-
async
|
|
33
|
-
|
|
34
|
-
async
|
|
35
|
-
|
|
36
|
-
async remove() { if (!removed) { removed = true; listeners.close(); subscription.remove(); player.clearLockScreenControls(); player.remove(); } },
|
|
37
|
-
};
|
|
25
|
+
await waitForAudio(async () => { if (failed || player.currentStatus.playbackState === "error") throw new SparkError("E_NATIVE", "Audio could not be loaded"); return player.isLoaded; }, options,
|
|
26
|
+
notify => { notifyReady = notify; return { remove() { notifyReady = undefined; } }; });
|
|
27
|
+
player.setActiveForLockScreen(true, { title: input.title ?? "Music" });
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
subscription.remove();
|
|
30
|
+
try { player.remove(); } catch (cleanup) { throw new SparkError("E_NATIVE", "Audio loading and cleanup failed", { cause: new AggregateError([cause, cleanup]) }); }
|
|
31
|
+
throw nativeError(cause);
|
|
32
|
+
}
|
|
33
|
+
return playerHandle({
|
|
34
|
+
async setVolume(volume) { player.volume = volume; },
|
|
35
|
+
async setMetadata(metadata) { player.updateLockScreenMetadata(metadata ?? {}); },
|
|
36
|
+
async play() { ended = false; player.play(); },
|
|
37
|
+
async pause() { player.pause(); },
|
|
38
|
+
async seekTo(seconds) { ended = false; await player.seekTo(seconds); },
|
|
39
|
+
async getStatus() { return statusValue(); },
|
|
40
|
+
addStatusListener(listener) { listeners.add(listener); listener(statusValue()); return { remove() { listeners.delete(listener); } }; },
|
|
41
|
+
async remove() { listeners.clear(); subscription.remove(); player.clearLockScreenControls(); player.remove(); },
|
|
42
|
+
});
|
|
38
43
|
}
|
|
44
|
+
|
|
45
|
+
export { useAudioPlayer } from "./hooks";
|
|
46
|
+
export type { AudioPlayerState, AudioPlayerHookOptions } from "./hooks";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { SparkError, nativeError } from "@legendapp/spark-desktop-app/src/contracts";
|
|
2
|
+
import { validateMetadata, validateVolume } from "./media-types";
|
|
3
|
+
import { statusListeners } from "./status-listeners";
|
|
4
|
+
import { validateTime, type AudioPlayer, type AudioPlayerOptions, type AudioStatus } from "./types";
|
|
5
|
+
|
|
6
|
+
export type PlayerBackend = Omit<AudioPlayer, "addListener"> & { addStatusListener(listener: (status: AudioStatus) => void): ReturnType<AudioPlayer["addListener"]> };
|
|
7
|
+
export function validAudioStatus(value: unknown): value is AudioStatus {
|
|
8
|
+
if (!value || typeof value !== "object") return false;
|
|
9
|
+
const status = value as AudioStatus;
|
|
10
|
+
return typeof status.playing === "boolean" && typeof status.didJustFinish === "boolean" && (status.error === null || typeof status.error === "string") && [status.currentTime, status.duration, status.volume].every(value => typeof value === "number" && Number.isFinite(value) && value >= 0) && status.volume <= 1;
|
|
11
|
+
}
|
|
12
|
+
/** Command completion is ordered: AVPlayer seeks can finish after later native submissions.
|
|
13
|
+
* The queue is required for that guarantee; native status events do not enter it. */
|
|
14
|
+
export function playerHandle(backend: PlayerBackend): AudioPlayer {
|
|
15
|
+
let queue: Promise<unknown> = Promise.resolve();
|
|
16
|
+
let closing = false;
|
|
17
|
+
let removed = false;
|
|
18
|
+
let removal: Promise<void> | undefined;
|
|
19
|
+
const alive = () => { if (closing) throw new SparkError("E_CLOSED", "Audio player is closing or removed"); };
|
|
20
|
+
function enqueue<T>(operation: () => Promise<T>): Promise<T> {
|
|
21
|
+
if (closing) return Promise.reject(new SparkError("E_CLOSED", "Audio player is closing or removed"));
|
|
22
|
+
const result = queue.catch(() => {}).then(operation).catch(error => { throw nativeError(error); });
|
|
23
|
+
queue = result; return result;
|
|
24
|
+
}
|
|
25
|
+
const read = () => enqueue(async () => {
|
|
26
|
+
const status = await backend.getStatus();
|
|
27
|
+
if (!validAudioStatus(status)) throw new SparkError("E_INVALID_DATA", "Invalid audio status");
|
|
28
|
+
return status;
|
|
29
|
+
});
|
|
30
|
+
const listeners = statusListeners(listener => backend.addStatusListener(status => {
|
|
31
|
+
if (!validAudioStatus(status)) { console.error(new SparkError("E_INVALID_DATA", "Invalid audio status")); return; }
|
|
32
|
+
listener(status);
|
|
33
|
+
}));
|
|
34
|
+
return {
|
|
35
|
+
async setVolume(volume) { validateVolume(volume); await enqueue(() => backend.setVolume(volume)); },
|
|
36
|
+
async setMetadata(metadata) {
|
|
37
|
+
if (metadata !== null) validateMetadata(metadata);
|
|
38
|
+
const snapshot = metadata === null ? null : { ...metadata };
|
|
39
|
+
await enqueue(() => backend.setMetadata(snapshot));
|
|
40
|
+
},
|
|
41
|
+
addListener(event, listener) {
|
|
42
|
+
alive();
|
|
43
|
+
if (event !== "playbackStatusUpdate" || typeof listener !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected a playbackStatusUpdate listener");
|
|
44
|
+
return listeners.add(listener);
|
|
45
|
+
},
|
|
46
|
+
play: () => enqueue(() => backend.play()),
|
|
47
|
+
pause: () => enqueue(() => backend.pause()),
|
|
48
|
+
async seekTo(seconds) { validateTime(seconds); await enqueue(() => backend.seekTo(seconds)); },
|
|
49
|
+
getStatus: read,
|
|
50
|
+
remove() {
|
|
51
|
+
if (removed) return Promise.resolve();
|
|
52
|
+
if (removal) return removal;
|
|
53
|
+
closing = true; listeners.close();
|
|
54
|
+
removal = queue.catch(() => {}).then(() => backend.remove()).then(() => { removed = true; }).catch(error => { throw nativeError(error); }).finally(() => { removal = undefined; });
|
|
55
|
+
return removal;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function validatePlayerOptions(options: AudioPlayerOptions) {
|
|
60
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected audio player options");
|
|
61
|
+
for (const key of Object.keys(options)) if (!["loadTimeoutMs", "signal"].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported audio option: ${key}`);
|
|
62
|
+
if (options.loadTimeoutMs !== undefined && (!Number.isFinite(options.loadTimeoutMs) || options.loadTimeoutMs <= 0 || options.loadTimeoutMs > 2147483647)) throw new SparkError("E_INVALID_ARGUMENT", "loadTimeoutMs must be a positive timer duration");
|
|
63
|
+
if (options.signal !== undefined && (!options.signal || typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function" || typeof options.signal.removeEventListener !== "function")) throw new SparkError("E_INVALID_ARGUMENT", "Expected an AbortSignal");
|
|
64
|
+
if (options.signal?.aborted) throw new SparkError("E_ABORTED", "Audio loading aborted");
|
|
65
|
+
}
|
|
66
|
+
/** Loading deadline begins after allocation. Wait on a native readiness promise or
|
|
67
|
+
* existing backend events; allocation itself is not timed out. */
|
|
68
|
+
export async function waitForAudio(ready: () => Promise<boolean>, options: AudioPlayerOptions, subscribe?: (notify: () => void) => ReturnType<AudioPlayer["addListener"]>): Promise<void> {
|
|
69
|
+
let stopped = false;
|
|
70
|
+
let deadline: ReturnType<typeof setTimeout> | undefined;
|
|
71
|
+
let wake: (() => void) | undefined;
|
|
72
|
+
let changed = false, subscription: ReturnType<AudioPlayer["addListener"]> | undefined;
|
|
73
|
+
let abort = () => {};
|
|
74
|
+
try {
|
|
75
|
+
subscription = subscribe?.(() => { changed = true; wake?.(); });
|
|
76
|
+
await Promise.race([
|
|
77
|
+
new Promise<never>((_, reject) => {
|
|
78
|
+
abort = () => reject(new SparkError("E_ABORTED", "Audio loading aborted"));
|
|
79
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
80
|
+
if (options.signal?.aborted) abort();
|
|
81
|
+
deadline = setTimeout(() => reject(new SparkError("E_TIMEOUT", "Audio loading timed out")), options.loadTimeoutMs ?? 15000);
|
|
82
|
+
}),
|
|
83
|
+
(async () => {
|
|
84
|
+
while (!stopped) {
|
|
85
|
+
changed = false;
|
|
86
|
+
if (await ready()) return;
|
|
87
|
+
if (!subscribe) throw new SparkError("E_INVALID_DATA", "Native audio readiness did not acknowledge loading");
|
|
88
|
+
if (!stopped && !changed) await new Promise<void>(resolve => {
|
|
89
|
+
wake = resolve;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
})(),
|
|
93
|
+
]);
|
|
94
|
+
} finally {
|
|
95
|
+
stopped = true; clearTimeout(deadline); wake?.(); subscription?.remove(); options.signal?.removeEventListener("abort", abort);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,22 +1,28 @@
|
|
|
1
|
+
import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
|
|
1
2
|
import type { AudioStatus } from "./types";
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
import type { Subscription } from "@legendapp/spark-desktop-app/src/contracts";
|
|
4
|
+
/** Share one backend event stream without sampling reads or React state. */
|
|
5
|
+
export function statusListeners(subscribe: (listener: (status: AudioStatus) => void) => Subscription) {
|
|
4
6
|
const listeners = new Set<(status: AudioStatus) => void>();
|
|
5
|
-
let
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} catch (error) { if (!closed) console.error(error); }
|
|
12
|
-
finally { running = false; if (!closed && listeners.size) timer = setTimeout(tick, 250); }
|
|
13
|
-
}
|
|
7
|
+
let closed = false, starting = false;
|
|
8
|
+
let subscription: Subscription | undefined;
|
|
9
|
+
const deliver = (status: AudioStatus) => {
|
|
10
|
+
// Snapshot preserves delivery order when callbacks add/remove subscriptions.
|
|
11
|
+
if (!closed) for (const listener of [...listeners]) { try { if (!closed && listeners.has(listener)) listener(status); } catch (error) { console.error(error); } }
|
|
12
|
+
};
|
|
14
13
|
return {
|
|
15
14
|
add(listener: (status: AudioStatus) => void) {
|
|
16
|
-
if (closed) throw new
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
if (closed) throw new SparkError("E_CLOSED", "Audio player has been removed");
|
|
16
|
+
const entry = (status: AudioStatus) => listener(status);
|
|
17
|
+
listeners.add(entry);
|
|
18
|
+
if (!subscription && !starting) try {
|
|
19
|
+
starting = true;
|
|
20
|
+
const value = subscribe(deliver);
|
|
21
|
+
if (closed || !listeners.size) value.remove(); else subscription = value;
|
|
22
|
+
} catch (error) { listeners.delete(entry); throw error; }
|
|
23
|
+
finally { starting = false; }
|
|
24
|
+
return { remove() { listeners.delete(entry); if (!listeners.size) { subscription?.remove(); subscription = undefined; } } };
|
|
19
25
|
},
|
|
20
|
-
close() { closed = true;
|
|
26
|
+
close() { closed = true; listeners.clear(); subscription?.remove(); subscription = undefined; },
|
|
21
27
|
};
|
|
22
28
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { SparkError, type Subscription } from "@legendapp/spark-desktop-app/src/contracts";
|
|
2
|
+
export type { Subscription } from "@legendapp/spark-desktop-app/src/contracts";
|
|
1
3
|
import type { AudioMetadata } from "./media-types";
|
|
2
4
|
export * from "./media-types";
|
|
3
5
|
/** Seconds throughout. Commands reject when the backend cannot complete them. */
|
|
4
6
|
export type AudioStatus = { playing: boolean; currentTime: number; duration: number; didJustFinish: boolean; error: string | null; volume: number };
|
|
5
7
|
export type AudioSource = { uri: string; title?: string };
|
|
8
|
+
export interface AudioPlayerOptions { loadTimeoutMs?: number; signal?: AbortSignal }
|
|
6
9
|
export interface AudioPlayer {
|
|
7
10
|
setVolume(volume: number): Promise<void>;
|
|
8
|
-
setMetadata(metadata: AudioMetadata): Promise<void>;
|
|
9
|
-
addListener(event: "playbackStatusUpdate", listener: (status: AudioStatus) => void):
|
|
11
|
+
setMetadata(metadata: AudioMetadata | null): Promise<void>;
|
|
12
|
+
addListener(event: "playbackStatusUpdate", listener: (status: AudioStatus) => void): Subscription;
|
|
10
13
|
play(): Promise<void>;
|
|
11
14
|
pause(): Promise<void>;
|
|
12
15
|
seekTo(seconds: number): Promise<void>;
|
|
@@ -14,8 +17,10 @@ export interface AudioPlayer {
|
|
|
14
17
|
remove(): Promise<void>;
|
|
15
18
|
}
|
|
16
19
|
export function validateSource(source: AudioSource) {
|
|
17
|
-
if (!source || typeof source.uri !== "string" || !source.uri || source.uri.includes("\0")) throw new
|
|
20
|
+
if (!source || typeof source !== "object" || Array.isArray(source) || typeof source.uri !== "string" || !source.uri || source.uri.includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", "Audio requires a URI or absolute desktop file path");
|
|
21
|
+
for (const key of Object.keys(source)) if (!["uri", "title"].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported audio source option: ${key}`);
|
|
22
|
+
if (source.title !== undefined && typeof source.title !== "string") throw new SparkError("E_INVALID_ARGUMENT", "Audio title must be a string");
|
|
18
23
|
}
|
|
19
24
|
export function validateTime(seconds: number) {
|
|
20
|
-
if (!Number.isFinite(seconds) || seconds < 0) throw new
|
|
25
|
+
if (!Number.isFinite(seconds) || seconds < 0) throw new SparkError("E_INVALID_ARGUMENT", "Seek position must be a finite, nonnegative number of seconds");
|
|
21
26
|
}
|