framewebworker 0.1.4 → 0.2.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/README.md +357 -107
- package/dist/index.cjs +21 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -19
- package/dist/index.d.ts +41 -19
- package/dist/index.js +20 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +69 -72
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +58 -39
- package/dist/react/index.d.ts +58 -39
- package/dist/react/index.js +65 -71
- package/dist/react/index.js.map +1 -1
- package/dist/render.cjs +861 -0
- package/dist/render.cjs.map +1 -0
- package/dist/render.js +855 -0
- package/dist/render.js.map +1 -0
- package/package.json +1 -1
package/dist/react/index.d.cts
CHANGED
|
@@ -41,7 +41,8 @@ interface CaptionOptions {
|
|
|
41
41
|
segments: CaptionSegment[];
|
|
42
42
|
style?: Partial<CaptionStyle>;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
/** Input descriptor for one clip source used by mergeClips() */
|
|
45
|
+
interface ClipSource {
|
|
45
46
|
/** Video source: URL string, File, Blob, or HTMLVideoElement */
|
|
46
47
|
source: string | File | Blob | HTMLVideoElement;
|
|
47
48
|
/** Trim start in seconds (default: 0) */
|
|
@@ -90,16 +91,12 @@ interface RenderMetrics {
|
|
|
90
91
|
clips: ClipMetrics[];
|
|
91
92
|
framesPerSecond: number;
|
|
92
93
|
}
|
|
94
|
+
/** One time-range segment from a single source video, used by exportClips() */
|
|
93
95
|
interface Segment {
|
|
94
96
|
start: number;
|
|
95
97
|
end: number;
|
|
96
98
|
captions?: CaptionSegment[];
|
|
97
99
|
}
|
|
98
|
-
/** Options for the render()/renderToUrl() single-video API */
|
|
99
|
-
interface SingleVideoRenderOptions extends Omit<StitchOptions, 'onProgress' | 'onComplete'> {
|
|
100
|
-
onProgress?: (progress: RichProgress) => void;
|
|
101
|
-
onComplete?: (metrics: RenderMetrics) => void;
|
|
102
|
-
}
|
|
103
100
|
type ClipStatus = 'pending' | 'rendering' | 'encoding' | 'done' | 'error';
|
|
104
101
|
interface ClipProgress {
|
|
105
102
|
index: number;
|
|
@@ -110,54 +107,55 @@ interface RichProgress {
|
|
|
110
107
|
overall: number;
|
|
111
108
|
clips: ClipProgress[];
|
|
112
109
|
}
|
|
113
|
-
/**
|
|
114
|
-
interface
|
|
110
|
+
/** Options for mergeClips() / FrameWorker.mergeClips() */
|
|
111
|
+
interface MergeOptions extends Omit<RenderOptions, 'onProgress'> {
|
|
112
|
+
onProgress?: (progress: RichProgress) => void;
|
|
113
|
+
onComplete?: (metrics: RenderMetrics) => void;
|
|
114
|
+
}
|
|
115
|
+
/** Options for exportClips() / exportClipsToUrl() */
|
|
116
|
+
interface ExportOptions extends Omit<MergeOptions, 'onProgress' | 'onComplete'> {
|
|
115
117
|
onProgress?: (progress: RichProgress) => void;
|
|
116
118
|
onComplete?: (metrics: RenderMetrics) => void;
|
|
117
119
|
}
|
|
118
120
|
interface FrameWorker {
|
|
119
|
-
/** Render a single clip to a Blob */
|
|
120
|
-
render(clip:
|
|
121
|
-
/** Render a single clip and return an object URL */
|
|
122
|
-
renderToUrl(clip:
|
|
123
|
-
/**
|
|
124
|
-
|
|
121
|
+
/** Render a single clip to a Blob (legacy single-clip API) */
|
|
122
|
+
render(clip: ClipSource, options?: RenderOptions): Promise<Blob>;
|
|
123
|
+
/** Render a single clip and return an object URL (legacy single-clip API) */
|
|
124
|
+
renderToUrl(clip: ClipSource, options?: RenderOptions): Promise<string>;
|
|
125
|
+
/** Merge multiple clip sources into one Blob */
|
|
126
|
+
mergeClips(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
125
127
|
blob: Blob;
|
|
126
128
|
metrics: RenderMetrics;
|
|
127
129
|
}>;
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
+
/** Merge multiple clip sources and return an object URL */
|
|
131
|
+
mergeClipsToUrl(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
132
|
+
url: string;
|
|
133
|
+
metrics: RenderMetrics;
|
|
134
|
+
}>;
|
|
135
|
+
/** @deprecated Use mergeClips() */
|
|
136
|
+
stitch(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
137
|
+
blob: Blob;
|
|
138
|
+
metrics: RenderMetrics;
|
|
139
|
+
}>;
|
|
140
|
+
/** @deprecated Use mergeClipsToUrl() */
|
|
141
|
+
stitchToUrl(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
130
142
|
url: string;
|
|
131
143
|
metrics: RenderMetrics;
|
|
132
144
|
}>;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
interface
|
|
136
|
-
progress: number;
|
|
137
|
-
isRendering: boolean;
|
|
138
|
-
error: Error | null;
|
|
139
|
-
blob: Blob | null;
|
|
140
|
-
url: string | null;
|
|
141
|
-
}
|
|
142
|
-
interface UseClipRenderActions {
|
|
143
|
-
render: (clip: ClipInput, options?: Omit<RenderOptions, 'onProgress' | 'signal'>) => Promise<Blob | null>;
|
|
144
|
-
cancel: () => void;
|
|
145
|
-
reset: () => void;
|
|
146
|
-
}
|
|
147
|
-
type UseClipRenderResult = UseClipRenderState & UseClipRenderActions;
|
|
148
|
-
declare function useClipRender(frameWorker: FrameWorker): UseClipRenderResult;
|
|
149
|
-
interface UseRenderResult {
|
|
147
|
+
interface UseExportClipsResult {
|
|
150
148
|
start: () => void;
|
|
151
149
|
cancel: () => void;
|
|
150
|
+
isRendering: boolean;
|
|
152
151
|
progress: RichProgress | null;
|
|
153
152
|
metrics: RenderMetrics | null;
|
|
154
153
|
url: string | null;
|
|
155
154
|
error: Error | null;
|
|
156
|
-
isRendering: boolean;
|
|
157
155
|
}
|
|
158
|
-
declare function
|
|
156
|
+
declare function useExportClips(videoUrl: string | null, segments: Segment[], options?: Omit<ExportOptions, 'onProgress' | 'onComplete' | 'signal'>): UseExportClipsResult;
|
|
159
157
|
|
|
160
|
-
interface
|
|
158
|
+
interface UseMergeClipsState {
|
|
161
159
|
progress: RichProgress;
|
|
162
160
|
isRendering: boolean;
|
|
163
161
|
error: Error | null;
|
|
@@ -165,12 +163,33 @@ interface UseStitchState {
|
|
|
165
163
|
url: string | null;
|
|
166
164
|
metrics: RenderMetrics | null;
|
|
167
165
|
}
|
|
168
|
-
interface
|
|
169
|
-
|
|
166
|
+
interface UseMergeClipsActions {
|
|
167
|
+
mergeClips: (clips: ClipSource[], options?: Omit<MergeOptions, 'onProgress' | 'onComplete' | 'signal'>) => Promise<Blob | null>;
|
|
168
|
+
cancel: () => void;
|
|
169
|
+
reset: () => void;
|
|
170
|
+
}
|
|
171
|
+
type UseMergeClipsResult = UseMergeClipsState & UseMergeClipsActions;
|
|
172
|
+
declare function useMergeClips(frameWorker: FrameWorker): UseMergeClipsResult;
|
|
173
|
+
|
|
174
|
+
interface UsePreviewClipState {
|
|
175
|
+
progress: number;
|
|
176
|
+
isRendering: boolean;
|
|
177
|
+
error: Error | null;
|
|
178
|
+
blob: Blob | null;
|
|
179
|
+
url: string | null;
|
|
180
|
+
}
|
|
181
|
+
interface UsePreviewClipActions {
|
|
182
|
+
render: (clip: ClipSource, options?: Omit<RenderOptions, 'onProgress' | 'signal'>) => Promise<Blob | null>;
|
|
170
183
|
cancel: () => void;
|
|
171
184
|
reset: () => void;
|
|
172
185
|
}
|
|
173
|
-
type
|
|
174
|
-
declare function
|
|
186
|
+
type UsePreviewClipResult = UsePreviewClipState & UsePreviewClipActions;
|
|
187
|
+
declare function usePreviewClip(frameWorker: FrameWorker): UsePreviewClipResult;
|
|
188
|
+
/** @deprecated Use UsePreviewClipResult */
|
|
189
|
+
type UseClipRenderResult = UsePreviewClipResult;
|
|
190
|
+
/** @deprecated Use UsePreviewClipState */
|
|
191
|
+
type UseClipRenderState = UsePreviewClipState;
|
|
192
|
+
/** @deprecated Use UsePreviewClipActions */
|
|
193
|
+
type UseClipRenderActions = UsePreviewClipActions;
|
|
175
194
|
|
|
176
|
-
export { type UseClipRenderActions, type UseClipRenderResult, type UseClipRenderState, type
|
|
195
|
+
export { type UseClipRenderActions, type UseClipRenderResult, type UseClipRenderState, type UseExportClipsResult, type UseMergeClipsActions, type UseMergeClipsResult, type UseMergeClipsState, type UsePreviewClipActions, type UsePreviewClipResult, type UsePreviewClipState, type UseMergeClipsActions as UseStitchActions, type UseMergeClipsResult as UseStitchResult, type UseMergeClipsState as UseStitchState, usePreviewClip as useClipRender, useExportClips, useMergeClips, usePreviewClip, useExportClips as useRender, useMergeClips as useStitch };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -41,7 +41,8 @@ interface CaptionOptions {
|
|
|
41
41
|
segments: CaptionSegment[];
|
|
42
42
|
style?: Partial<CaptionStyle>;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
/** Input descriptor for one clip source used by mergeClips() */
|
|
45
|
+
interface ClipSource {
|
|
45
46
|
/** Video source: URL string, File, Blob, or HTMLVideoElement */
|
|
46
47
|
source: string | File | Blob | HTMLVideoElement;
|
|
47
48
|
/** Trim start in seconds (default: 0) */
|
|
@@ -90,16 +91,12 @@ interface RenderMetrics {
|
|
|
90
91
|
clips: ClipMetrics[];
|
|
91
92
|
framesPerSecond: number;
|
|
92
93
|
}
|
|
94
|
+
/** One time-range segment from a single source video, used by exportClips() */
|
|
93
95
|
interface Segment {
|
|
94
96
|
start: number;
|
|
95
97
|
end: number;
|
|
96
98
|
captions?: CaptionSegment[];
|
|
97
99
|
}
|
|
98
|
-
/** Options for the render()/renderToUrl() single-video API */
|
|
99
|
-
interface SingleVideoRenderOptions extends Omit<StitchOptions, 'onProgress' | 'onComplete'> {
|
|
100
|
-
onProgress?: (progress: RichProgress) => void;
|
|
101
|
-
onComplete?: (metrics: RenderMetrics) => void;
|
|
102
|
-
}
|
|
103
100
|
type ClipStatus = 'pending' | 'rendering' | 'encoding' | 'done' | 'error';
|
|
104
101
|
interface ClipProgress {
|
|
105
102
|
index: number;
|
|
@@ -110,54 +107,55 @@ interface RichProgress {
|
|
|
110
107
|
overall: number;
|
|
111
108
|
clips: ClipProgress[];
|
|
112
109
|
}
|
|
113
|
-
/**
|
|
114
|
-
interface
|
|
110
|
+
/** Options for mergeClips() / FrameWorker.mergeClips() */
|
|
111
|
+
interface MergeOptions extends Omit<RenderOptions, 'onProgress'> {
|
|
112
|
+
onProgress?: (progress: RichProgress) => void;
|
|
113
|
+
onComplete?: (metrics: RenderMetrics) => void;
|
|
114
|
+
}
|
|
115
|
+
/** Options for exportClips() / exportClipsToUrl() */
|
|
116
|
+
interface ExportOptions extends Omit<MergeOptions, 'onProgress' | 'onComplete'> {
|
|
115
117
|
onProgress?: (progress: RichProgress) => void;
|
|
116
118
|
onComplete?: (metrics: RenderMetrics) => void;
|
|
117
119
|
}
|
|
118
120
|
interface FrameWorker {
|
|
119
|
-
/** Render a single clip to a Blob */
|
|
120
|
-
render(clip:
|
|
121
|
-
/** Render a single clip and return an object URL */
|
|
122
|
-
renderToUrl(clip:
|
|
123
|
-
/**
|
|
124
|
-
|
|
121
|
+
/** Render a single clip to a Blob (legacy single-clip API) */
|
|
122
|
+
render(clip: ClipSource, options?: RenderOptions): Promise<Blob>;
|
|
123
|
+
/** Render a single clip and return an object URL (legacy single-clip API) */
|
|
124
|
+
renderToUrl(clip: ClipSource, options?: RenderOptions): Promise<string>;
|
|
125
|
+
/** Merge multiple clip sources into one Blob */
|
|
126
|
+
mergeClips(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
125
127
|
blob: Blob;
|
|
126
128
|
metrics: RenderMetrics;
|
|
127
129
|
}>;
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
+
/** Merge multiple clip sources and return an object URL */
|
|
131
|
+
mergeClipsToUrl(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
132
|
+
url: string;
|
|
133
|
+
metrics: RenderMetrics;
|
|
134
|
+
}>;
|
|
135
|
+
/** @deprecated Use mergeClips() */
|
|
136
|
+
stitch(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
137
|
+
blob: Blob;
|
|
138
|
+
metrics: RenderMetrics;
|
|
139
|
+
}>;
|
|
140
|
+
/** @deprecated Use mergeClipsToUrl() */
|
|
141
|
+
stitchToUrl(clips: ClipSource[], options?: MergeOptions): Promise<{
|
|
130
142
|
url: string;
|
|
131
143
|
metrics: RenderMetrics;
|
|
132
144
|
}>;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
interface
|
|
136
|
-
progress: number;
|
|
137
|
-
isRendering: boolean;
|
|
138
|
-
error: Error | null;
|
|
139
|
-
blob: Blob | null;
|
|
140
|
-
url: string | null;
|
|
141
|
-
}
|
|
142
|
-
interface UseClipRenderActions {
|
|
143
|
-
render: (clip: ClipInput, options?: Omit<RenderOptions, 'onProgress' | 'signal'>) => Promise<Blob | null>;
|
|
144
|
-
cancel: () => void;
|
|
145
|
-
reset: () => void;
|
|
146
|
-
}
|
|
147
|
-
type UseClipRenderResult = UseClipRenderState & UseClipRenderActions;
|
|
148
|
-
declare function useClipRender(frameWorker: FrameWorker): UseClipRenderResult;
|
|
149
|
-
interface UseRenderResult {
|
|
147
|
+
interface UseExportClipsResult {
|
|
150
148
|
start: () => void;
|
|
151
149
|
cancel: () => void;
|
|
150
|
+
isRendering: boolean;
|
|
152
151
|
progress: RichProgress | null;
|
|
153
152
|
metrics: RenderMetrics | null;
|
|
154
153
|
url: string | null;
|
|
155
154
|
error: Error | null;
|
|
156
|
-
isRendering: boolean;
|
|
157
155
|
}
|
|
158
|
-
declare function
|
|
156
|
+
declare function useExportClips(videoUrl: string | null, segments: Segment[], options?: Omit<ExportOptions, 'onProgress' | 'onComplete' | 'signal'>): UseExportClipsResult;
|
|
159
157
|
|
|
160
|
-
interface
|
|
158
|
+
interface UseMergeClipsState {
|
|
161
159
|
progress: RichProgress;
|
|
162
160
|
isRendering: boolean;
|
|
163
161
|
error: Error | null;
|
|
@@ -165,12 +163,33 @@ interface UseStitchState {
|
|
|
165
163
|
url: string | null;
|
|
166
164
|
metrics: RenderMetrics | null;
|
|
167
165
|
}
|
|
168
|
-
interface
|
|
169
|
-
|
|
166
|
+
interface UseMergeClipsActions {
|
|
167
|
+
mergeClips: (clips: ClipSource[], options?: Omit<MergeOptions, 'onProgress' | 'onComplete' | 'signal'>) => Promise<Blob | null>;
|
|
168
|
+
cancel: () => void;
|
|
169
|
+
reset: () => void;
|
|
170
|
+
}
|
|
171
|
+
type UseMergeClipsResult = UseMergeClipsState & UseMergeClipsActions;
|
|
172
|
+
declare function useMergeClips(frameWorker: FrameWorker): UseMergeClipsResult;
|
|
173
|
+
|
|
174
|
+
interface UsePreviewClipState {
|
|
175
|
+
progress: number;
|
|
176
|
+
isRendering: boolean;
|
|
177
|
+
error: Error | null;
|
|
178
|
+
blob: Blob | null;
|
|
179
|
+
url: string | null;
|
|
180
|
+
}
|
|
181
|
+
interface UsePreviewClipActions {
|
|
182
|
+
render: (clip: ClipSource, options?: Omit<RenderOptions, 'onProgress' | 'signal'>) => Promise<Blob | null>;
|
|
170
183
|
cancel: () => void;
|
|
171
184
|
reset: () => void;
|
|
172
185
|
}
|
|
173
|
-
type
|
|
174
|
-
declare function
|
|
186
|
+
type UsePreviewClipResult = UsePreviewClipState & UsePreviewClipActions;
|
|
187
|
+
declare function usePreviewClip(frameWorker: FrameWorker): UsePreviewClipResult;
|
|
188
|
+
/** @deprecated Use UsePreviewClipResult */
|
|
189
|
+
type UseClipRenderResult = UsePreviewClipResult;
|
|
190
|
+
/** @deprecated Use UsePreviewClipState */
|
|
191
|
+
type UseClipRenderState = UsePreviewClipState;
|
|
192
|
+
/** @deprecated Use UsePreviewClipActions */
|
|
193
|
+
type UseClipRenderActions = UsePreviewClipActions;
|
|
175
194
|
|
|
176
|
-
export { type UseClipRenderActions, type UseClipRenderResult, type UseClipRenderState, type
|
|
195
|
+
export { type UseClipRenderActions, type UseClipRenderResult, type UseClipRenderState, type UseExportClipsResult, type UseMergeClipsActions, type UseMergeClipsResult, type UseMergeClipsState, type UsePreviewClipActions, type UsePreviewClipResult, type UsePreviewClipState, type UseMergeClipsActions as UseStitchActions, type UseMergeClipsResult as UseStitchResult, type UseMergeClipsState as UseStitchState, usePreviewClip as useClipRender, useExportClips, useMergeClips, usePreviewClip, useExportClips as useRender, useMergeClips as useStitch };
|
package/dist/react/index.js
CHANGED
|
@@ -1,63 +1,8 @@
|
|
|
1
1
|
import { useState, useRef, useCallback } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { exportClips } from '../render.js';
|
|
3
3
|
|
|
4
|
-
// src/react/
|
|
5
|
-
function
|
|
6
|
-
const [state, setState] = useState({
|
|
7
|
-
progress: 0,
|
|
8
|
-
isRendering: false,
|
|
9
|
-
error: null,
|
|
10
|
-
blob: null,
|
|
11
|
-
url: null
|
|
12
|
-
});
|
|
13
|
-
const abortRef = useRef(null);
|
|
14
|
-
const urlRef = useRef(null);
|
|
15
|
-
const cancel = useCallback(() => {
|
|
16
|
-
abortRef.current?.abort();
|
|
17
|
-
}, []);
|
|
18
|
-
const reset = useCallback(() => {
|
|
19
|
-
if (urlRef.current) {
|
|
20
|
-
URL.revokeObjectURL(urlRef.current);
|
|
21
|
-
urlRef.current = null;
|
|
22
|
-
}
|
|
23
|
-
setState({ progress: 0, isRendering: false, error: null, blob: null, url: null });
|
|
24
|
-
}, []);
|
|
25
|
-
const render = useCallback(
|
|
26
|
-
async (clip, options) => {
|
|
27
|
-
if (urlRef.current) {
|
|
28
|
-
URL.revokeObjectURL(urlRef.current);
|
|
29
|
-
urlRef.current = null;
|
|
30
|
-
}
|
|
31
|
-
const controller = new AbortController();
|
|
32
|
-
abortRef.current = controller;
|
|
33
|
-
setState({ progress: 0, isRendering: true, error: null, blob: null, url: null });
|
|
34
|
-
try {
|
|
35
|
-
const blob = await frameWorker.render(clip, {
|
|
36
|
-
...options,
|
|
37
|
-
signal: controller.signal,
|
|
38
|
-
onProgress: (p) => {
|
|
39
|
-
setState((prev) => ({ ...prev, progress: p }));
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
const url = URL.createObjectURL(blob);
|
|
43
|
-
urlRef.current = url;
|
|
44
|
-
setState({ progress: 1, isRendering: false, error: null, blob, url });
|
|
45
|
-
return blob;
|
|
46
|
-
} catch (err) {
|
|
47
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
48
|
-
setState((prev) => ({ ...prev, isRendering: false, error: null }));
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
52
|
-
setState((prev) => ({ ...prev, isRendering: false, error }));
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
[frameWorker]
|
|
57
|
-
);
|
|
58
|
-
return { ...state, render, cancel, reset };
|
|
59
|
-
}
|
|
60
|
-
function useRender(videoUrl, segments, options) {
|
|
4
|
+
// src/react/useExportClips.ts
|
|
5
|
+
function useExportClips(videoUrl, segments, options) {
|
|
61
6
|
const [isRendering, setIsRendering] = useState(false);
|
|
62
7
|
const [progress, setProgress] = useState(null);
|
|
63
8
|
const [metrics, setMetrics] = useState(null);
|
|
@@ -81,7 +26,7 @@ function useRender(videoUrl, segments, options) {
|
|
|
81
26
|
setMetrics(null);
|
|
82
27
|
setUrl(null);
|
|
83
28
|
setError(null);
|
|
84
|
-
|
|
29
|
+
exportClips(videoUrl, segments, {
|
|
85
30
|
...options,
|
|
86
31
|
signal: controller.signal,
|
|
87
32
|
onProgress: (p) => setProgress(p),
|
|
@@ -100,10 +45,10 @@ function useRender(videoUrl, segments, options) {
|
|
|
100
45
|
setIsRendering(false);
|
|
101
46
|
});
|
|
102
47
|
}, [videoUrl, segments, options, isRendering]);
|
|
103
|
-
return { start, cancel, progress, metrics, url, error
|
|
48
|
+
return { start, cancel, isRendering, progress, metrics, url, error };
|
|
104
49
|
}
|
|
105
50
|
var INITIAL_PROGRESS = { overall: 0, clips: [] };
|
|
106
|
-
function
|
|
51
|
+
function useMergeClips(frameWorker) {
|
|
107
52
|
const [state, setState] = useState({
|
|
108
53
|
progress: INITIAL_PROGRESS,
|
|
109
54
|
isRendering: false,
|
|
@@ -124,7 +69,7 @@ function useStitch(frameWorker) {
|
|
|
124
69
|
}
|
|
125
70
|
setState({ progress: INITIAL_PROGRESS, isRendering: false, error: null, blob: null, url: null, metrics: null });
|
|
126
71
|
}, []);
|
|
127
|
-
const
|
|
72
|
+
const mergeClips = useCallback(
|
|
128
73
|
async (clips, options) => {
|
|
129
74
|
if (urlRef.current) {
|
|
130
75
|
URL.revokeObjectURL(urlRef.current);
|
|
@@ -134,15 +79,11 @@ function useStitch(frameWorker) {
|
|
|
134
79
|
abortRef.current = controller;
|
|
135
80
|
setState({ progress: INITIAL_PROGRESS, isRendering: true, error: null, blob: null, url: null, metrics: null });
|
|
136
81
|
try {
|
|
137
|
-
const { blob, metrics } = await frameWorker.
|
|
82
|
+
const { blob, metrics } = await frameWorker.mergeClips(clips, {
|
|
138
83
|
...options,
|
|
139
84
|
signal: controller.signal,
|
|
140
|
-
onProgress: (p) => {
|
|
141
|
-
|
|
142
|
-
},
|
|
143
|
-
onComplete: (m) => {
|
|
144
|
-
setState((prev) => ({ ...prev, metrics: m }));
|
|
145
|
-
}
|
|
85
|
+
onProgress: (p) => setState((prev) => ({ ...prev, progress: p })),
|
|
86
|
+
onComplete: (m) => setState((prev) => ({ ...prev, metrics: m }))
|
|
146
87
|
});
|
|
147
88
|
const url = URL.createObjectURL(blob);
|
|
148
89
|
urlRef.current = url;
|
|
@@ -164,9 +105,62 @@ function useStitch(frameWorker) {
|
|
|
164
105
|
},
|
|
165
106
|
[frameWorker]
|
|
166
107
|
);
|
|
167
|
-
return { ...state,
|
|
108
|
+
return { ...state, mergeClips, cancel, reset };
|
|
109
|
+
}
|
|
110
|
+
function usePreviewClip(frameWorker) {
|
|
111
|
+
const [state, setState] = useState({
|
|
112
|
+
progress: 0,
|
|
113
|
+
isRendering: false,
|
|
114
|
+
error: null,
|
|
115
|
+
blob: null,
|
|
116
|
+
url: null
|
|
117
|
+
});
|
|
118
|
+
const abortRef = useRef(null);
|
|
119
|
+
const urlRef = useRef(null);
|
|
120
|
+
const cancel = useCallback(() => {
|
|
121
|
+
abortRef.current?.abort();
|
|
122
|
+
}, []);
|
|
123
|
+
const reset = useCallback(() => {
|
|
124
|
+
if (urlRef.current) {
|
|
125
|
+
URL.revokeObjectURL(urlRef.current);
|
|
126
|
+
urlRef.current = null;
|
|
127
|
+
}
|
|
128
|
+
setState({ progress: 0, isRendering: false, error: null, blob: null, url: null });
|
|
129
|
+
}, []);
|
|
130
|
+
const render = useCallback(
|
|
131
|
+
async (clip, options) => {
|
|
132
|
+
if (urlRef.current) {
|
|
133
|
+
URL.revokeObjectURL(urlRef.current);
|
|
134
|
+
urlRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
abortRef.current = controller;
|
|
138
|
+
setState({ progress: 0, isRendering: true, error: null, blob: null, url: null });
|
|
139
|
+
try {
|
|
140
|
+
const blob = await frameWorker.render(clip, {
|
|
141
|
+
...options,
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
onProgress: (p) => setState((prev) => ({ ...prev, progress: p }))
|
|
144
|
+
});
|
|
145
|
+
const url = URL.createObjectURL(blob);
|
|
146
|
+
urlRef.current = url;
|
|
147
|
+
setState({ progress: 1, isRendering: false, error: null, blob, url });
|
|
148
|
+
return blob;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
151
|
+
setState((prev) => ({ ...prev, isRendering: false, error: null }));
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
155
|
+
setState((prev) => ({ ...prev, isRendering: false, error }));
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
[frameWorker]
|
|
160
|
+
);
|
|
161
|
+
return { ...state, render, cancel, reset };
|
|
168
162
|
}
|
|
169
163
|
|
|
170
|
-
export { useClipRender, useRender, useStitch };
|
|
164
|
+
export { usePreviewClip as useClipRender, useExportClips, useMergeClips, usePreviewClip, useExportClips as useRender, useMergeClips as useStitch };
|
|
171
165
|
//# sourceMappingURL=index.js.map
|
|
172
166
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/useRender.ts","../../src/react/useStitch.ts"],"names":["renderSegments","useState","useRef","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport { useState, useCallback, useRef } from 'react';\nimport type { ClipInput, RenderOptions, FrameWorker } from '../types.js';\nimport type { Segment, SingleVideoRenderOptions, RichProgress, RenderMetrics } from '../types.js';\nimport { render as renderSegments } from '../render.js';\n\n// ── useClipRender — wraps FrameWorker.render(clip) ───────────────────────────\n\nexport interface UseClipRenderState {\n progress: number;\n isRendering: boolean;\n error: Error | null;\n blob: Blob | null;\n url: string | null;\n}\n\nexport interface UseClipRenderActions {\n render: (clip: ClipInput, options?: Omit<RenderOptions, 'onProgress' | 'signal'>) => Promise<Blob | null>;\n cancel: () => void;\n reset: () => void;\n}\n\nexport type UseClipRenderResult = UseClipRenderState & UseClipRenderActions;\n\nexport function useClipRender(frameWorker: FrameWorker): UseClipRenderResult {\n const [state, setState] = useState<UseClipRenderState>({\n progress: 0,\n isRendering: false,\n error: null,\n blob: null,\n url: null,\n });\n\n const abortRef = useRef<AbortController | null>(null);\n const urlRef = useRef<string | null>(null);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const reset = useCallback(() => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n setState({ progress: 0, isRendering: false, error: null, blob: null, url: null });\n }, []);\n\n const render = useCallback(\n async (\n clip: ClipInput,\n options?: Omit<RenderOptions, 'onProgress' | 'signal'>\n ): Promise<Blob | null> => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n setState({ progress: 0, isRendering: true, error: null, blob: null, url: null });\n\n try {\n const blob = await frameWorker.render(clip, {\n ...options,\n signal: controller.signal,\n onProgress: (p) => {\n setState((prev) => ({ ...prev, progress: p }));\n },\n });\n\n const url = URL.createObjectURL(blob);\n urlRef.current = url;\n setState({ progress: 1, isRendering: false, error: null, blob, url });\n return blob;\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n setState((prev) => ({ ...prev, isRendering: false, error: null }));\n return null;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n setState((prev) => ({ ...prev, isRendering: false, error }));\n return null;\n }\n },\n [frameWorker]\n );\n\n return { ...state, render, cancel, reset };\n}\n\n// ── useRender — single-video multi-segment API ────────────────────────────────\n\nexport interface UseRenderResult {\n start: () => void;\n cancel: () => void;\n progress: RichProgress | null;\n metrics: RenderMetrics | null;\n url: string | null;\n error: Error | null;\n isRendering: boolean;\n}\n\nexport function useRender(\n videoUrl: string | null,\n segments: Segment[],\n options?: Omit<SingleVideoRenderOptions, 'onProgress' | 'onComplete' | 'signal'>\n): UseRenderResult {\n const [isRendering, setIsRendering] = useState(false);\n const [progress, setProgress] = useState<RichProgress | null>(null);\n const [metrics, setMetrics] = useState<RenderMetrics | null>(null);\n const [url, setUrl] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const abortRef = useRef<AbortController | null>(null);\n const urlRef = useRef<string | null>(null);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const start = useCallback(() => {\n if (!videoUrl || isRendering) return;\n\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n setIsRendering(true);\n setProgress(null);\n setMetrics(null);\n setUrl(null);\n setError(null);\n\n renderSegments(videoUrl, segments, {\n ...options,\n signal: controller.signal,\n onProgress: (p) => setProgress(p),\n onComplete: (m) => setMetrics(m),\n }).then(({ blob }) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n setIsRendering(false);\n }).catch((err) => {\n if (err instanceof DOMException && err.name === 'AbortError') {\n setIsRendering(false);\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsRendering(false);\n });\n }, [videoUrl, segments, options, isRendering]);\n\n return { start, cancel, progress, metrics, url, error, isRendering };\n}\n","'use client';\n\nimport { useState, useCallback, useRef } from 'react';\nimport type { ClipInput, StitchOptions, RichProgress, RenderMetrics, FrameWorker } from '../types.js';\n\nexport interface UseStitchState {\n progress: RichProgress;\n isRendering: boolean;\n error: Error | null;\n blob: Blob | null;\n url: string | null;\n metrics: RenderMetrics | null;\n}\n\nexport interface UseStitchActions {\n stitch: (clips: ClipInput[], options?: Omit<StitchOptions, 'onProgress' | 'onComplete' | 'signal'>) => Promise<Blob | null>;\n cancel: () => void;\n reset: () => void;\n}\n\nexport type UseStitchResult = UseStitchState & UseStitchActions;\n\nconst INITIAL_PROGRESS: RichProgress = { overall: 0, clips: [] };\n\nexport function useStitch(frameWorker: FrameWorker): UseStitchResult {\n const [state, setState] = useState<UseStitchState>({\n progress: INITIAL_PROGRESS,\n isRendering: false,\n error: null,\n blob: null,\n url: null,\n metrics: null,\n });\n\n const abortRef = useRef<AbortController | null>(null);\n const urlRef = useRef<string | null>(null);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const reset = useCallback(() => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n setState({ progress: INITIAL_PROGRESS, isRendering: false, error: null, blob: null, url: null, metrics: null });\n }, []);\n\n const stitch = useCallback(\n async (\n clips: ClipInput[],\n options?: Omit<StitchOptions, 'onProgress' | 'onComplete' | 'signal'>\n ): Promise<Blob | null> => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n setState({ progress: INITIAL_PROGRESS, isRendering: true, error: null, blob: null, url: null, metrics: null });\n\n try {\n const { blob, metrics } = await frameWorker.stitch(clips, {\n ...options,\n signal: controller.signal,\n onProgress: (p) => {\n setState((prev) => ({ ...prev, progress: p }));\n },\n onComplete: (m) => {\n setState((prev) => ({ ...prev, metrics: m }));\n },\n });\n\n const url = URL.createObjectURL(blob);\n urlRef.current = url;\n const doneProgress: RichProgress = {\n overall: 1,\n clips: clips.map((_, i) => ({ index: i, status: 'done', progress: 1 })),\n };\n setState((prev) => ({ ...prev, progress: doneProgress, isRendering: false, blob, url }));\n return blob;\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n setState((prev) => ({ ...prev, isRendering: false, error: null }));\n return null;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n setState((prev) => ({ ...prev, isRendering: false, error }));\n return null;\n }\n },\n [frameWorker]\n );\n\n return { ...state, stitch, cancel, reset };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/useExportClips.ts","../../src/react/useMergeClips.ts","../../src/react/useRender.ts"],"names":["useState","useRef","useCallback"],"mappings":";;;;AAgBO,SAAS,cAAA,CACd,QAAA,EACA,QAAA,EACA,OAAA,EACsB;AACtB,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA8B,IAAI,CAAA;AAClE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAA+B,IAAI,CAAA;AACjE,EAAA,MAAM,CAAC,GAAA,EAAK,MAAM,CAAA,GAAI,SAAwB,IAAI,CAAA;AAClD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,QAAA,GAAW,OAA+B,IAAI,CAAA;AACpD,EAAA,MAAM,MAAA,GAAS,OAAsB,IAAI,CAAA;AAEzC,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM;AAC/B,IAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AAAA,EAC1B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,IAAI,CAAC,YAAY,WAAA,EAAa;AAE9B,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,GAAA,CAAI,eAAA,CAAgB,OAAO,OAAO,CAAA;AAClC,MAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAAA,IACnB;AAEA,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,QAAA,CAAS,OAAA,GAAU,UAAA;AAEnB,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,MAAA,CAAO,IAAI,CAAA;AACX,IAAA,QAAA,CAAS,IAAI,CAAA;AAEb,IAAA,WAAA,CAAY,UAAU,QAAA,EAAU;AAAA,MAC9B,GAAG,OAAA;AAAA,MACH,QAAQ,UAAA,CAAW,MAAA;AAAA,MACnB,UAAA,EAAY,CAAC,CAAA,KAAM,WAAA,CAAY,CAAC,CAAA;AAAA,MAChC,UAAA,EAAY,CAAC,CAAA,KAAM,UAAA,CAAW,CAAC;AAAA,KAChC,CAAA,CAAE,IAAA,CAAK,CAAC,EAAE,MAAK,KAAM;AACpB,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AAC1C,MAAA,MAAA,CAAO,OAAA,GAAU,SAAA;AACjB,MAAA,MAAA,CAAO,SAAS,CAAA;AAChB,MAAA,cAAA,CAAe,KAAK,CAAA;AAAA,IACtB,CAAC,CAAA,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AAChB,MAAA,IAAI,GAAA,YAAe,YAAA,IAAgB,GAAA,CAAI,IAAA,KAAS,YAAA,EAAc;AAC5D,QAAA,cAAA,CAAe,KAAK,CAAA;AACpB,QAAA;AAAA,MACF;AACA,MAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC5D,MAAA,cAAA,CAAe,KAAK,CAAA;AAAA,IACtB,CAAC,CAAA;AAAA,EACH,GAAG,CAAC,QAAA,EAAU,QAAA,EAAU,OAAA,EAAS,WAAW,CAAC,CAAA;AAE7C,EAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,aAAa,QAAA,EAAU,OAAA,EAAS,KAAK,KAAA,EAAM;AACrE;AClDA,IAAM,mBAAiC,EAAE,OAAA,EAAS,CAAA,EAAG,KAAA,EAAO,EAAC,EAAE;AAExD,SAAS,cAAc,WAAA,EAA+C;AAC3E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,QAAAA,CAA6B;AAAA,IACrD,QAAA,EAAU,gBAAA;AAAA,IACV,WAAA,EAAa,KAAA;AAAA,IACb,KAAA,EAAO,IAAA;AAAA,IACP,IAAA,EAAM,IAAA;AAAA,IACN,GAAA,EAAK,IAAA;AAAA,IACL,OAAA,EAAS;AAAA,GACV,CAAA;AAED,EAAA,MAAM,QAAA,GAAWC,OAA+B,IAAI,CAAA;AACpD,EAAA,MAAM,MAAA,GAASA,OAAsB,IAAI,CAAA;AAEzC,EAAA,MAAM,MAAA,GAASC,YAAY,MAAM;AAC/B,IAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AAAA,EAC1B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,GAAA,CAAI,eAAA,CAAgB,OAAO,OAAO,CAAA;AAClC,MAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAAA,IACnB;AACA,IAAA,QAAA,CAAS,EAAE,QAAA,EAAU,gBAAA,EAAkB,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAAA,EAChH,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,WAAAA;AAAA,IACjB,OACE,OACA,OAAA,KACyB;AACzB,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,GAAA,CAAI,eAAA,CAAgB,OAAO,OAAO,CAAA;AAClC,QAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAAA,MACnB;AAEA,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,QAAA,CAAS,OAAA,GAAU,UAAA;AAEnB,MAAA,QAAA,CAAS,EAAE,QAAA,EAAU,gBAAA,EAAkB,WAAA,EAAa,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAE7G,MAAA,IAAI;AACF,QAAA,MAAM,EAAE,IAAA,EAAM,OAAA,KAAY,MAAM,WAAA,CAAY,WAAW,KAAA,EAAO;AAAA,UAC5D,GAAG,OAAA;AAAA,UACH,QAAQ,UAAA,CAAW,MAAA;AAAA,UACnB,UAAA,EAAY,CAAC,CAAA,KAAM,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,CAAA,EAAE,CAAE,CAAA;AAAA,UAChE,UAAA,EAAY,CAAC,CAAA,KAAM,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,OAAA,EAAS,CAAA,EAAE,CAAE;AAAA,SAChE,CAAA;AAED,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,QAAA,MAAA,CAAO,OAAA,GAAU,GAAA;AACjB,QAAA,MAAM,YAAA,GAA6B;AAAA,UACjC,OAAA,EAAS,CAAA;AAAA,UACT,KAAA,EAAO,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,MAAO,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,MAAA,EAAQ,QAAA,EAAU,GAAE,CAAE;AAAA,SACxE;AACA,QAAA,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,YAAA,EAAc,WAAA,EAAa,KAAA,EAAO,IAAA,EAAM,GAAA,EAAI,CAAE,CAAA;AACvF,QAAA,OAAO,IAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,GAAA,YAAe,YAAA,IAAgB,GAAA,CAAI,IAAA,KAAS,YAAA,EAAc;AAC5D,UAAA,QAAA,CAAS,CAAC,UAAU,EAAE,GAAG,MAAM,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,IAAA,EAAK,CAAE,CAAA;AACjE,UAAA,OAAO,IAAA;AAAA,QACT;AACA,QAAA,MAAM,KAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAChE,QAAA,QAAA,CAAS,CAAC,UAAU,EAAE,GAAG,MAAM,WAAA,EAAa,KAAA,EAAO,OAAM,CAAE,CAAA;AAC3D,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,CAAA;AAAA,IACA,CAAC,WAAW;AAAA,GACd;AAEA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,UAAA,EAAY,QAAQ,KAAA,EAAM;AAC/C;ACvEO,SAAS,eAAe,WAAA,EAAgD;AAC7E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIF,QAAAA,CAA8B;AAAA,IACtD,QAAA,EAAU,CAAA;AAAA,IACV,WAAA,EAAa,KAAA;AAAA,IACb,KAAA,EAAO,IAAA;AAAA,IACP,IAAA,EAAM,IAAA;AAAA,IACN,GAAA,EAAK;AAAA,GACN,CAAA;AAED,EAAA,MAAM,QAAA,GAAWC,OAA+B,IAAI,CAAA;AACpD,EAAA,MAAM,MAAA,GAASA,OAAsB,IAAI,CAAA;AAEzC,EAAA,MAAM,MAAA,GAASC,YAAY,MAAM;AAC/B,IAAA,QAAA,CAAS,SAAS,KAAA,EAAM;AAAA,EAC1B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,GAAA,CAAI,eAAA,CAAgB,OAAO,OAAO,CAAA;AAClC,MAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAAA,IACnB;AACA,IAAA,QAAA,CAAS,EAAE,QAAA,EAAU,CAAA,EAAG,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,CAAA;AAAA,EAClF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAASA,WAAAA;AAAA,IACb,OACE,MACA,OAAA,KACyB;AACzB,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,GAAA,CAAI,eAAA,CAAgB,OAAO,OAAO,CAAA;AAClC,QAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAAA,MACnB;AAEA,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,QAAA,CAAS,OAAA,GAAU,UAAA;AAEnB,MAAA,QAAA,CAAS,EAAE,QAAA,EAAU,CAAA,EAAG,WAAA,EAAa,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,CAAA;AAE/E,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAAY,MAAA,CAAO,IAAA,EAAM;AAAA,UAC1C,GAAG,OAAA;AAAA,UACH,QAAQ,UAAA,CAAW,MAAA;AAAA,UACnB,UAAA,EAAY,CAAC,CAAA,KAAM,QAAA,CAAS,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,QAAA,EAAU,CAAA,EAAE,CAAE;AAAA,SACjE,CAAA;AAED,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,QAAA,MAAA,CAAO,OAAA,GAAU,GAAA;AACjB,QAAA,QAAA,CAAS,EAAE,UAAU,CAAA,EAAG,WAAA,EAAa,OAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,CAAA;AACpE,QAAA,OAAO,IAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,GAAA,YAAe,YAAA,IAAgB,GAAA,CAAI,IAAA,KAAS,YAAA,EAAc;AAC5D,UAAA,QAAA,CAAS,CAAC,UAAU,EAAE,GAAG,MAAM,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,IAAA,EAAK,CAAE,CAAA;AACjE,UAAA,OAAO,IAAA;AAAA,QACT;AACA,QAAA,MAAM,KAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAChE,QAAA,QAAA,CAAS,CAAC,UAAU,EAAE,GAAG,MAAM,WAAA,EAAa,KAAA,EAAO,OAAM,CAAE,CAAA;AAC3D,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,CAAA;AAAA,IACA,CAAC,WAAW;AAAA,GACd;AAEA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAA,EAAQ,QAAQ,KAAA,EAAM;AAC3C","file":"index.js","sourcesContent":["'use client';\n\nimport { useState, useCallback, useRef } from 'react';\nimport type { Segment, ExportOptions, RichProgress, RenderMetrics } from '../types.js';\nimport { exportClips } from '../render.js';\n\nexport interface UseExportClipsResult {\n start: () => void;\n cancel: () => void;\n isRendering: boolean;\n progress: RichProgress | null;\n metrics: RenderMetrics | null;\n url: string | null;\n error: Error | null;\n}\n\nexport function useExportClips(\n videoUrl: string | null,\n segments: Segment[],\n options?: Omit<ExportOptions, 'onProgress' | 'onComplete' | 'signal'>\n): UseExportClipsResult {\n const [isRendering, setIsRendering] = useState(false);\n const [progress, setProgress] = useState<RichProgress | null>(null);\n const [metrics, setMetrics] = useState<RenderMetrics | null>(null);\n const [url, setUrl] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n const abortRef = useRef<AbortController | null>(null);\n const urlRef = useRef<string | null>(null);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const start = useCallback(() => {\n if (!videoUrl || isRendering) return;\n\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n setIsRendering(true);\n setProgress(null);\n setMetrics(null);\n setUrl(null);\n setError(null);\n\n exportClips(videoUrl, segments, {\n ...options,\n signal: controller.signal,\n onProgress: (p) => setProgress(p),\n onComplete: (m) => setMetrics(m),\n }).then(({ blob }) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n setIsRendering(false);\n }).catch((err) => {\n if (err instanceof DOMException && err.name === 'AbortError') {\n setIsRendering(false);\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsRendering(false);\n });\n }, [videoUrl, segments, options, isRendering]);\n\n return { start, cancel, isRendering, progress, metrics, url, error };\n}\n","'use client';\n\nimport { useState, useCallback, useRef } from 'react';\nimport type { ClipSource, MergeOptions, RichProgress, RenderMetrics, FrameWorker } from '../types.js';\n\nexport interface UseMergeClipsState {\n progress: RichProgress;\n isRendering: boolean;\n error: Error | null;\n blob: Blob | null;\n url: string | null;\n metrics: RenderMetrics | null;\n}\n\nexport interface UseMergeClipsActions {\n mergeClips: (clips: ClipSource[], options?: Omit<MergeOptions, 'onProgress' | 'onComplete' | 'signal'>) => Promise<Blob | null>;\n cancel: () => void;\n reset: () => void;\n}\n\nexport type UseMergeClipsResult = UseMergeClipsState & UseMergeClipsActions;\n\nconst INITIAL_PROGRESS: RichProgress = { overall: 0, clips: [] };\n\nexport function useMergeClips(frameWorker: FrameWorker): UseMergeClipsResult {\n const [state, setState] = useState<UseMergeClipsState>({\n progress: INITIAL_PROGRESS,\n isRendering: false,\n error: null,\n blob: null,\n url: null,\n metrics: null,\n });\n\n const abortRef = useRef<AbortController | null>(null);\n const urlRef = useRef<string | null>(null);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const reset = useCallback(() => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n setState({ progress: INITIAL_PROGRESS, isRendering: false, error: null, blob: null, url: null, metrics: null });\n }, []);\n\n const mergeClips = useCallback(\n async (\n clips: ClipSource[],\n options?: Omit<MergeOptions, 'onProgress' | 'onComplete' | 'signal'>\n ): Promise<Blob | null> => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n setState({ progress: INITIAL_PROGRESS, isRendering: true, error: null, blob: null, url: null, metrics: null });\n\n try {\n const { blob, metrics } = await frameWorker.mergeClips(clips, {\n ...options,\n signal: controller.signal,\n onProgress: (p) => setState((prev) => ({ ...prev, progress: p })),\n onComplete: (m) => setState((prev) => ({ ...prev, metrics: m })),\n });\n\n const url = URL.createObjectURL(blob);\n urlRef.current = url;\n const doneProgress: RichProgress = {\n overall: 1,\n clips: clips.map((_, i) => ({ index: i, status: 'done', progress: 1 })),\n };\n setState((prev) => ({ ...prev, progress: doneProgress, isRendering: false, blob, url }));\n return blob;\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n setState((prev) => ({ ...prev, isRendering: false, error: null }));\n return null;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n setState((prev) => ({ ...prev, isRendering: false, error }));\n return null;\n }\n },\n [frameWorker]\n );\n\n return { ...state, mergeClips, cancel, reset };\n}\n","'use client';\n\n// ── usePreviewClip — wraps FrameWorker.render(clip) ──────────────────────────\n\nimport { useState, useCallback, useRef } from 'react';\nimport type { ClipSource, RenderOptions, FrameWorker } from '../types.js';\n\nexport interface UsePreviewClipState {\n progress: number;\n isRendering: boolean;\n error: Error | null;\n blob: Blob | null;\n url: string | null;\n}\n\nexport interface UsePreviewClipActions {\n render: (clip: ClipSource, options?: Omit<RenderOptions, 'onProgress' | 'signal'>) => Promise<Blob | null>;\n cancel: () => void;\n reset: () => void;\n}\n\nexport type UsePreviewClipResult = UsePreviewClipState & UsePreviewClipActions;\n\nexport function usePreviewClip(frameWorker: FrameWorker): UsePreviewClipResult {\n const [state, setState] = useState<UsePreviewClipState>({\n progress: 0,\n isRendering: false,\n error: null,\n blob: null,\n url: null,\n });\n\n const abortRef = useRef<AbortController | null>(null);\n const urlRef = useRef<string | null>(null);\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const reset = useCallback(() => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n setState({ progress: 0, isRendering: false, error: null, blob: null, url: null });\n }, []);\n\n const render = useCallback(\n async (\n clip: ClipSource,\n options?: Omit<RenderOptions, 'onProgress' | 'signal'>\n ): Promise<Blob | null> => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n setState({ progress: 0, isRendering: true, error: null, blob: null, url: null });\n\n try {\n const blob = await frameWorker.render(clip, {\n ...options,\n signal: controller.signal,\n onProgress: (p) => setState((prev) => ({ ...prev, progress: p })),\n });\n\n const url = URL.createObjectURL(blob);\n urlRef.current = url;\n setState({ progress: 1, isRendering: false, error: null, blob, url });\n return blob;\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') {\n setState((prev) => ({ ...prev, isRendering: false, error: null }));\n return null;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n setState((prev) => ({ ...prev, isRendering: false, error }));\n return null;\n }\n },\n [frameWorker]\n );\n\n return { ...state, render, cancel, reset };\n}\n\n// Deprecated aliases\n/** @deprecated Use usePreviewClip() */\nexport const useClipRender = usePreviewClip;\n/** @deprecated Use UsePreviewClipResult */\nexport type UseClipRenderResult = UsePreviewClipResult;\n/** @deprecated Use UsePreviewClipState */\nexport type UseClipRenderState = UsePreviewClipState;\n/** @deprecated Use UsePreviewClipActions */\nexport type UseClipRenderActions = UsePreviewClipActions;\n"]}
|