@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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/android/CMakeLists.txt +30 -0
  4. package/android/build.gradle +119 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/cpp/pictor_overlaystage.cpp +372 -0
  7. package/android/src/main/cpp/pictor_publish.cpp +87 -0
  8. package/android/src/main/cpp/pictor_videosurface.cpp +735 -0
  9. package/android/src/main/java/dev/pictor/video/AacPriming.kt +261 -0
  10. package/android/src/main/java/dev/pictor/video/AudioMixer.kt +1112 -0
  11. package/android/src/main/java/dev/pictor/video/AudioProbe.kt +162 -0
  12. package/android/src/main/java/dev/pictor/video/CadenceShaderProgram.kt +337 -0
  13. package/android/src/main/java/dev/pictor/video/CodecLedger.kt +126 -0
  14. package/android/src/main/java/dev/pictor/video/CompositionSpike.kt +978 -0
  15. package/android/src/main/java/dev/pictor/video/Diagnostics.kt +54 -0
  16. package/android/src/main/java/dev/pictor/video/EncoderGate.kt +476 -0
  17. package/android/src/main/java/dev/pictor/video/ExportGate.kt +1974 -0
  18. package/android/src/main/java/dev/pictor/video/ExportHeartbeat.kt +72 -0
  19. package/android/src/main/java/dev/pictor/video/ExportProgress.kt +103 -0
  20. package/android/src/main/java/dev/pictor/video/FrameClock.kt +591 -0
  21. package/android/src/main/java/dev/pictor/video/FramePending.kt +181 -0
  22. package/android/src/main/java/dev/pictor/video/GateAudio.kt +456 -0
  23. package/android/src/main/java/dev/pictor/video/GateDestination.kt +445 -0
  24. package/android/src/main/java/dev/pictor/video/GateFrames.kt +662 -0
  25. package/android/src/main/java/dev/pictor/video/GateMux.kt +185 -0
  26. package/android/src/main/java/dev/pictor/video/ManagedSources.kt +333 -0
  27. package/android/src/main/java/dev/pictor/video/Media3Player.kt +934 -0
  28. package/android/src/main/java/dev/pictor/video/MediaStoreSave.kt +419 -0
  29. package/android/src/main/java/dev/pictor/video/OverlayResources.kt +169 -0
  30. package/android/src/main/java/dev/pictor/video/PictorVideoModule.kt +1612 -0
  31. package/android/src/main/java/dev/pictor/video/PictorVideoPackage.kt +26 -0
  32. package/android/src/main/java/dev/pictor/video/PlaybackAnchor.kt +161 -0
  33. package/android/src/main/java/dev/pictor/video/PreviewAudioPlayer.kt +291 -0
  34. package/android/src/main/java/dev/pictor/video/PreviewConfig.kt +96 -0
  35. package/android/src/main/java/dev/pictor/video/PreviewMix.kt +396 -0
  36. package/android/src/main/java/dev/pictor/video/PreviewSync.kt +374 -0
  37. package/android/src/main/java/dev/pictor/video/PreviewTrace.kt +478 -0
  38. package/android/src/main/java/dev/pictor/video/PrimingTrim.kt +337 -0
  39. package/android/src/main/java/dev/pictor/video/RepeatingEncoderFactory.kt +97 -0
  40. package/android/src/main/java/dev/pictor/video/SceneProgram.kt +169 -0
  41. package/android/src/main/java/dev/pictor/video/SceneProgramNative.kt +299 -0
  42. package/android/src/main/java/dev/pictor/video/SkiaPassThroughEffect.kt +695 -0
  43. package/android/src/main/java/dev/pictor/video/SurfaceAttachment.kt +110 -0
  44. package/android/src/main/java/dev/pictor/video/ThumbnailDecoder.kt +171 -0
  45. package/android/src/main/java/dev/pictor/video/VideoSurface.kt +394 -0
  46. package/app.plugin.js +43 -0
  47. package/dist/index.d.mts +3622 -0
  48. package/dist/index.d.ts +3622 -0
  49. package/dist/index.js +3737 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/index.mjs +3625 -0
  52. package/dist/index.mjs.map +1 -0
  53. package/package.json +82 -0
  54. package/plugin/gradleProperties.d.ts +19 -0
  55. package/plugin/gradleProperties.js +64 -0
  56. package/react-native.config.js +13 -0
  57. package/src/NativePictorVideo.ts +574 -0
  58. package/src/audioProbe.ts +72 -0
  59. package/src/compositionSpike.ts +305 -0
  60. package/src/convergence.ts +112 -0
  61. package/src/encoderGate.ts +66 -0
  62. package/src/exportDestination.ts +149 -0
  63. package/src/exportJob.ts +232 -0
  64. package/src/exportProgress.ts +391 -0
  65. package/src/exportProgressPump.ts +190 -0
  66. package/src/exporter.ts +655 -0
  67. package/src/index.ts +276 -0
  68. package/src/managedSources.ts +252 -0
  69. package/src/media3.ts +412 -0
  70. package/src/mediaStore.ts +177 -0
  71. package/src/memoryGuard.ts +118 -0
  72. package/src/mixSources.ts +82 -0
  73. package/src/overlayStaging.ts +216 -0
  74. package/src/playbackLifecycle.ts +137 -0
  75. package/src/player.ts +39 -0
  76. package/src/playlistSession.ts +125 -0
  77. package/src/previewAudioDiagnostics.ts +67 -0
  78. package/src/previewAudioSession.ts +168 -0
  79. package/src/previewCadence.ts +950 -0
  80. package/src/previewMix.ts +212 -0
  81. package/src/previewTrace.ts +348 -0
  82. package/src/spikeExcursions.ts +405 -0
  83. package/src/submissionScoring.ts +680 -0
  84. package/src/surfaceOwner.ts +264 -0
  85. package/src/surfaceRecovery.ts +121 -0
  86. package/src/syncTrace.ts +189 -0
  87. package/src/thumbnailProvider.ts +163 -0
  88. package/src/tracePhases.ts +217 -0
  89. package/src/useCompositionPlayback.ts +327 -0
  90. package/src/useMedia3Playback.ts +492 -0
  91. package/src/usePreviewAudio.ts +305 -0
  92. package/src/useVideoSurface.ts +282 -0
