@pictor-sdk/video-android 0.1.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/LICENSE +21 -0
- package/README.md +40 -0
- package/android/CMakeLists.txt +30 -0
- package/android/build.gradle +119 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/pictor_overlaystage.cpp +372 -0
- package/android/src/main/cpp/pictor_publish.cpp +87 -0
- package/android/src/main/cpp/pictor_videosurface.cpp +735 -0
- package/android/src/main/java/dev/pictor/video/AacPriming.kt +261 -0
- package/android/src/main/java/dev/pictor/video/AudioMixer.kt +1112 -0
- package/android/src/main/java/dev/pictor/video/AudioProbe.kt +162 -0
- package/android/src/main/java/dev/pictor/video/CadenceShaderProgram.kt +337 -0
- package/android/src/main/java/dev/pictor/video/CodecLedger.kt +126 -0
- package/android/src/main/java/dev/pictor/video/CompositionSpike.kt +978 -0
- package/android/src/main/java/dev/pictor/video/Diagnostics.kt +54 -0
- package/android/src/main/java/dev/pictor/video/EncoderGate.kt +476 -0
- package/android/src/main/java/dev/pictor/video/ExportGate.kt +1974 -0
- package/android/src/main/java/dev/pictor/video/ExportHeartbeat.kt +72 -0
- package/android/src/main/java/dev/pictor/video/ExportProgress.kt +103 -0
- package/android/src/main/java/dev/pictor/video/FrameClock.kt +591 -0
- package/android/src/main/java/dev/pictor/video/FramePending.kt +181 -0
- package/android/src/main/java/dev/pictor/video/GateAudio.kt +456 -0
- package/android/src/main/java/dev/pictor/video/GateDestination.kt +445 -0
- package/android/src/main/java/dev/pictor/video/GateFrames.kt +662 -0
- package/android/src/main/java/dev/pictor/video/GateMux.kt +185 -0
- package/android/src/main/java/dev/pictor/video/ManagedSources.kt +333 -0
- package/android/src/main/java/dev/pictor/video/Media3Player.kt +934 -0
- package/android/src/main/java/dev/pictor/video/MediaStoreSave.kt +419 -0
- package/android/src/main/java/dev/pictor/video/OverlayResources.kt +169 -0
- package/android/src/main/java/dev/pictor/video/PictorVideoModule.kt +1612 -0
- package/android/src/main/java/dev/pictor/video/PictorVideoPackage.kt +26 -0
- package/android/src/main/java/dev/pictor/video/PlaybackAnchor.kt +161 -0
- package/android/src/main/java/dev/pictor/video/PreviewAudioPlayer.kt +291 -0
- package/android/src/main/java/dev/pictor/video/PreviewConfig.kt +96 -0
- package/android/src/main/java/dev/pictor/video/PreviewMix.kt +396 -0
- package/android/src/main/java/dev/pictor/video/PreviewSync.kt +374 -0
- package/android/src/main/java/dev/pictor/video/PreviewTrace.kt +478 -0
- package/android/src/main/java/dev/pictor/video/PrimingTrim.kt +337 -0
- package/android/src/main/java/dev/pictor/video/RepeatingEncoderFactory.kt +97 -0
- package/android/src/main/java/dev/pictor/video/SceneProgram.kt +169 -0
- package/android/src/main/java/dev/pictor/video/SceneProgramNative.kt +299 -0
- package/android/src/main/java/dev/pictor/video/SkiaPassThroughEffect.kt +695 -0
- package/android/src/main/java/dev/pictor/video/SurfaceAttachment.kt +110 -0
- package/android/src/main/java/dev/pictor/video/ThumbnailDecoder.kt +171 -0
- package/android/src/main/java/dev/pictor/video/VideoSurface.kt +394 -0
- package/app.plugin.js +43 -0
- package/dist/index.d.mts +3622 -0
- package/dist/index.d.ts +3622 -0
- package/dist/index.js +3737 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3625 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +82 -0
- package/plugin/gradleProperties.d.ts +19 -0
- package/plugin/gradleProperties.js +64 -0
- package/react-native.config.js +13 -0
- package/src/NativePictorVideo.ts +574 -0
- package/src/audioProbe.ts +72 -0
- package/src/compositionSpike.ts +305 -0
- package/src/convergence.ts +112 -0
- package/src/encoderGate.ts +66 -0
- package/src/exportDestination.ts +149 -0
- package/src/exportJob.ts +232 -0
- package/src/exportProgress.ts +391 -0
- package/src/exportProgressPump.ts +190 -0
- package/src/exporter.ts +655 -0
- package/src/index.ts +276 -0
- package/src/managedSources.ts +252 -0
- package/src/media3.ts +412 -0
- package/src/mediaStore.ts +177 -0
- package/src/memoryGuard.ts +118 -0
- package/src/mixSources.ts +82 -0
- package/src/overlayStaging.ts +216 -0
- package/src/playbackLifecycle.ts +137 -0
- package/src/player.ts +39 -0
- package/src/playlistSession.ts +125 -0
- package/src/previewAudioDiagnostics.ts +67 -0
- package/src/previewAudioSession.ts +168 -0
- package/src/previewCadence.ts +950 -0
- package/src/previewMix.ts +212 -0
- package/src/previewTrace.ts +348 -0
- package/src/spikeExcursions.ts +405 -0
- package/src/submissionScoring.ts +680 -0
- package/src/surfaceOwner.ts +264 -0
- package/src/surfaceRecovery.ts +121 -0
- package/src/syncTrace.ts +189 -0
- package/src/thumbnailProvider.ts +163 -0
- package/src/tracePhases.ts +217 -0
- package/src/useCompositionPlayback.ts +327 -0
- package/src/useMedia3Playback.ts +492 -0
- package/src/usePreviewAudio.ts +305 -0
- package/src/useVideoSurface.ts +282 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@pictor-sdk/video-android` — optional Android video playback adapter.
|
|
3
|
+
*
|
|
4
|
+
* Playback is Media3's ExoPlayer rendering into a persistent external-OES
|
|
5
|
+
* `SurfaceTexture`; the renderer keeps one texture and one `SkImage` for the
|
|
6
|
+
* whole session and composes them with the rest of the document in Skia. No
|
|
7
|
+
* frame crosses the JS thread.
|
|
8
|
+
*
|
|
9
|
+
* **Requires Android API 29 or higher**, which is conservative: the surviving
|
|
10
|
+
* path needs 24, and 29 is held only until something older than the one device
|
|
11
|
+
* here can actually run it. Installing this package raises a host's
|
|
12
|
+
* `minSdkVersion`; not installing it leaves image-only consumers exactly where
|
|
13
|
+
* they were.
|
|
14
|
+
*
|
|
15
|
+
* The surface here is **internal** until the Pixel 7 measurements validate it.
|
|
16
|
+
* Nothing is re-exported from a package a consumer installs by default.
|
|
17
|
+
*
|
|
18
|
+
* @experimental
|
|
19
|
+
*/
|
|
20
|
+
export {
|
|
21
|
+
createMemoryGuard,
|
|
22
|
+
describeTrip,
|
|
23
|
+
observeMemory,
|
|
24
|
+
DEFAULT_CEILING_BYTES,
|
|
25
|
+
DEFAULT_RISE_BYTES,
|
|
26
|
+
DEFAULT_WINDOW_SAMPLES,
|
|
27
|
+
type MemoryGuard,
|
|
28
|
+
} from './memoryGuard'
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
createSyncTrace,
|
|
32
|
+
distribution,
|
|
33
|
+
expectedPtsAt,
|
|
34
|
+
record as recordTick,
|
|
35
|
+
syncReport,
|
|
36
|
+
type Distribution,
|
|
37
|
+
type SyncReport,
|
|
38
|
+
type SyncTrace,
|
|
39
|
+
type TickSample,
|
|
40
|
+
} from './syncTrace'
|
|
41
|
+
|
|
42
|
+
export { AndroidVideoPlayer } from './player'
|
|
43
|
+
|
|
44
|
+
export { Media3, type Media3State } from './media3'
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
NO_SURFACE,
|
|
48
|
+
afterPrepare,
|
|
49
|
+
idlePlaylistSession,
|
|
50
|
+
isCurrentGeneration,
|
|
51
|
+
retireSurface,
|
|
52
|
+
shouldPrepare,
|
|
53
|
+
type PlaylistIntent,
|
|
54
|
+
type PlaylistSession,
|
|
55
|
+
} from './playlistSession'
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
createSurfaceOwner,
|
|
59
|
+
nextOwnerToken,
|
|
60
|
+
resetSurfaceOwnerForTests,
|
|
61
|
+
surfaceOwner,
|
|
62
|
+
type SurfaceCreateResult,
|
|
63
|
+
type SurfaceOwner,
|
|
64
|
+
type SurfaceOwnerEffects,
|
|
65
|
+
type SurfaceReadiness,
|
|
66
|
+
} from './surfaceOwner'
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
useVideoSurface,
|
|
70
|
+
videoSurfaceSlot,
|
|
71
|
+
type VideoSurface,
|
|
72
|
+
type VideoSurfaceHandle,
|
|
73
|
+
type VideoSurfaceOptions,
|
|
74
|
+
} from './useVideoSurface'
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
useCompositionPlayback,
|
|
78
|
+
type CompositionPlayback,
|
|
79
|
+
type CompositionPlaybackState,
|
|
80
|
+
type UseCompositionPlaybackOptions,
|
|
81
|
+
} from './useCompositionPlayback'
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
useMedia3Playback,
|
|
85
|
+
type Media3Playback,
|
|
86
|
+
type UseMedia3PlaybackOptions,
|
|
87
|
+
} from './useMedia3Playback'
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
createAndroidThumbnailProvider,
|
|
91
|
+
thumbnailCounters,
|
|
92
|
+
type AndroidThumbnailOptions,
|
|
93
|
+
} from './thumbnailProvider'
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
beginPhase,
|
|
97
|
+
observe,
|
|
98
|
+
recoveryFrames,
|
|
99
|
+
CONVERGED_RUN,
|
|
100
|
+
MAX_RECOVERY_SAMPLES,
|
|
101
|
+
type Phase,
|
|
102
|
+
type PhaseState,
|
|
103
|
+
} from './convergence'
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
createMedia3VideoExporter,
|
|
107
|
+
VideoExportRejected,
|
|
108
|
+
type Media3ExporterOptions,
|
|
109
|
+
type OverlayPreparationResult,
|
|
110
|
+
type OverlayResourceDescriptor,
|
|
111
|
+
} from './exporter'
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
createPlaybackLifecycle,
|
|
115
|
+
type PlaybackLifecycle,
|
|
116
|
+
type PlaybackLifecycleEffects,
|
|
117
|
+
type PlaybackStopReason,
|
|
118
|
+
} from './playbackLifecycle'
|
|
119
|
+
export {
|
|
120
|
+
RECOVERY_GRACE_MS,
|
|
121
|
+
classifyStall,
|
|
122
|
+
shouldRecover,
|
|
123
|
+
type RecoverySample,
|
|
124
|
+
type StallKind,
|
|
125
|
+
} from './surfaceRecovery'
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
EXPORT_STAGES,
|
|
129
|
+
createExportJobs,
|
|
130
|
+
type ExportJob,
|
|
131
|
+
type ExportJobs,
|
|
132
|
+
type ExportJobsOptions,
|
|
133
|
+
type ExportStage,
|
|
134
|
+
} from './exportJob'
|
|
135
|
+
|
|
136
|
+
export {
|
|
137
|
+
MAX_OBSERVED_HEARTBEAT_GAP_MS,
|
|
138
|
+
STALL_GRACE_MS,
|
|
139
|
+
createProgressReporter,
|
|
140
|
+
createStallWatchdog,
|
|
141
|
+
planProgressWeights,
|
|
142
|
+
type ProgressPlan,
|
|
143
|
+
type ProgressPlanInput,
|
|
144
|
+
type ProgressReporter,
|
|
145
|
+
type ProgressReporterOptions,
|
|
146
|
+
type ShardPlan,
|
|
147
|
+
type StallVerdict,
|
|
148
|
+
type StallWatchdog,
|
|
149
|
+
type StallWatchdogOptions,
|
|
150
|
+
} from './exportProgress'
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
createMediaStoreSaver,
|
|
154
|
+
shareSavedMedia,
|
|
155
|
+
sweepPendingMediaSaves,
|
|
156
|
+
MediaSaveCancelled,
|
|
157
|
+
MediaSaveFailed,
|
|
158
|
+
} from './mediaStore'
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
createOverlayStagingSink,
|
|
162
|
+
installOverlayStaging,
|
|
163
|
+
measureStagingStall,
|
|
164
|
+
sweepOverlayStaging,
|
|
165
|
+
type OverlayStagingSink,
|
|
166
|
+
type StagedRaster,
|
|
167
|
+
} from './overlayStaging'
|
|
168
|
+
export { probeAudioSource } from './audioProbe'
|
|
169
|
+
export type { AudioProbeFailure, AudioProbeResult } from './audioProbe'
|
|
170
|
+
export {
|
|
171
|
+
ingestManagedSource,
|
|
172
|
+
managedSourceLedger,
|
|
173
|
+
managedSourceList,
|
|
174
|
+
seedManagedSourceForGate,
|
|
175
|
+
sweepManagedSourcesRawForGate,
|
|
176
|
+
managedSourceToken,
|
|
177
|
+
releaseManagedSource,
|
|
178
|
+
sweepManagedSources,
|
|
179
|
+
} from './managedSources'
|
|
180
|
+
export type { IngestResult, ManagedSource, ManagedSourceFailure } from './managedSources'
|
|
181
|
+
export {
|
|
182
|
+
cancelPreviewMix,
|
|
183
|
+
preparePreviewMix,
|
|
184
|
+
previewMixKey,
|
|
185
|
+
previewMixLedger,
|
|
186
|
+
releasePreviewMix,
|
|
187
|
+
sweepPreviewMixes,
|
|
188
|
+
} from './previewMix'
|
|
189
|
+
export type { PreparedMix, PreviewMixResult } from './previewMix'
|
|
190
|
+
export {
|
|
191
|
+
PREVIEW_DRIFT_TOLERANCE_US,
|
|
192
|
+
decidePreviewAudio,
|
|
193
|
+
previewAudioNeeded,
|
|
194
|
+
previewProgramJson,
|
|
195
|
+
} from './previewAudioSession'
|
|
196
|
+
export type {
|
|
197
|
+
PreviewAudioDecision,
|
|
198
|
+
PreviewAudioInputs,
|
|
199
|
+
PreviewAudioStatus,
|
|
200
|
+
} from './previewAudioSession'
|
|
201
|
+
export {
|
|
202
|
+
PHASE_MARK_PREFIX,
|
|
203
|
+
summarisePhases,
|
|
204
|
+
type PhaseReport,
|
|
205
|
+
type PhaseSummary,
|
|
206
|
+
} from './tracePhases'
|
|
207
|
+
export {
|
|
208
|
+
compositionSpikeSpec,
|
|
209
|
+
spikePause,
|
|
210
|
+
spikePlay,
|
|
211
|
+
spikeRelease,
|
|
212
|
+
spikeSeek,
|
|
213
|
+
spikeClaim,
|
|
214
|
+
spikeDrain,
|
|
215
|
+
spikeStart,
|
|
216
|
+
spikeState,
|
|
217
|
+
type CompositionSpikeItem,
|
|
218
|
+
type CompositionSpikeSpec,
|
|
219
|
+
type CompositionSpikeState,
|
|
220
|
+
type SpikeSample,
|
|
221
|
+
} from './compositionSpike'
|
|
222
|
+
export {
|
|
223
|
+
classifyExcursions,
|
|
224
|
+
offsetAt,
|
|
225
|
+
windowAround,
|
|
226
|
+
SPIKE_TOLERANCE_US,
|
|
227
|
+
type Excursion,
|
|
228
|
+
type ExcursionCause,
|
|
229
|
+
type ExcursionReport,
|
|
230
|
+
} from './spikeExcursions'
|
|
231
|
+
export {
|
|
232
|
+
PREVIEW_OUTPUT_FPS,
|
|
233
|
+
PREVIEW_TOLERANCE_US,
|
|
234
|
+
checkCadence,
|
|
235
|
+
type CadenceClip,
|
|
236
|
+
type CadenceResult,
|
|
237
|
+
type Verdict,
|
|
238
|
+
} from './previewCadence'
|
|
239
|
+
export {
|
|
240
|
+
scoreSubmissions,
|
|
241
|
+
traceSeam,
|
|
242
|
+
traceStartup,
|
|
243
|
+
traceSubmission,
|
|
244
|
+
type ScoredSubmission,
|
|
245
|
+
type SubmissionRecord,
|
|
246
|
+
type SubmissionReport,
|
|
247
|
+
type SeamStage,
|
|
248
|
+
type StartupRow,
|
|
249
|
+
type StartupTrace,
|
|
250
|
+
type SeamTrace,
|
|
251
|
+
type SubmissionTrace,
|
|
252
|
+
type TraceRow,
|
|
253
|
+
} from './submissionScoring'
|
|
254
|
+
export { usePreviewAudio } from './usePreviewAudio'
|
|
255
|
+
export { previewAudioDiagnostics } from './previewAudioDiagnostics'
|
|
256
|
+
export type { PreviewAudioDiagnostics } from './previewAudioDiagnostics'
|
|
257
|
+
export type { PreviewAudioBinding, UsePreviewAudioOptions } from './usePreviewAudio'
|
|
258
|
+
export { mixSpanContext, resolveMixSources } from './mixSources'
|
|
259
|
+
export type { ResolvedMixSources } from './mixSources'
|
|
260
|
+
export { ENCODER_GATE_FIXTURES, runEncoderGate } from './encoderGate'
|
|
261
|
+
export type { EncoderGateResult } from './encoderGate'
|
|
262
|
+
export {
|
|
263
|
+
armPreviewTrace,
|
|
264
|
+
drainAllPreviewTrace,
|
|
265
|
+
drainPreviewTrace,
|
|
266
|
+
markPreviewTrace,
|
|
267
|
+
muteSourceAudio,
|
|
268
|
+
previewCodecLedger,
|
|
269
|
+
previewFramePipeline,
|
|
270
|
+
previewParts,
|
|
271
|
+
setPreviewParts,
|
|
272
|
+
previewSyncCounters,
|
|
273
|
+
stopPreviewForHost,
|
|
274
|
+
summariseOffsets,
|
|
275
|
+
} from './previewTrace'
|
|
276
|
+
export type { PreviewParts, TraceEvent, TracePage } from './previewTrace'
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { requireNative } from './player'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Media the app owns, copied out of wherever it was picked from.
|
|
5
|
+
*
|
|
6
|
+
* A picker returns a reference, not a file. A `content://` grant is scoped to
|
|
7
|
+
* the activity result that produced it; `copyToCacheDirectory` improves on that
|
|
8
|
+
* by writing into **cache**, which Android reclaims whenever it wants storage
|
|
9
|
+
* back. Either way a project persisted with a picker URI reopens with media
|
|
10
|
+
* that has quietly gone, and nothing says so until an export fails.
|
|
11
|
+
*
|
|
12
|
+
* So ingestion copies into `filesDir` and the project stores that. See
|
|
13
|
+
* `ManagedSources.kt` for why the name is the content hash and why nothing is
|
|
14
|
+
* ever deleted implicitly.
|
|
15
|
+
*
|
|
16
|
+
* @experimental
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Where a managed source lives, and what it is. @experimental */
|
|
20
|
+
export interface ManagedSource {
|
|
21
|
+
/** A `file://` URI in app-owned durable storage. Safe to persist. */
|
|
22
|
+
uri: string
|
|
23
|
+
/** The content hash. Names the file; never derived from the user's filename. */
|
|
24
|
+
token: string
|
|
25
|
+
byteLength: number
|
|
26
|
+
sha256: string
|
|
27
|
+
/**
|
|
28
|
+
* Whether this call brought the file into existence.
|
|
29
|
+
*
|
|
30
|
+
* False means the bytes were already stored and something else may be
|
|
31
|
+
* pointing at them — a caller unwinding its own work must not release a
|
|
32
|
+
* source it did not create.
|
|
33
|
+
*/
|
|
34
|
+
created: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @experimental */
|
|
38
|
+
export interface ManagedSourceFailure {
|
|
39
|
+
ok: false
|
|
40
|
+
/** Diagnostic. Never shown to a person: it may name a path. */
|
|
41
|
+
reason: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @experimental */
|
|
45
|
+
export type IngestResult = ({ ok: true } & ManagedSource) | ManagedSourceFailure
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Copy a picked source into durable, app-owned storage.
|
|
49
|
+
*
|
|
50
|
+
* Never throws for an unreadable source: a pick that cannot be copied is an
|
|
51
|
+
* answer. It throws only if the adapter is not installed.
|
|
52
|
+
*
|
|
53
|
+
* @experimental
|
|
54
|
+
*/
|
|
55
|
+
export function ingestManagedSource(uri: string): IngestResult {
|
|
56
|
+
return parse<IngestResult>(
|
|
57
|
+
() => requireNative().ingestManagedSource(uri),
|
|
58
|
+
(value) => ({
|
|
59
|
+
ok: true,
|
|
60
|
+
uri: String(value.uri ?? ''),
|
|
61
|
+
token: String(value.token ?? ''),
|
|
62
|
+
byteLength: Number(value.byteLength ?? 0),
|
|
63
|
+
sha256: String(value.sha256 ?? ''),
|
|
64
|
+
created: value.created === true,
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Drop one managed source.
|
|
71
|
+
*
|
|
72
|
+
* For unwinding an ingestion whose item was never created. Not for Undo: an
|
|
73
|
+
* Undo that deleted the source would leave Redo restoring an item that points
|
|
74
|
+
* at nothing.
|
|
75
|
+
*
|
|
76
|
+
* @experimental
|
|
77
|
+
*/
|
|
78
|
+
export function releaseManagedSource(token: string): boolean {
|
|
79
|
+
try {
|
|
80
|
+
const value = JSON.parse(requireNative().releaseManagedSource(token)) as { removed?: boolean }
|
|
81
|
+
return value.removed === true
|
|
82
|
+
} catch {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Remove every managed source not named in `keepTokens`.
|
|
89
|
+
*
|
|
90
|
+
* The only thing that deletes media, and it is always given the full set of
|
|
91
|
+
* what the project still references. Also removes partials a killed process
|
|
92
|
+
* left behind.
|
|
93
|
+
*
|
|
94
|
+
* @experimental
|
|
95
|
+
*/
|
|
96
|
+
export function sweepManagedSources(keepTokens: readonly string[]): {
|
|
97
|
+
removed: number
|
|
98
|
+
kept: number
|
|
99
|
+
partials: number
|
|
100
|
+
/** Files that are not ours, counted so the accounting matches a listing. */
|
|
101
|
+
skipped: number
|
|
102
|
+
} {
|
|
103
|
+
try {
|
|
104
|
+
const value = JSON.parse(
|
|
105
|
+
requireNative().sweepManagedSources(JSON.stringify([...keepTokens])),
|
|
106
|
+
) as { ok?: boolean; removed?: number; kept?: number; partials?: number; skipped?: number }
|
|
107
|
+
if (value.ok !== true) return { removed: 0, kept: 0, partials: 0, skipped: 0 }
|
|
108
|
+
return {
|
|
109
|
+
removed: Number(value.removed ?? 0),
|
|
110
|
+
kept: Number(value.kept ?? 0),
|
|
111
|
+
partials: Number(value.partials ?? 0),
|
|
112
|
+
skipped: Number(value.skipped ?? 0),
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// A sweep that cannot be understood removes nothing. The opposite default
|
|
116
|
+
// would delete a project's media on a parse error.
|
|
117
|
+
return { removed: 0, kept: 0, partials: 0, skipped: 0 }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Every filename in the managed directory, including partials and strays.
|
|
123
|
+
*
|
|
124
|
+
* Read-only accounting, so it is available in release for the same reason the
|
|
125
|
+
* ledgers are: it is how a sweep is proven rather than asserted.
|
|
126
|
+
* {@link managedSourceLedger} reports only well-formed sources, which cannot
|
|
127
|
+
* show what a sweep left behind.
|
|
128
|
+
*
|
|
129
|
+
* @experimental
|
|
130
|
+
*/
|
|
131
|
+
export function managedSourceList(): { name: string; byteLength: number }[] {
|
|
132
|
+
try {
|
|
133
|
+
const value = JSON.parse(requireNative().managedSourceList()) as {
|
|
134
|
+
items?: { name: string; byteLength: number }[]
|
|
135
|
+
}
|
|
136
|
+
return value.items ?? []
|
|
137
|
+
} catch {
|
|
138
|
+
return []
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Gate-only: put a file in the managed directory with an exact name.
|
|
144
|
+
*
|
|
145
|
+
* Ingestion only ever produces well-formed, referenced sources, so the cases a
|
|
146
|
+
* sweep exists to handle — an orphan, a stale partial, a file that is not ours
|
|
147
|
+
* — cannot be reached through the normal API. Refused in a release build built
|
|
148
|
+
* without diagnostics.
|
|
149
|
+
*
|
|
150
|
+
* @experimental
|
|
151
|
+
*/
|
|
152
|
+
export function seedManagedSourceForGate(name: string, bytes: number): boolean {
|
|
153
|
+
try {
|
|
154
|
+
const value = JSON.parse(requireNative().managedSourceGateSeed(name, bytes)) as { ok?: boolean }
|
|
155
|
+
return value.ok === true
|
|
156
|
+
} catch {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Gate-only: sweep with a keep list handed over exactly as written.
|
|
163
|
+
*
|
|
164
|
+
* The production path serialises its own list, so an unparseable one can never
|
|
165
|
+
* reach native — which makes "an unreadable keep list deletes nothing"
|
|
166
|
+
* unprovable through it. Returns what native reported.
|
|
167
|
+
*
|
|
168
|
+
* @experimental
|
|
169
|
+
*/
|
|
170
|
+
export function sweepManagedSourcesRawForGate(rawKeep: string): {
|
|
171
|
+
ok: boolean
|
|
172
|
+
removed: number
|
|
173
|
+
kept: number
|
|
174
|
+
partials: number
|
|
175
|
+
skipped: number
|
|
176
|
+
} {
|
|
177
|
+
try {
|
|
178
|
+
const value = JSON.parse(requireNative().managedSourceGateSweepRaw(rawKeep)) as {
|
|
179
|
+
ok?: boolean
|
|
180
|
+
removed?: number
|
|
181
|
+
kept?: number
|
|
182
|
+
partials?: number
|
|
183
|
+
skipped?: number
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
ok: value.ok === true,
|
|
187
|
+
removed: Number(value.removed ?? 0),
|
|
188
|
+
kept: Number(value.kept ?? 0),
|
|
189
|
+
partials: Number(value.partials ?? 0),
|
|
190
|
+
skipped: Number(value.skipped ?? 0),
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
return { ok: false, removed: 0, kept: 0, partials: 0, skipped: 0 }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** What is stored, for assertions. Tokens and sizes, never names. @experimental */
|
|
198
|
+
export function managedSourceLedger(): {
|
|
199
|
+
count: number
|
|
200
|
+
bytes: number
|
|
201
|
+
items: { token: string; byteLength: number }[]
|
|
202
|
+
} {
|
|
203
|
+
try {
|
|
204
|
+
const value = JSON.parse(requireNative().managedSourceLedger()) as {
|
|
205
|
+
count?: number
|
|
206
|
+
bytes?: number
|
|
207
|
+
items?: { token: string; byteLength: number }[]
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
count: Number(value.count ?? 0),
|
|
211
|
+
bytes: Number(value.bytes ?? 0),
|
|
212
|
+
items: value.items ?? [],
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
return { count: 0, bytes: 0, items: [] }
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The token a managed URI names, or null if it is not one of ours.
|
|
221
|
+
*
|
|
222
|
+
* Lets a host collect the keep list for {@link sweepManagedSources} straight
|
|
223
|
+
* from a project's sources without storing a second index that could drift.
|
|
224
|
+
*
|
|
225
|
+
* @experimental
|
|
226
|
+
*/
|
|
227
|
+
export function managedSourceToken(uri: string): string | null {
|
|
228
|
+
const match = /\/([0-9a-f]{64})\.bin$/.exec(uri)
|
|
229
|
+
return match ? match[1]! : null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parse<T>(
|
|
233
|
+
call: () => string,
|
|
234
|
+
build: (value: Record<string, unknown>) => T,
|
|
235
|
+
): T | ManagedSourceFailure {
|
|
236
|
+
let raw: string
|
|
237
|
+
try {
|
|
238
|
+
raw = call()
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) }
|
|
241
|
+
}
|
|
242
|
+
let value: Record<string, unknown>
|
|
243
|
+
try {
|
|
244
|
+
value = JSON.parse(raw) as Record<string, unknown>
|
|
245
|
+
} catch {
|
|
246
|
+
return { ok: false, reason: 'native returned unparseable JSON' }
|
|
247
|
+
}
|
|
248
|
+
if (value.ok !== true) {
|
|
249
|
+
return { ok: false, reason: typeof value.reason === 'string' ? value.reason : 'failed' }
|
|
250
|
+
}
|
|
251
|
+
return build(value)
|
|
252
|
+
}
|