@pictor-sdk/video-android 0.1.0 → 0.1.4
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/android/build.gradle +36 -9
- package/android/consumer-rules.pro +47 -0
- package/android/src/main/AndroidManifest.xml +15 -1
- package/android/src/main/cpp/pictor_videosurface.cpp +88 -8
- package/android/src/main/java/dev/pictor/video/AudioMixer.kt +39 -4
- package/android/src/main/java/dev/pictor/video/BoundedWait.kt +89 -0
- package/android/src/main/java/dev/pictor/video/CadenceShaderProgram.kt +29 -0
- package/android/src/main/java/dev/pictor/video/CompositionSeek.kt +8 -0
- package/android/src/main/java/dev/pictor/video/CompositionSpike.kt +23 -13
- package/android/src/main/java/dev/pictor/video/CompositionVideoItem.kt +16 -0
- package/android/src/main/java/dev/pictor/video/CompositionWindow.kt +17 -0
- package/android/src/main/java/dev/pictor/video/ExportGate.kt +421 -44
- package/android/src/main/java/dev/pictor/video/FrameClock.kt +6 -1
- package/android/src/main/java/dev/pictor/video/GateLadder.kt +405 -0
- package/android/src/main/java/dev/pictor/video/MediaStoreSave.kt +216 -9
- package/android/src/main/java/dev/pictor/video/PictorVideoModule.kt +13 -0
- package/android/src/main/java/dev/pictor/video/RunRegistry.kt +108 -0
- package/android/src/main/java/dev/pictor/video/SkiaPassThroughEffect.kt +4 -0
- package/dist/index.d.mts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/plugin/gradleProperties.js +9 -6
- package/src/NativePictorVideo.ts +11 -0
- package/src/compositionSpike.ts +9 -0
- package/src/exporter.ts +41 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pictor-sdk/video-android",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Optional Android video playback and export adapter for Pictor. Requires Android API 29+.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"react": ">=18.0.0",
|
|
30
30
|
"@expo/config-plugins": ">=54.0.0",
|
|
31
31
|
"react-native-reanimated": ">=3.16.0",
|
|
32
|
-
"@pictor-sdk/video-core": "^0.1.
|
|
32
|
+
"@pictor-sdk/video-core": "^0.1.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"react-native": "^0.81.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@types/react": "^19.0.0",
|
|
41
41
|
"@expo/config-plugins": "^54.0.5",
|
|
42
42
|
"react-native-reanimated": "^4.1.0",
|
|
43
|
-
"@pictor-sdk/video-core": "0.1.
|
|
43
|
+
"@pictor-sdk/video-core": "0.1.1"
|
|
44
44
|
},
|
|
45
45
|
"codegenConfig": {
|
|
46
46
|
"name": "PictorVideoSpec",
|
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* The oldest Android the adapter
|
|
14
|
+
* The oldest Android the adapter builds against.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* 24, matching `android/build.gradle` — the two have to agree or a prebuild
|
|
17
|
+
* writes a floor the library then rejects. React Native needs 24 and Media3
|
|
18
|
+
* 1.9.4 declares 23, so nothing below this is reachable anyway.
|
|
19
|
+
*
|
|
20
|
+
* This is a *build* floor. Whether 24..28 actually works is a separate claim,
|
|
21
|
+
* and `docs/research/min-sdk-24.md` records how far it has been taken. A host
|
|
22
|
+
* that wants a higher floor passes one: this raises, never lowers.
|
|
20
23
|
*/
|
|
21
|
-
const MIN_SDK_VERSION =
|
|
24
|
+
const MIN_SDK_VERSION = 24
|
|
22
25
|
|
|
23
26
|
const MIN_SDK_KEY = 'android.minSdkVersion'
|
|
24
27
|
|
package/src/NativePictorVideo.ts
CHANGED
|
@@ -469,6 +469,17 @@ export interface Spec extends TurboModule {
|
|
|
469
469
|
exportGateHoldMediaCopy(enabled: boolean): string
|
|
470
470
|
/** Gate-only: wedge the cadence program after N frames. -1 is off. @experimental */
|
|
471
471
|
exportGateStallAfterFrames(frames: number): string
|
|
472
|
+
/**
|
|
473
|
+
* Gate-only: the smallest failing Media3 export, one stage at a time.
|
|
474
|
+
*
|
|
475
|
+
* Takes a JSON spec — uri, source span in microseconds, the configured speed
|
|
476
|
+
* and the emission plan `resampleClip` compiled — and runs a plain
|
|
477
|
+
* Transformer, then the same run with speed mapping, cadence and Skia added
|
|
478
|
+
* individually. Starts in the background and returns immediately; each rung
|
|
479
|
+
* and the whole ladder are reported to logcat under `PictorLadderRung` and
|
|
480
|
+
* `PictorLadder`. @experimental
|
|
481
|
+
*/
|
|
482
|
+
exportGateLadder(specJson: string): string
|
|
472
483
|
/**
|
|
473
484
|
* Arm the gate-only controls. Everything gate-only is inert until this is
|
|
474
485
|
* called, and the production exporter never calls it. @experimental
|
package/src/compositionSpike.ts
CHANGED
|
@@ -34,6 +34,10 @@ export interface CompositionSpikeItem {
|
|
|
34
34
|
/** Clipping bounds in **source** time, which is where speed is applied. */
|
|
35
35
|
startUs: number
|
|
36
36
|
endUs: number
|
|
37
|
+
/** Full source duration, before trimming or speed. Omit only for legacy callers. */
|
|
38
|
+
sourceDurationUs?: number
|
|
39
|
+
/** Exclusive project-time end from the playlist compiler, after trim and speed. */
|
|
40
|
+
timelineEndUs?: number
|
|
37
41
|
speed: number
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -122,6 +126,8 @@ export interface CompositionSpikeState {
|
|
|
122
126
|
playbackState?: number
|
|
123
127
|
durationUs?: number
|
|
124
128
|
presentedUs?: number | null
|
|
129
|
+
/** Playlist window belonging to the latched frame, not the current playhead. */
|
|
130
|
+
presentedWindowIndex?: number
|
|
125
131
|
/** How old the presented frame was when it was read, so a stalled sample can be excluded. */
|
|
126
132
|
presentedAgeNs?: number
|
|
127
133
|
/** How long the paired read itself took. Bounds the pairing error. */
|
|
@@ -148,6 +154,7 @@ export function compositionSpikeSpec(
|
|
|
148
154
|
const playlist = playlistOf(project)
|
|
149
155
|
const items: CompositionSpikeItem[] = []
|
|
150
156
|
const unsupported = playlist.unsupported.map((u) => `${u.clipId}: ${u.reason}`)
|
|
157
|
+
const clips = new Map(project.clips.map((clip) => [clip.id, clip]))
|
|
151
158
|
for (const item of playlist.items) {
|
|
152
159
|
const uri = resolve(item.source as unknown as { kind: string; uri?: string })
|
|
153
160
|
if (uri === null) {
|
|
@@ -158,6 +165,8 @@ export function compositionSpikeSpec(
|
|
|
158
165
|
uri,
|
|
159
166
|
startUs: Number(item.startUs),
|
|
160
167
|
endUs: Number(item.endUs),
|
|
168
|
+
sourceDurationUs: clips.get(item.clipId)?.metadata.duration,
|
|
169
|
+
timelineEndUs: Number(item.timelineEndUs),
|
|
161
170
|
speed: item.speed,
|
|
162
171
|
})
|
|
163
172
|
}
|
package/src/exporter.ts
CHANGED
|
@@ -169,6 +169,27 @@ export interface Media3ExporterOptions {
|
|
|
169
169
|
* Native counting is unthrottled regardless; this is only the bridge rate.
|
|
170
170
|
*/
|
|
171
171
|
progressPollMs?: number
|
|
172
|
+
/**
|
|
173
|
+
* Which mechanism re-times a clip's **video** when its speed is not 1.
|
|
174
|
+
*
|
|
175
|
+
* `'cadence'` (the default, and what has shipped) compiles an exact emit plan
|
|
176
|
+
* in `resampleClip` and a GL effect drops or repeats frames to match it. The
|
|
177
|
+
* plan's length is the clip's length by construction, so project time is
|
|
178
|
+
* exact — and above 1× the decoder still reads every source frame, which is
|
|
179
|
+
* what produces a burst of output followed immediately by end-of-stream.
|
|
180
|
+
*
|
|
181
|
+
* `'media3'` sends no plan, so no cadence effect is built and
|
|
182
|
+
* `EditedMediaItem.setSpeed` carries the video timing on its own.
|
|
183
|
+
*
|
|
184
|
+
* Exposed because the burst is implicated in a vendor encoder that drops its
|
|
185
|
+
* output end-of-stream at high rates — see
|
|
186
|
+
* `docs/research/export-speed-stall.md`. It is a comparison switch, not a
|
|
187
|
+
* recommendation: the two paths select different frames, and which one is
|
|
188
|
+
* correct is what the comparison is for.
|
|
189
|
+
*
|
|
190
|
+
* @experimental
|
|
191
|
+
*/
|
|
192
|
+
videoSpeedVia?: 'cadence' | 'media3'
|
|
172
193
|
}
|
|
173
194
|
|
|
174
195
|
/** Why an export will not start. @experimental */
|
|
@@ -357,9 +378,18 @@ export function createMedia3VideoExporter(options: Media3ExporterOptions): Video
|
|
|
357
378
|
}
|
|
358
379
|
const resources = overlayPlan.resources
|
|
359
380
|
|
|
381
|
+
/*
|
|
382
|
+
Whether a re-timed clip gets an emit plan at all.
|
|
383
|
+
|
|
384
|
+
Omitting it is how the cadence effect is left out: native builds one
|
|
385
|
+
only when both arrays arrive, so there is no second switch to keep in
|
|
386
|
+
step. A clip at 1× has nothing to re-time either way.
|
|
387
|
+
*/
|
|
388
|
+
const viaMedia3 = options.videoSpeedVia === 'media3'
|
|
360
389
|
const shards = plan.map((shard, index) => {
|
|
361
390
|
const clip = playable[index]!
|
|
362
|
-
const cadence =
|
|
391
|
+
const cadence =
|
|
392
|
+
viaMedia3 && clip.speed !== 1 ? null : cadenceFor(shard.plan.frames)
|
|
363
393
|
return {
|
|
364
394
|
uri: uris[index]!,
|
|
365
395
|
startMs: Math.round(clip.sourceIn / 1000),
|
|
@@ -378,11 +408,19 @@ export function createMedia3VideoExporter(options: Media3ExporterOptions): Video
|
|
|
378
408
|
What matters is that the *source's* audio never enters this pipeline:
|
|
379
409
|
a per-shard audio track has to be re-encoded to concatenate, which is
|
|
380
410
|
what put a second generation on everything the user hears.
|
|
411
|
+
|
|
412
|
+
`ExportGate` notes that `setRemoveAudio` beside a speed change is a
|
|
413
|
+
combination Media3 warns about, and that was tried as a candidate
|
|
414
|
+
fix for the high-rate export stall: giving the re-timing the real
|
|
415
|
+
stream instead of silence. It made no difference to the fault — the
|
|
416
|
+
encoder stopped at exactly the same place — so the change was
|
|
417
|
+
reverted rather than kept for a benefit that could not be shown.
|
|
418
|
+
See `docs/research/export-speed-stall.md`.
|
|
381
419
|
*/
|
|
382
420
|
forceAudioTrack: true,
|
|
383
421
|
removeAudio: true,
|
|
384
|
-
emitsPerInput: cadence
|
|
385
|
-
outputPtsUs: cadence
|
|
422
|
+
emitsPerInput: cadence?.emitsPerInput,
|
|
423
|
+
outputPtsUs: cadence?.outputPtsUs,
|
|
386
424
|
spanUs: Math.round((shard.plan.frameCount * 1_000_000) / fps),
|
|
387
425
|
}
|
|
388
426
|
})
|