@@ -0,0 +1,168 @@
1
+ import { compileAudioMixProgram, type VideoProject } from '@pictor-sdk/video-core'
2
+
3
+ /**
4
+ * Deciding what a preview should be doing about audio.
5
+ *
6
+ * Pure, and separate from the hook that acts on it, because every rule worth
7
+ * getting right here is a rule about state rather than about React: whether a
8
+ * project needs a mix at all, whether the mix on hand is the one the document
9
+ * describes, whether a result that just arrived is still wanted, and whether
10
+ * two players have drifted far enough to be worth correcting.
11
+ *
12
+ * ## Two sources of sound, never both
13
+ *
14
+ * Without music, the video player's own audio is the preview and nothing here
15
+ * applies. With music, the mix covers the *whole* timeline — including the
16
+ * audio of the clips themselves — so playing the clips' audio as well would
17
+ * play everything twice. There is no state in which both are audible, and the
18
+ * transition between them is the thing this file exists to sequence.
19
+ *
20
+ * @experimental
21
+ */
22
+
23
+ /** What a preview's audio is doing. @experimental */
24
+ export type PreviewAudioStatus =
25
+ /** No music in the project. The video player's own audio is the preview. */
26
+ | 'source'
27
+ /** Music exists and the mix for *this* document is not ready. */
28
+ | 'preparing'
29
+ /** The mix on hand matches the document. Source audio is muted. */
30
+ | 'ready'
31
+ /** The mix could not be prepared. Nothing is played rather than the wrong thing. */
32
+ | 'failed'
33
+
34
+ /** @experimental */
35
+ export interface PreviewAudioDecision {
36
+ status: PreviewAudioStatus
37
+ /** The key the document wants. Empty when there is no music. */
38
+ wantedKey: string
39
+ /** Whether a job should be started for `wantedKey`. */
40
+ shouldPrepare: boolean
41
+ /** Whether the video player's own audio must be silenced. */
42
+ muteSourceAudio: boolean
43
+ /** Whether playback may run right now. */
44
+ canPlay: boolean
45
+ }
46
+
47
+ /** @experimental */
48
+ export interface PreviewAudioInputs {
49
+ /** The key the current document compiles to, or '' when it has no music. */
50
+ wantedKey: string
51
+ /** The key of the mix already prepared and loaded, if any. */
52
+ readyKey: string | null
53
+ /** The key a job is currently preparing, if any. */
54
+ preparingKey: string | null
55
+ /** The key whose preparation failed, if any. */
56
+ failedKey: string | null
57
+ }
58
+
59
+ /**
60
+ * Whether a project needs a mixed preview at all.
61
+ *
62
+ * True when anything in the compiled program is audible *and comes from an
63
+ * audio item* — a project whose only sound is clip audio does not need a mix,
64
+ * because the video player already plays exactly that and does it in realtime.
65
+ *
66
+ * @experimental
67
+ */
68
+ export function previewAudioNeeded(project: VideoProject): boolean {
69
+ return project.audio.length > 0
70
+ }
71
+
72
+ /**
73
+ * What to do, given what is wanted and what is on hand.
74
+ *
75
+ * @experimental
76
+ */
77
+ export function decidePreviewAudio(inputs: PreviewAudioInputs): PreviewAudioDecision {
78
+ const { wantedKey, readyKey, preparingKey, failedKey } = inputs
79
+
80
+ if (wantedKey === '') {
81
+ // No music. The video player's audio is the preview, exactly as it was
82
+ // before any of this existed.
83
+ return {
84
+ status: 'source',
85
+ wantedKey,
86
+ shouldPrepare: false,
87
+ muteSourceAudio: false,
88
+ canPlay: true,
89
+ }
90
+ }
91
+
92
+ if (readyKey === wantedKey) {
93
+ return {
94
+ status: 'ready',
95
+ wantedKey,
96
+ shouldPrepare: false,
97
+ // The mix contains the clips' audio too. Leaving the source audible
98
+ // would play the whole timeline twice.
99
+ muteSourceAudio: true,
100
+ canPlay: true,
101
+ }
102
+ }
103
+
104
+ if (failedKey === wantedKey) {
105
+ /*
106
+ Refusing rather than falling back.
107
+
108
+ The tempting fallback is "play the clip audio instead", and it is wrong:
109
+ the person would hear a preview missing its music, with no reliable way to
110
+ tell that from a project whose music is silent. A named failure is worse
111
+ to look at and better to act on.
112
+ */
113
+ return {
114
+ status: 'failed',
115
+ wantedKey,
116
+ shouldPrepare: false,
117
+ muteSourceAudio: true,
118
+ canPlay: false,
119
+ }
120
+ }
121
+
122
+ /*
123
+ Preparing. A mix for an older document may still be loaded, and it must not
124
+ play: it is the previous edit's sound, which is the most convincing kind of
125
+ wrong. Source audio stays muted throughout, so the transition is silence
126
+ rather than a burst of un-mixed clip audio.
127
+ */
128
+ return {
129
+ status: 'preparing',
130
+ wantedKey,
131
+ shouldPrepare: preparingKey !== wantedKey,
132
+ muteSourceAudio: true,
133
+ canPlay: false,
134
+ }
135
+ }
136
+
137
+ /** The key a project's audio compiles to, given resolved sources. @experimental */
138
+ export function previewProgramJson(project: VideoProject): string {
139
+ return JSON.stringify(compileAudioMixProgram(project))
140
+ }
141
+
142
+ /*
143
+ The drift comparison itself lives in native, not here.
144
+
145
+ It used to be a function in this file, and it was never wired to a caller —
146
+ measured, a clip seam left the mix 240 ms ahead of the picture and it stayed
147
+ there. Reimplementing it here now would be worse than that: both anchors are
148
+ published on one monotonic clock in native, and a JavaScript twin would have
149
+ no way to read them at the same instant, so the two would disagree about what
150
+ "at the same time" meant. See `PreviewTrace.driftCheck`.
151
+ */
152
+
153
+ /**
154
+ * The default tolerance.
155
+ *
156
+ * The drift this used to chase is prevented rather than corrected: `PreviewSync`
157
+ * holds both transports through a stall and releases them aligned, which is
158
+ * where all of it came from. A correcting seek during playback costs 200-340 ms
159
+ * on a Pixel 7 — the same size as the error it was chasing, so it oscillated
160
+ * between +264 ms and -198 ms — and realignment now happens while everything is
161
+ * held, where a seek has nothing to chase.
162
+ *
163
+ * So this is back to the tolerance the gates were written against. It is a
164
+ * backstop for something nobody has seen, not the mechanism.
165
+ *
166
+ * @experimental
167
+ */
168
+ export const PREVIEW_DRIFT_TOLERANCE_US = 60_000