@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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { VideoExportProgress } from '@pictor-sdk/video-core'
|
|
2
|
+
import type { ExportStage } from './exportJob'
|
|
3
|
+
import {
|
|
4
|
+
STALL_GRACE_MS,
|
|
5
|
+
createProgressReporter,
|
|
6
|
+
createStallWatchdog,
|
|
7
|
+
type ProgressPlan,
|
|
8
|
+
type StallVerdict,
|
|
9
|
+
} from './exportProgress'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reading native counters, and turning them into a progress stream.
|
|
13
|
+
*
|
|
14
|
+
* The native run is one call that resolves when the whole export is done, so
|
|
15
|
+
* there is nothing to subscribe to: progress has to be *read*. That is the whole
|
|
16
|
+
* reason this exists as a pump rather than an event stream, and it is also why
|
|
17
|
+
* the two signals stay separate on either side of the boundary.
|
|
18
|
+
*
|
|
19
|
+
* **Natively, nothing is throttled.** Every emitted frame, every block of mixed
|
|
20
|
+
* audio and every muxed sample batch increments a counter or beats, at whatever
|
|
21
|
+
* rate the encoder runs. Throttling there would be throttling the evidence.
|
|
22
|
+
*
|
|
23
|
+
* **Across the bridge, everything is throttled.** This polls at 8 Hz and the
|
|
24
|
+
* reporter emits at most that often, because nobody can read a progress bar
|
|
25
|
+
* faster and every update costs a crossing. A 0.1× export that beats a thousand
|
|
26
|
+
* times a second still produces eight callbacks.
|
|
27
|
+
*
|
|
28
|
+
* The watchdog reads the *beat total* rather than the progress fraction. A long
|
|
29
|
+
* mux moves the bar by almost nothing while beating steadily, and treating that
|
|
30
|
+
* as a stall would kill a healthy export — which is the failure mode the
|
|
31
|
+
* separation exists to prevent.
|
|
32
|
+
*
|
|
33
|
+
* @experimental
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** What native reports about work completed so far. @experimental */
|
|
37
|
+
export interface ProgressSnapshot {
|
|
38
|
+
/** Output frames written, per shard index. */
|
|
39
|
+
shards: readonly number[]
|
|
40
|
+
/** Mixed audio samples handed to the encoder. */
|
|
41
|
+
audioSamples: number
|
|
42
|
+
/** What `audioSamples` counts up to. Zero before the mix starts. */
|
|
43
|
+
audioPlanned: number
|
|
44
|
+
/** Concatenation and mux finished. */
|
|
45
|
+
muxed: boolean
|
|
46
|
+
/**
|
|
47
|
+
* Every heartbeat from every source, summed.
|
|
48
|
+
*
|
|
49
|
+
* Liveness only. It is deliberately not a work total — a mux batch and an
|
|
50
|
+
* output frame are not the same amount of anything — so it feeds the watchdog
|
|
51
|
+
* and never the bar.
|
|
52
|
+
*/
|
|
53
|
+
beats: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @experimental */
|
|
57
|
+
export interface ProgressPumpOptions {
|
|
58
|
+
plan: ProgressPlan
|
|
59
|
+
durationUs: number
|
|
60
|
+
/** Read the native counters. Called on every tick. */
|
|
61
|
+
read(): ProgressSnapshot
|
|
62
|
+
onProgress?: (progress: VideoExportProgress) => void
|
|
63
|
+
/** Called once, when nothing has reported for the threshold. */
|
|
64
|
+
onStall(verdict: StallVerdict): void
|
|
65
|
+
/** Bridge delivery rate. 125 ms is 8 Hz. */
|
|
66
|
+
pollMs?: number
|
|
67
|
+
thresholdMs?: number
|
|
68
|
+
now?: () => number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @experimental */
|
|
72
|
+
export interface ProgressPump {
|
|
73
|
+
/** Emit `{ progress: 0, time: 0 }`, once. */
|
|
74
|
+
begin(): void
|
|
75
|
+
/** Read native once and report what changed. */
|
|
76
|
+
tick(): void
|
|
77
|
+
/**
|
|
78
|
+
* The destination is closed and verified readable. Emits 1, once.
|
|
79
|
+
*
|
|
80
|
+
* Separate from the mux on purpose: a bar that fills when the encoder stops
|
|
81
|
+
* produces a "done" over a file that cannot yet be opened.
|
|
82
|
+
*/
|
|
83
|
+
complete(): void
|
|
84
|
+
/** Deliver nothing further, whatever arrives. */
|
|
85
|
+
stop(): void
|
|
86
|
+
/** How often {@link tick} expects to be called. */
|
|
87
|
+
readonly pollMs: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Where the export was when it went quiet.
|
|
92
|
+
*
|
|
93
|
+
* Derived from what has finished rather than from a native stage string, so a
|
|
94
|
+
* stall report cannot claim a stage the counters contradict.
|
|
95
|
+
*/
|
|
96
|
+
function stageOf(snapshot: ProgressSnapshot, plan: ProgressPlan): ExportStage {
|
|
97
|
+
if (snapshot.muxed) return 'publishing'
|
|
98
|
+
const shardsDone = plan.shardFrames.every(
|
|
99
|
+
(total, index) => (snapshot.shards[index] ?? 0) >= total,
|
|
100
|
+
)
|
|
101
|
+
if (!shardsDone) return 'shards'
|
|
102
|
+
if (snapshot.audioPlanned > 0 && snapshot.audioSamples < snapshot.audioPlanned) return 'audio'
|
|
103
|
+
return 'concatenating'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @experimental */
|
|
107
|
+
export function createProgressPump(options: ProgressPumpOptions): ProgressPump {
|
|
108
|
+
const now = options.now ?? (() => Date.now())
|
|
109
|
+
const pollMs = options.pollMs ?? 125
|
|
110
|
+
const plan = options.plan
|
|
111
|
+
const reporter = createProgressReporter({
|
|
112
|
+
plan,
|
|
113
|
+
durationUs: options.durationUs,
|
|
114
|
+
onProgress: options.onProgress,
|
|
115
|
+
// The reporter's own throttle is the poll interval: a tick that finds
|
|
116
|
+
// nothing new should not be able to spend a crossing saying so.
|
|
117
|
+
throttleMs: pollMs,
|
|
118
|
+
now,
|
|
119
|
+
})
|
|
120
|
+
const watchdog = createStallWatchdog({ thresholdMs: options.thresholdMs ?? STALL_GRACE_MS })
|
|
121
|
+
let lastBeats = -1
|
|
122
|
+
let stopped = false
|
|
123
|
+
let stalled = false
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
pollMs,
|
|
127
|
+
|
|
128
|
+
begin() {
|
|
129
|
+
reporter.begin()
|
|
130
|
+
// Seeded here rather than on the first tick. Without it the threshold
|
|
131
|
+
// would be measured from whenever polling happened to start, which for a
|
|
132
|
+
// slow first shard is a different moment from when work began.
|
|
133
|
+
watchdog.advanced('resolving', 0, now())
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
tick() {
|
|
137
|
+
if (stopped || stalled) return
|
|
138
|
+
const snapshot = options.read()
|
|
139
|
+
/*
|
|
140
|
+
One read of native state is one update to the bar.
|
|
141
|
+
|
|
142
|
+
Without the batch the first shard spends the throttle slot and the rest
|
|
143
|
+
— including the whole audio mix — are dropped until the next tick, so
|
|
144
|
+
the last thing to change before an export ends could never be delivered
|
|
145
|
+
at all.
|
|
146
|
+
*/
|
|
147
|
+
reporter.batch(() => {
|
|
148
|
+
for (let i = 0; i < plan.shardFrames.length; i += 1) {
|
|
149
|
+
reporter.shard(i, snapshot.shards[i] ?? 0)
|
|
150
|
+
}
|
|
151
|
+
if (snapshot.audioPlanned > 0 && plan.audioSamples > 0) {
|
|
152
|
+
/*
|
|
153
|
+
Rescaled rather than counted twice.
|
|
154
|
+
|
|
155
|
+
Native counts interleaved samples at its own rate and channel count;
|
|
156
|
+
the plan counts what the compiler predicted. Passing native's number
|
|
157
|
+
straight through would weight audio by whichever of the two happened
|
|
158
|
+
to be larger, so what crosses is the fraction.
|
|
159
|
+
*/
|
|
160
|
+
const fraction = Math.min(1, snapshot.audioSamples / snapshot.audioPlanned)
|
|
161
|
+
reporter.audio(plan.audioSamples * fraction)
|
|
162
|
+
}
|
|
163
|
+
if (snapshot.muxed) reporter.muxed()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const at = now()
|
|
167
|
+
if (snapshot.beats !== lastBeats) {
|
|
168
|
+
lastBeats = snapshot.beats
|
|
169
|
+
watchdog.advanced(stageOf(snapshot, plan), snapshot.beats, at)
|
|
170
|
+
}
|
|
171
|
+
const verdict = watchdog.stalledFor(at)
|
|
172
|
+
if (verdict) {
|
|
173
|
+
// Once. The export is torn down from here and there is nothing further
|
|
174
|
+
// to observe; continuing to poll a dead run would report it again.
|
|
175
|
+
stalled = true
|
|
176
|
+
options.onStall(verdict)
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
complete() {
|
|
181
|
+
if (stopped) return
|
|
182
|
+
reporter.complete()
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
stop() {
|
|
186
|
+
stopped = true
|
|
187
|
+
reporter.stop()
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
}
|