@swiftbrowser/react 0.28.0 → 0.28.2

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/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createContext, useContext, createElement, isValidElement, useMemo, useSyncExternalStore, useLayoutEffect, useRef, Fragment, useState, Children, useEffect, useId, cloneElement, useCallback } from "react";
2
2
  import { DefaultEventPriority, ConcurrentRoot } from "react-reconciler/constants";
3
3
  import Reconciler from "react-reconciler";
4
+ import { base64Decode, base64Encode } from "@swiftbrowser/web/src/base64";
5
+ import { addLiveBitmap, forgetLiveBitmap } from "@swiftbrowser/web/src/bitmaps";
4
6
  import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
5
7
  import { horizontalSizeClassFor } from "@swiftbrowser/web/src/protocol";
6
8
  import { BrowserHistory } from "@swiftbrowser/web/src/browserHistory";
@@ -16,7 +18,6 @@ import { createRoot } from "react-dom/client";
16
18
  import { flushSync } from "react-dom";
17
19
  import { ViewRegistry } from "@swiftbrowser/web/src/viewRegistry";
18
20
  import { chooseColor } from "@swiftbrowser/web/src/colorPicker";
19
- import { base64Encode } from "@swiftbrowser/web/src/base64";
20
21
  import { timingFor } from "@swiftbrowser/web/src/animation";
21
22
  const ViewEnvironmentContext = createContext({});
22
23
  function useViewEnvironment() {
@@ -63,6 +64,176 @@ function usePlatform() {
63
64
  function useRedacted() {
64
65
  return useContext(ViewEnvironmentContext).redacted === true;
65
66
  }
67
+ const registry = /* @__PURE__ */ new Map();
68
+ const byURL = /* @__PURE__ */ new Map();
69
+ function bitmapSource(file, options = {}) {
70
+ if (typeof file === "string") {
71
+ const known = byURL.get(file);
72
+ if (known !== void 0) return known;
73
+ const match = /^data:([^;,]*)(?:;[^,]*)?;base64,(.*)$/s.exec(file);
74
+ const source = match ? register(base64Decode(match[2]), match[2], { mime: match[1] || void 0, ...options }) : null;
75
+ byURL.set(file, source);
76
+ return source;
77
+ }
78
+ return register(file, null, options);
79
+ }
80
+ function register(bytes, base64, options) {
81
+ const header = imageHeader(bytes);
82
+ const width = options.width ?? header?.width;
83
+ const height = options.height ?? header?.height;
84
+ const mime = header?.mime ?? options.mime ?? "";
85
+ if (width === void 0 || height === void 0) return null;
86
+ const key = `f${fnv1a64(bytes)}`;
87
+ if (!registry.has(key)) registry.set(key, { op: "imagedata", key, width, height, mime, data: base64 ?? base64Encode(bytes) });
88
+ return { key, width, height, mime };
89
+ }
90
+ function liveBitmapSource(pixels, width, height) {
91
+ if (globalThis.__sbNative !== void 0) return null;
92
+ return { key: addLiveBitmap(pixels, width, height), width, height, mime: "" };
93
+ }
94
+ function releaseBitmapSource(source) {
95
+ if (source.key.startsWith("l")) forgetLiveBitmap(source.key);
96
+ }
97
+ function bitmapSourceOp(key) {
98
+ return registry.get(key);
99
+ }
100
+ function registerBitmapSourceOp(op) {
101
+ if (!registry.has(op.key)) registry.set(op.key, op);
102
+ }
103
+ function fnv1a64(bytes) {
104
+ let hi = 3421674724;
105
+ let lo = 2216829733;
106
+ for (let i = 0; i < bytes.length; i++) {
107
+ lo = (lo ^ bytes[i]) >>> 0;
108
+ const loMul = lo * 435;
109
+ const carry = Math.floor(loMul / 4294967296);
110
+ const newLo = loMul >>> 0;
111
+ const newHi = Math.imul(hi, 435) + carry + (lo << 8 >>> 0) >>> 0;
112
+ hi = newHi;
113
+ lo = newLo;
114
+ }
115
+ return hi.toString(16).padStart(8, "0") + lo.toString(16).padStart(8, "0");
116
+ }
117
+ function imageHeader(b) {
118
+ const u16 = (i, little = false) => little ? b[i] | b[i + 1] << 8 : b[i] << 8 | b[i + 1];
119
+ const u32 = (i) => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
120
+ if (b.length >= 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
121
+ return { mime: "image/png", width: u32(16), height: u32(20) };
122
+ }
123
+ if (b.length >= 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
124
+ return { mime: "image/gif", width: u16(6, true), height: u16(8, true) };
125
+ }
126
+ if (b.length >= 30 && b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) {
127
+ const chunk = String.fromCharCode(b[12], b[13], b[14], b[15]);
128
+ if (chunk === "VP8 ") return { mime: "image/webp", width: u16(26, true) & 16383, height: u16(28, true) & 16383 };
129
+ if (chunk === "VP8L") {
130
+ const bits = (b[21] | b[22] << 8 | b[23] << 16 | b[24] << 24) >>> 0;
131
+ return { mime: "image/webp", width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
132
+ }
133
+ if (chunk === "VP8X") return { mime: "image/webp", width: 1 + (b[24] | b[25] << 8 | b[26] << 16), height: 1 + (b[27] | b[28] << 8 | b[29] << 16) };
134
+ return null;
135
+ }
136
+ if (b.length >= 4 && b[0] === 255 && b[1] === 216) {
137
+ let orientation = 1;
138
+ let i = 2;
139
+ while (i + 4 <= b.length) {
140
+ if (b[i] !== 255) return null;
141
+ const marker = b[i + 1];
142
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
143
+ i += 2;
144
+ continue;
145
+ }
146
+ const length = u16(i + 2);
147
+ if (marker === 225 && length >= 16) orientation = exifOrientation(b, i + 4, length - 2) ?? orientation;
148
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
149
+ if (i + 9 > b.length) return null;
150
+ const height = u16(i + 5);
151
+ const width = u16(i + 7);
152
+ return orientation >= 5 && orientation <= 8 ? { mime: "image/jpeg", width: height, height: width } : { mime: "image/jpeg", width, height };
153
+ }
154
+ i += 2 + length;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ function exifOrientation(b, start, length) {
160
+ if (b[start] !== 69 || b[start + 1] !== 120 || b[start + 2] !== 105 || b[start + 3] !== 102) return null;
161
+ const tiff = start + 6;
162
+ const end = Math.min(b.length, start + length);
163
+ const little = b[tiff] === 73;
164
+ const u16 = (i) => little ? b[i] | b[i + 1] << 8 : b[i] << 8 | b[i + 1];
165
+ const u32 = (i) => little ? (b[i] | b[i + 1] << 8 | b[i + 2] << 16 | b[i + 3] << 24) >>> 0 : (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
166
+ if (tiff + 8 > end) return null;
167
+ const ifd = tiff + u32(tiff + 4);
168
+ if (ifd + 2 > end) return null;
169
+ const count = u16(ifd);
170
+ for (let n = 0; n < count; n++) {
171
+ const entry = ifd + 2 + n * 12;
172
+ if (entry + 12 > end) return null;
173
+ if (u16(entry) === 274) return u16(entry + 8);
174
+ }
175
+ return null;
176
+ }
177
+ function bitmapExtent(source, steps = []) {
178
+ let r2 = source ? { x: 0, y: 0, width: source.width, height: source.height } : { x: 0, y: 0, width: 0, height: 0 };
179
+ const infinite = () => !Number.isFinite(r2.width) || !Number.isFinite(r2.height);
180
+ for (const step of steps) {
181
+ switch (step.op) {
182
+ case "crop": {
183
+ if (infinite()) {
184
+ r2 = { x: step.x, y: step.y, width: step.width, height: step.height };
185
+ break;
186
+ }
187
+ const x0 = Math.max(r2.x, step.x);
188
+ const y0 = Math.max(r2.y, step.y);
189
+ const x1 = Math.min(r2.x + r2.width, step.x + step.width);
190
+ const y1 = Math.min(r2.y + r2.height, step.y + step.height);
191
+ r2 = { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
192
+ break;
193
+ }
194
+ case "render":
195
+ r2 = { x: 0, y: 0, width: step.width, height: step.height };
196
+ break;
197
+ case "clamp":
198
+ r2 = { x: -Infinity, y: -Infinity, width: Infinity, height: Infinity };
199
+ break;
200
+ case "gaussianBlur": {
201
+ if (infinite()) break;
202
+ const grow = Math.ceil(3 * step.radius);
203
+ r2 = { x: r2.x - grow, y: r2.y - grow, width: r2.width + 2 * grow, height: r2.height + 2 * grow };
204
+ break;
205
+ }
206
+ case "place":
207
+ if (!(step.sx > 0) || !(step.sy > 0)) r2 = { x: step.tx, y: step.ty, width: 0, height: 0 };
208
+ else if (!infinite()) r2 = { x: r2.x * step.sx + step.tx, y: r2.y * step.sy + step.ty, width: r2.width * step.sx, height: r2.height * step.sy };
209
+ break;
210
+ case "transform": {
211
+ if (infinite()) break;
212
+ const { a, b, c, d, tx, ty } = step;
213
+ if (!(Math.abs(a * d - b * c) > 1e-12)) {
214
+ r2 = { x: tx, y: ty, width: 0, height: 0 };
215
+ break;
216
+ }
217
+ const xs = [];
218
+ const ys = [];
219
+ for (const [x, y] of [
220
+ [r2.x, r2.y],
221
+ [r2.x + r2.width, r2.y],
222
+ [r2.x, r2.y + r2.height],
223
+ [r2.x + r2.width, r2.y + r2.height]
224
+ ]) {
225
+ xs.push(a * x + c * y + tx);
226
+ ys.push(b * x + d * y + ty);
227
+ }
228
+ const x0 = Math.min(...xs);
229
+ const y0 = Math.min(...ys);
230
+ r2 = { x: x0, y: y0, width: Math.max(...xs) - x0, height: Math.max(...ys) - y0 };
231
+ break;
232
+ }
233
+ }
234
+ }
235
+ return r2;
236
+ }
66
237
  const PREF_KIND = "$pref";
67
238
  const HOIST_KIND = "$hoist";
68
239
  function inheritTitleModes(screens) {
@@ -122,6 +293,8 @@ class HostTree {
122
293
  committedColorScheme;
123
294
  /** Remote: the root's props last sent. */
124
295
  committedRootProps = {};
296
+ /** The bitmap sources (`imagedata` keys) this tree has sent: each goes once, before the first image that draws it. */
297
+ sentSources = /* @__PURE__ */ new Set();
125
298
  /** Whether another tree draws this one (`createSwiftUIRoot(sink, { remote: true })`, for a `RemoteTree`). */
126
299
  get remote() {
127
300
  return this.options.remote === true;
@@ -221,6 +394,7 @@ class HostTree {
221
394
  };
222
395
  const visit = (child, sink, owner, ctx, hoistedTo) => {
223
396
  if (child.hidden) return;
397
+ if (child.kind === PREF_KIND || child.kind === HOIST_KIND) child.stale = false;
224
398
  if (child.kind === PREF_KIND) {
225
399
  contribute({ kind: "pref", target: child.target, props: child.props }, ctx);
226
400
  return;
@@ -303,6 +477,7 @@ class HostTree {
303
477
  const current = parent.committedChildren.filter((child) => child.shown && child.effectiveParent === parent);
304
478
  entry.children.forEach((child, index) => {
305
479
  const childEntry = child.entry;
480
+ if (child.kind === "image") this.sendBitmapSource(childEntry.props, ops);
306
481
  if (!child.created) {
307
482
  ops.push({ op: "create", id: child.id, kind: child.kind, props: childEntry.props });
308
483
  child.created = true;
@@ -352,6 +527,15 @@ class HostTree {
352
527
  ops.push(commit);
353
528
  return ops;
354
529
  }
530
+ /** An `imagedata` op ahead of an image whose recipe's source this tree has not sent yet (bitmaps.ts). */
531
+ sendBitmapSource(props, ops) {
532
+ const key = props["bitmap"]?.source;
533
+ if (typeof key !== "string" || this.sentSources.has(key)) return;
534
+ const op = bitmapSourceOp(key);
535
+ if (!op) return;
536
+ this.sentSources.add(key);
537
+ ops.push(op);
538
+ }
355
539
  /** Whether a pass is still animating with this completion serial. */
356
540
  awaitsCompletion(serial) {
357
541
  return this.completions.has(serial);
@@ -670,6 +854,9 @@ function createRemoteRoot() {
670
854
  case "commit":
671
855
  commit = op;
672
856
  break;
857
+ case "imagedata":
858
+ registerBitmapSourceOp(op);
859
+ break;
673
860
  }
674
861
  }
675
862
  if (!commit) return;
@@ -2138,6 +2325,21 @@ function openURL(url) {
2138
2325
  }
2139
2326
  function imageProps$1(props) {
2140
2327
  if (props.systemName !== void 0) return { systemName: props.systemName };
2328
+ if (props.bitmap !== void 0) {
2329
+ const { source, steps = [], version } = props.bitmap;
2330
+ const extent = bitmapExtent(source, steps);
2331
+ const finite = Number.isFinite(extent.width) && Number.isFinite(extent.height);
2332
+ const width = finite ? Math.max(0, Math.round(extent.width)) : 0;
2333
+ const height = finite ? Math.max(0, Math.round(extent.height)) : 0;
2334
+ const scale = props.scale !== void 0 && props.scale > 0 ? props.scale : 1;
2335
+ return {
2336
+ bitmap: { source: source?.key ?? null, steps, width, height, ...version !== void 0 ? { version } : {} },
2337
+ resizable: props.resizable ?? false,
2338
+ width: width / scale,
2339
+ height: height / scale,
2340
+ ...props.interpolation === "none" ? { interpolation: "none" } : {}
2341
+ };
2342
+ }
2141
2343
  if (props.url !== void 0) return { url: props.url, resizable: props.resizable ?? false, width: props.width ?? 0, height: props.height ?? 0 };
2142
2344
  return { name: props.name ?? "", resizable: props.resizable ?? false };
2143
2345
  }
@@ -4036,7 +4238,7 @@ function protocolRuns(text) {
4036
4238
  }
4037
4239
  function useAudioRecorder(options = {}) {
4038
4240
  const interval = options.meterInterval ?? 50;
4039
- const host = browserEnvironment.audioRecorder;
4241
+ const host2 = browserEnvironment.audioRecorder;
4040
4242
  const [status, setStatus] = useState("idle");
4041
4243
  const [time, setTime] = useState(0);
4042
4244
  const [levels, setLevels] = useState([]);
@@ -4045,13 +4247,13 @@ function useAudioRecorder(options = {}) {
4045
4247
  statusRef.current = status;
4046
4248
  const fail = (reason) => setError(reason instanceof Error ? reason.message : String(reason));
4047
4249
  useEffect(() => {
4048
- if (status !== "recording" || !host) return;
4250
+ if (status !== "recording" || !host2) return;
4049
4251
  let reading = false;
4050
4252
  let live = true;
4051
4253
  const timer = setInterval(() => {
4052
4254
  if (reading) return;
4053
4255
  reading = true;
4054
- host.meter().then((meter) => {
4256
+ host2.meter().then((meter) => {
4055
4257
  if (!live) return;
4056
4258
  setTime(meter.time);
4057
4259
  setLevels((previous) => [...previous, meter.level]);
@@ -4063,15 +4265,15 @@ function useAudioRecorder(options = {}) {
4063
4265
  live = false;
4064
4266
  clearInterval(timer);
4065
4267
  };
4066
- }, [status, host, interval]);
4268
+ }, [status, host2, interval]);
4067
4269
  useEffect(
4068
4270
  () => () => {
4069
- if (statusRef.current !== "idle") void host?.cancel().catch(() => void 0);
4271
+ if (statusRef.current !== "idle") void host2?.cancel().catch(() => void 0);
4070
4272
  },
4071
- [host]
4273
+ [host2]
4072
4274
  );
4073
4275
  const record = useCallback(async () => {
4074
- if (!host) {
4276
+ if (!host2) {
4075
4277
  setError("unsupported: this page cannot record");
4076
4278
  return;
4077
4279
  }
@@ -4081,28 +4283,28 @@ function useAudioRecorder(options = {}) {
4081
4283
  setLevels([]);
4082
4284
  setStatus("starting");
4083
4285
  try {
4084
- await host.start();
4286
+ await host2.start();
4085
4287
  setStatus("recording");
4086
4288
  } catch (reason) {
4087
4289
  fail(reason);
4088
4290
  setStatus("idle");
4089
4291
  }
4090
- }, [host]);
4292
+ }, [host2]);
4091
4293
  const pause = useCallback(async () => {
4092
- if (!host || statusRef.current !== "recording") return;
4093
- await host.pause().catch(fail);
4294
+ if (!host2 || statusRef.current !== "recording") return;
4295
+ await host2.pause().catch(fail);
4094
4296
  setStatus("paused");
4095
- }, [host]);
4297
+ }, [host2]);
4096
4298
  const resume = useCallback(async () => {
4097
- if (!host || statusRef.current !== "paused") return;
4098
- await host.resume().catch(fail);
4299
+ if (!host2 || statusRef.current !== "paused") return;
4300
+ await host2.resume().catch(fail);
4099
4301
  setStatus("recording");
4100
- }, [host]);
4302
+ }, [host2]);
4101
4303
  const stop = useCallback(async () => {
4102
- if (!host || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
4304
+ if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
4103
4305
  setStatus("stopping");
4104
4306
  try {
4105
- const recording = await host.stop();
4307
+ const recording = await host2.stop();
4106
4308
  setTime(recording.duration);
4107
4309
  return recording;
4108
4310
  } catch (reason) {
@@ -4111,30 +4313,538 @@ function useAudioRecorder(options = {}) {
4111
4313
  } finally {
4112
4314
  setStatus("idle");
4113
4315
  }
4114
- }, [host]);
4316
+ }, [host2]);
4115
4317
  const cancel = useCallback(async () => {
4116
- if (!host || statusRef.current === "idle") return;
4117
- await host.cancel().catch(fail);
4318
+ if (!host2 || statusRef.current === "idle") return;
4319
+ await host2.cancel().catch(fail);
4118
4320
  setStatus("idle");
4119
4321
  setTime(0);
4120
4322
  setLevels([]);
4121
- }, [host]);
4323
+ }, [host2]);
4122
4324
  const discard = useCallback(async (url) => {
4123
- await host?.discard(url).catch(fail);
4124
- }, [host]);
4325
+ await host2?.discard(url).catch(fail);
4326
+ }, [host2]);
4125
4327
  const trim = useCallback(
4126
4328
  async (url, keep) => {
4127
- if (!host) throw new Error("unsupported: this page cannot cut audio");
4329
+ if (!host2) throw new Error("unsupported: this page cannot cut audio");
4128
4330
  try {
4129
- return await host.trim(url, keep);
4331
+ return await host2.trim(url, keep);
4130
4332
  } catch (reason) {
4131
4333
  fail(reason);
4132
4334
  throw reason;
4133
4335
  }
4134
4336
  },
4135
- [host]
4337
+ [host2]
4338
+ );
4339
+ return { available: host2 !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
4340
+ }
4341
+ function host() {
4342
+ const found = browserEnvironment.avAsset;
4343
+ if (!found) throw new Error("unsupported: this page cannot read or write video");
4344
+ return found;
4345
+ }
4346
+ class CMTime {
4347
+ constructor(value, timescale) {
4348
+ this.value = value;
4349
+ this.timescale = timescale;
4350
+ }
4351
+ value;
4352
+ timescale;
4353
+ static zero = new CMTime(0, 1);
4354
+ static invalid = new CMTime(0, 0);
4355
+ static positiveInfinity = new CMTime(Infinity, 1);
4356
+ /** `CMTime(value:timescale:)`. */
4357
+ static make(value, timescale) {
4358
+ return new CMTime(value, timescale);
4359
+ }
4360
+ /** `CMTime(seconds:preferredTimescale:)`: rounded to the timescale. */
4361
+ static seconds(seconds, preferredTimescale = 600) {
4362
+ if (!Number.isFinite(seconds)) return seconds > 0 ? CMTime.positiveInfinity : CMTime.invalid;
4363
+ return new CMTime(Math.round(seconds * preferredTimescale), preferredTimescale);
4364
+ }
4365
+ get isValid() {
4366
+ return this.timescale > 0;
4367
+ }
4368
+ /** `CMTimeGetSeconds`. */
4369
+ get seconds() {
4370
+ return this.timescale > 0 ? this.value / this.timescale : Number.NaN;
4371
+ }
4372
+ add(other) {
4373
+ return CMTime.seconds(this.seconds + other.seconds, Math.max(this.timescale, other.timescale, 1));
4374
+ }
4375
+ subtract(other) {
4376
+ return CMTime.seconds(this.seconds - other.seconds, Math.max(this.timescale, other.timescale, 1));
4377
+ }
4378
+ /** `CMTimeCompare`: -1, 0 or 1. */
4379
+ compare(other) {
4380
+ const a = this.seconds;
4381
+ const b = other.seconds;
4382
+ return a < b ? -1 : a > b ? 1 : 0;
4383
+ }
4384
+ static minimum(a, b) {
4385
+ return a.compare(b) <= 0 ? a : b;
4386
+ }
4387
+ static maximum(a, b) {
4388
+ return a.compare(b) >= 0 ? a : b;
4389
+ }
4390
+ }
4391
+ class CMTimeRange {
4392
+ constructor(start, duration) {
4393
+ this.start = start;
4394
+ this.duration = duration;
4395
+ }
4396
+ start;
4397
+ duration;
4398
+ static zero = new CMTimeRange(CMTime.zero, CMTime.zero);
4399
+ /** `CMTimeRange(start:end:)`. */
4400
+ static fromTo(start, end) {
4401
+ return new CMTimeRange(start, CMTime.maximum(end.subtract(start), CMTime.zero));
4402
+ }
4403
+ /** `CMTimeRange(start:duration:)` in seconds. */
4404
+ static seconds(start, duration, preferredTimescale = 600) {
4405
+ return new CMTimeRange(CMTime.seconds(start, preferredTimescale), CMTime.seconds(duration, preferredTimescale));
4406
+ }
4407
+ get end() {
4408
+ return this.start.add(this.duration);
4409
+ }
4410
+ get isEmpty() {
4411
+ return this.duration.seconds <= 0;
4412
+ }
4413
+ containsTime(time) {
4414
+ return time.compare(this.start) >= 0 && time.compare(this.end) < 0;
4415
+ }
4416
+ }
4417
+ const AVMediaType = { video: "vide", audio: "soun" };
4418
+ class AVAssetTrack {
4419
+ constructor(trackID, mediaType, naturalSize, nominalFrameRate, timeRange, codec) {
4420
+ this.trackID = trackID;
4421
+ this.mediaType = mediaType;
4422
+ this.naturalSize = naturalSize;
4423
+ this.nominalFrameRate = nominalFrameRate;
4424
+ this.timeRange = timeRange;
4425
+ this.codec = codec;
4426
+ }
4427
+ trackID;
4428
+ mediaType;
4429
+ naturalSize;
4430
+ nominalFrameRate;
4431
+ timeRange;
4432
+ codec;
4433
+ }
4434
+ class AVAsset {
4435
+ /** @internal */
4436
+ segmentsForExport() {
4437
+ return this.segments();
4438
+ }
4439
+ /** `loadTracks(withMediaType:)`. */
4440
+ async loadTracks(mediaType) {
4441
+ return (await this.load("tracks")).filter((track) => track.mediaType === mediaType);
4442
+ }
4443
+ }
4444
+ const tracksOf = (info) => info.tracks.map(
4445
+ (t) => new AVAssetTrack(
4446
+ t.trackID,
4447
+ t.mediaType === "video" ? AVMediaType.video : AVMediaType.audio,
4448
+ t.naturalSize,
4449
+ t.nominalFrameRate,
4450
+ CMTimeRange.seconds(t.timeRange.start, t.timeRange.duration),
4451
+ t.codec
4452
+ )
4453
+ );
4454
+ class AVURLAsset extends AVAsset {
4455
+ constructor(url) {
4456
+ super();
4457
+ this.url = url;
4458
+ }
4459
+ url;
4460
+ info = null;
4461
+ loaded() {
4462
+ if (!this.info) {
4463
+ this.info = host().load(this.url);
4464
+ this.info.catch(() => {
4465
+ this.info = null;
4466
+ });
4467
+ }
4468
+ return this.info;
4469
+ }
4470
+ async segments() {
4471
+ return [{ url: this.url, start: 0, end: (await this.loaded()).duration }];
4472
+ }
4473
+ async load(key) {
4474
+ const info = await this.loaded();
4475
+ const values = {
4476
+ duration: CMTime.seconds(info.duration),
4477
+ tracks: tracksOf(info),
4478
+ naturalSize: info.naturalSize,
4479
+ isPlayable: info.tracks.length > 0,
4480
+ isExportable: info.tracks.length > 0
4481
+ };
4482
+ return values[key];
4483
+ }
4484
+ }
4485
+ class AVMutableComposition extends AVAsset {
4486
+ parts = [];
4487
+ /** The composition's length. */
4488
+ get duration() {
4489
+ return CMTime.seconds(this.parts.reduce((sum, part) => sum + part.range.duration.seconds, 0));
4490
+ }
4491
+ /** `insertTimeRange(_:of:at:)`: `range` of `asset` (an `AVURLAsset`, or another composition's parts) placed at `at`. */
4492
+ async insertTimeRange(range, asset, at) {
4493
+ const inserted = [];
4494
+ if (asset instanceof AVURLAsset) {
4495
+ const length = (await asset.load("duration")).seconds;
4496
+ const start = Math.max(0, range.start.seconds);
4497
+ const end = Math.min(length, range.end.seconds);
4498
+ if (end - start > 1e-3) inserted.push({ asset, range: CMTimeRange.seconds(start, end - start) });
4499
+ } else if (asset instanceof AVMutableComposition) {
4500
+ inserted.push(...asset.partsIn(range.start.seconds, range.end.seconds));
4501
+ } else {
4502
+ throw new Error("unsupported: only an AVURLAsset or an AVMutableComposition can be inserted");
4503
+ }
4504
+ const time = Math.max(0, Math.min(at.seconds, this.duration.seconds));
4505
+ const [before, after] = this.splitAt(time);
4506
+ this.parts = [...before, ...inserted, ...after];
4507
+ }
4508
+ /** `removeTimeRange(_:)`: the media in `range` cut out, the rest closing up. */
4509
+ removeTimeRange(range) {
4510
+ const [before] = this.splitAt(range.start.seconds);
4511
+ const [, after] = this.splitAt(range.end.seconds);
4512
+ this.parts = [...before, ...after];
4513
+ }
4514
+ /** The parts' media from `start` to `end` of the composition's timeline. */
4515
+ partsIn(start, end) {
4516
+ const [, from] = this.splitAt(start);
4517
+ const out = [];
4518
+ let cursor = start;
4519
+ for (const part of from) {
4520
+ if (cursor >= end) break;
4521
+ const take = Math.min(part.range.duration.seconds, end - cursor);
4522
+ if (take > 1e-3) out.push({ asset: part.asset, range: CMTimeRange.seconds(part.range.start.seconds, take) });
4523
+ cursor += take;
4524
+ }
4525
+ return out;
4526
+ }
4527
+ /** The parts before `time` and from it, a part across it split in two. */
4528
+ splitAt(time) {
4529
+ const before = [];
4530
+ const after = [];
4531
+ let cursor = 0;
4532
+ for (const part of this.parts) {
4533
+ const length = part.range.duration.seconds;
4534
+ const start = part.range.start.seconds;
4535
+ if (cursor + length <= time + 1e-9) before.push(part);
4536
+ else if (cursor >= time - 1e-9) after.push(part);
4537
+ else {
4538
+ const head = time - cursor;
4539
+ before.push({ asset: part.asset, range: CMTimeRange.seconds(start, head) });
4540
+ after.push({ asset: part.asset, range: CMTimeRange.seconds(start + head, length - head) });
4541
+ }
4542
+ cursor += length;
4543
+ }
4544
+ return [before, after];
4545
+ }
4546
+ async segments() {
4547
+ return this.parts.map((part) => ({ url: part.asset.url, start: part.range.start.seconds, end: part.range.end.seconds }));
4548
+ }
4549
+ async load(key) {
4550
+ const first = this.parts[0]?.asset;
4551
+ const tracks = first ? await first.load("tracks") : [];
4552
+ const values = {
4553
+ duration: this.duration,
4554
+ tracks: tracks.map((t) => new AVAssetTrack(t.trackID, t.mediaType, t.naturalSize, t.nominalFrameRate, new CMTimeRange(CMTime.zero, this.duration), t.codec)),
4555
+ naturalSize: first ? await first.load("naturalSize") : { width: 0, height: 0 },
4556
+ isPlayable: this.parts.length > 0,
4557
+ isExportable: this.parts.length > 0
4558
+ };
4559
+ return values[key];
4560
+ }
4561
+ }
4562
+ class AVAssetImageGenerator {
4563
+ constructor(asset) {
4564
+ this.asset = asset;
4565
+ }
4566
+ asset;
4567
+ maximumSize = { width: 0, height: 0 };
4568
+ /** Stills come upright (the track's rotation applied), as with `appliesPreferredTrackTransform = true`. */
4569
+ appliesPreferredTrackTransform = true;
4570
+ made = [];
4571
+ /** The file and the time in it that `time` on the asset's timeline falls on (a composition's part). */
4572
+ async source(time) {
4573
+ let cursor = 0;
4574
+ const segments = await this.asset.segmentsForExport();
4575
+ for (const segment of segments) {
4576
+ const length = segment.end - segment.start;
4577
+ if (time < cursor + length || segment === segments[segments.length - 1]) return { url: segment.url, time: segment.start + Math.max(0, Math.min(length, time - cursor)) };
4578
+ cursor += length;
4579
+ }
4580
+ throw new Error("empty: the asset has no media");
4581
+ }
4582
+ /** `image(at:)`: the still at `time`. */
4583
+ async image(at) {
4584
+ const { url, time } = await this.source(Math.max(0, at.seconds));
4585
+ const options = {
4586
+ ...this.maximumSize.width > 0 ? { maxWidth: this.maximumSize.width } : {},
4587
+ ...this.maximumSize.height > 0 ? { maxHeight: this.maximumSize.height } : {}
4588
+ };
4589
+ const still = await host().image(url, time, options);
4590
+ this.made.push(still.url);
4591
+ return { image: still.url, actualTime: CMTime.seconds(at.seconds + (still.actualTime - time)), width: still.width, height: still.height };
4592
+ }
4593
+ /** `images(for:)`: a still for each time, in turn. */
4594
+ async *images(times) {
4595
+ for (const time of times) yield { ...await this.image(time), requestedTime: time };
4596
+ }
4597
+ /** Lets go of the stills this generator made (their `blob:` URLs on a page). */
4598
+ async release() {
4599
+ const urls = this.made.splice(0);
4600
+ await Promise.all(urls.map((url) => host().release(url)));
4601
+ }
4602
+ }
4603
+ const AVFileType = { mp4: "public.mpeg-4", mov: "com.apple.quicktime-movie", m4v: "com.apple.m4v-video", webm: "org.webmproject.webm" };
4604
+ const FILE_TYPES = {
4605
+ "public.mpeg-4": "mp4",
4606
+ "com.apple.m4v-video": "mp4",
4607
+ "com.apple.quicktime-movie": "mov",
4608
+ "org.webmproject.webm": "webm"
4609
+ };
4610
+ const AVAssetExportPresetPassthrough = "AVAssetExportPresetPassthrough";
4611
+ const AVAssetExportPresetHighestQuality = "AVAssetExportPresetHighestQuality";
4612
+ const AVAssetExportPresetMediumQuality = "AVAssetExportPresetMediumQuality";
4613
+ const AVAssetExportPresetLowQuality = "AVAssetExportPresetLowQuality";
4614
+ const AVAssetExportPreset640x480 = "AVAssetExportPreset640x480";
4615
+ const AVAssetExportPreset960x540 = "AVAssetExportPreset960x540";
4616
+ const AVAssetExportPreset1280x720 = "AVAssetExportPreset1280x720";
4617
+ const AVAssetExportPreset1920x1080 = "AVAssetExportPreset1920x1080";
4618
+ const AVAssetExportPreset3840x2160 = "AVAssetExportPreset3840x2160";
4619
+ let exports$1 = 0;
4620
+ class AVAssetExportSession {
4621
+ constructor(asset, presetName) {
4622
+ this.asset = asset;
4623
+ this.presetName = presetName;
4624
+ }
4625
+ asset;
4626
+ presetName;
4627
+ outputURL = null;
4628
+ outputFileType = AVFileType.mp4;
4629
+ timeRange = null;
4630
+ status = "unknown";
4631
+ progress = 0;
4632
+ error = null;
4633
+ /** Heard as `progress` changes (a few times a second). */
4634
+ onProgress = null;
4635
+ /** The written file's length and MIME type, once completed. */
4636
+ result = null;
4637
+ id = `export-${++exports$1}`;
4638
+ /** `AVAssetExportSession.allExportPresets()`. */
4639
+ static allExportPresets() {
4640
+ return [
4641
+ AVAssetExportPresetPassthrough,
4642
+ AVAssetExportPresetHighestQuality,
4643
+ AVAssetExportPresetMediumQuality,
4644
+ AVAssetExportPresetLowQuality,
4645
+ AVAssetExportPreset640x480,
4646
+ AVAssetExportPreset960x540,
4647
+ AVAssetExportPreset1280x720,
4648
+ AVAssetExportPreset1920x1080,
4649
+ AVAssetExportPreset3840x2160
4650
+ ];
4651
+ }
4652
+ /** The asset's parts, cut to `timeRange` when it is set. */
4653
+ async parts() {
4654
+ const all = await this.asset.segmentsForExport();
4655
+ if (!this.timeRange) return all;
4656
+ const from = this.timeRange.start.seconds;
4657
+ const to = this.timeRange.end.seconds;
4658
+ const out = [];
4659
+ let cursor = 0;
4660
+ for (const segment of all) {
4661
+ const length = segment.end - segment.start;
4662
+ const start = Math.max(from, cursor);
4663
+ const end = Math.min(to, cursor + length);
4664
+ if (end - start > 1e-3) out.push({ url: segment.url, start: segment.start + (start - cursor), end: segment.start + (end - cursor) });
4665
+ cursor += length;
4666
+ }
4667
+ return out;
4668
+ }
4669
+ /** `export()`: writes the file and answers its URL (also `outputURL`); rejects with why it could not. */
4670
+ async export() {
4671
+ if (this.status === "exporting") throw new Error("busy: already exporting");
4672
+ this.status = "exporting";
4673
+ this.error = null;
4674
+ this.setProgress(0);
4675
+ const assets = host();
4676
+ const ticker = setInterval(() => {
4677
+ void assets.progress(this.id).then((p) => this.status === "exporting" && this.setProgress(p));
4678
+ }, 200);
4679
+ try {
4680
+ const result = await assets.export(this.id, {
4681
+ segments: await this.parts(),
4682
+ preset: this.presetName,
4683
+ fileType: FILE_TYPES[this.outputFileType] ?? "mp4",
4684
+ ...this.outputURL ? { outputURL: this.outputURL } : {}
4685
+ });
4686
+ this.outputURL = result.url;
4687
+ this.result = { duration: result.duration, type: result.type };
4688
+ this.status = "completed";
4689
+ this.setProgress(1);
4690
+ return result.url;
4691
+ } catch (reason) {
4692
+ const error = reason instanceof Error ? reason : new Error(String(reason));
4693
+ this.error = error;
4694
+ this.status = error.message.startsWith("cancelled") ? "cancelled" : "failed";
4695
+ throw error;
4696
+ } finally {
4697
+ clearInterval(ticker);
4698
+ }
4699
+ }
4700
+ /** `cancelExport()`. */
4701
+ cancelExport() {
4702
+ if (this.status !== "exporting") return;
4703
+ void host().cancel(this.id);
4704
+ }
4705
+ setProgress(progress) {
4706
+ if (progress === this.progress) return;
4707
+ this.progress = progress;
4708
+ this.onProgress?.(progress);
4709
+ }
4710
+ }
4711
+ function useVideoRecorder(options = {}) {
4712
+ const interval = options.timeInterval ?? 100;
4713
+ const host2 = browserEnvironment.videoRecorder;
4714
+ const [status, setStatus] = useState("closed");
4715
+ const [stream, setStream] = useState(null);
4716
+ const [facing, setFacing] = useState("user");
4717
+ const [time, setTime] = useState(0);
4718
+ const [error, setError] = useState(null);
4719
+ const statusRef = useRef(status);
4720
+ statusRef.current = status;
4721
+ const facingRef = useRef(facing);
4722
+ facingRef.current = facing;
4723
+ const fail = (reason) => setError(reason instanceof Error ? reason.message : String(reason));
4724
+ useEffect(() => {
4725
+ if (status !== "recording" || !host2) return;
4726
+ let reading = false;
4727
+ let live = true;
4728
+ const timer = setInterval(() => {
4729
+ if (reading) return;
4730
+ reading = true;
4731
+ host2.time().then((seconds) => live && setTime(seconds)).catch(() => void 0).finally(() => {
4732
+ reading = false;
4733
+ });
4734
+ }, interval);
4735
+ return () => {
4736
+ live = false;
4737
+ clearInterval(timer);
4738
+ };
4739
+ }, [status, host2, interval]);
4740
+ useEffect(
4741
+ () => () => {
4742
+ if (statusRef.current !== "closed") void host2?.close().catch(() => void 0);
4743
+ },
4744
+ [host2]
4745
+ );
4746
+ const open = useCallback(
4747
+ async (wanted) => {
4748
+ if (!host2) {
4749
+ setError("unsupported: this page cannot record video");
4750
+ return;
4751
+ }
4752
+ if (statusRef.current === "recording" || statusRef.current === "paused" || statusRef.current === "stopping") return;
4753
+ setError(null);
4754
+ setStatus("opening");
4755
+ try {
4756
+ const opened = await host2.open({ facing: wanted ?? facingRef.current });
4757
+ setStream(opened.stream);
4758
+ setFacing(opened.facing);
4759
+ setStatus("ready");
4760
+ } catch (reason) {
4761
+ fail(reason);
4762
+ setStream(null);
4763
+ setStatus("closed");
4764
+ }
4765
+ },
4766
+ [host2]
4767
+ );
4768
+ const close = useCallback(async () => {
4769
+ if (!host2) return;
4770
+ await host2.close().catch(fail);
4771
+ setStream(null);
4772
+ setStatus("closed");
4773
+ setTime(0);
4774
+ }, [host2]);
4775
+ const flip = useCallback(async () => {
4776
+ if (statusRef.current !== "ready") return;
4777
+ await open(facingRef.current === "user" ? "environment" : "user");
4778
+ }, [open]);
4779
+ const record = useCallback(async () => {
4780
+ if (!host2 || statusRef.current !== "ready") return;
4781
+ setError(null);
4782
+ setTime(0);
4783
+ try {
4784
+ await host2.start();
4785
+ setStatus("recording");
4786
+ } catch (reason) {
4787
+ fail(reason);
4788
+ }
4789
+ }, [host2]);
4790
+ const pause = useCallback(async () => {
4791
+ if (!host2 || statusRef.current !== "recording") return;
4792
+ await host2.pause().catch(fail);
4793
+ setStatus("paused");
4794
+ }, [host2]);
4795
+ const resume = useCallback(async () => {
4796
+ if (!host2 || statusRef.current !== "paused") return;
4797
+ await host2.resume().catch(fail);
4798
+ setStatus("recording");
4799
+ }, [host2]);
4800
+ const stop = useCallback(async () => {
4801
+ if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
4802
+ setStatus("stopping");
4803
+ try {
4804
+ const recording = await host2.stop();
4805
+ setTime(recording.duration);
4806
+ return recording;
4807
+ } catch (reason) {
4808
+ fail(reason);
4809
+ return null;
4810
+ } finally {
4811
+ setStatus("ready");
4812
+ }
4813
+ }, [host2]);
4814
+ const cancel = useCallback(async () => {
4815
+ if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return;
4816
+ await host2.cancel().catch(fail);
4817
+ setStatus("ready");
4818
+ setTime(0);
4819
+ }, [host2]);
4820
+ const discard = useCallback(
4821
+ async (url) => {
4822
+ await host2?.discard(url).catch(fail);
4823
+ },
4824
+ [host2]
4825
+ );
4826
+ return { available: host2 !== void 0, status, stream, facing, time, error, open, close, flip, record, pause, resume, stop, cancel, discard };
4827
+ }
4828
+ function CameraPreview(props) {
4829
+ const { own, modifiers } = splitProps(props);
4830
+ const stream = own.recorder.stream;
4831
+ if (!stream) return applyModifiers(sb("color", { color: { r: 0, g: 0, b: 0, a: 1 } }), modifiers);
4832
+ return applyModifiers(
4833
+ sb("video", {
4834
+ url: null,
4835
+ playing: false,
4836
+ muted: true,
4837
+ volume: 0,
4838
+ rate: 1,
4839
+ loop: false,
4840
+ gravity: own.gravity ?? "resizeAspectFill",
4841
+ controls: false,
4842
+ seek: { time: 0, serial: 0 },
4843
+ stream,
4844
+ mirrored: own.mirrored ?? own.recorder.facing === "user"
4845
+ }),
4846
+ modifiers
4136
4847
  );
4137
- return { available: host !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
4138
4848
  }
4139
4849
  let nextId$1 = 1;
4140
4850
  const newId = () => nextId$1++;
@@ -4143,9 +4853,9 @@ function unsupported() {
4143
4853
  return new Error("unsupported: this page or app has no audio engine");
4144
4854
  }
4145
4855
  function perform$1(command2) {
4146
- const host = hostNow$1();
4147
- if (!host) return Promise.reject(unsupported());
4148
- return host.perform(command2);
4856
+ const host2 = hostNow$1();
4857
+ if (!host2) return Promise.reject(unsupported());
4858
+ return host2.perform(command2);
4149
4859
  }
4150
4860
  class AVAudioTime {
4151
4861
  spec;
@@ -4751,9 +5461,9 @@ function useAudioEngine(build) {
4751
5461
  let nextId = 1;
4752
5462
  const hostNow = () => browserEnvironment.languageModel;
4753
5463
  function perform(command2) {
4754
- const host = hostNow();
4755
- if (!host) return Promise.reject(new GenerationError("assetsUnavailable", "This page or app has no language model."));
4756
- return host.perform(command2).catch((error) => {
5464
+ const host2 = hostNow();
5465
+ if (!host2) return Promise.reject(new GenerationError("assetsUnavailable", "This page or app has no language model."));
5466
+ return host2.perform(command2).catch((error) => {
4757
5467
  throw GenerationError.from(error);
4758
5468
  });
4759
5469
  }
@@ -5236,8 +5946,9 @@ function ProgressView(props) {
5236
5946
  }
5237
5947
  function ToolbarItem(props) {
5238
5948
  const hidden = props.sharedBackgroundVisibility === "hidden";
5239
- const items = withEnvironment({ labelStyle: "iconOnly" }, props.children);
5240
- return sb("toolbar", hidden ? { placement: toolbarPlacement(props.placement), sharedBackground: "hidden" } : { placement: toolbarPlacement(props.placement) }, void 0, items);
5949
+ const placement = toolbarPlacement(props.placement);
5950
+ const items = placement === "secondary" ? props.children : withEnvironment({ labelStyle: "iconOnly" }, props.children);
5951
+ return sb("toolbar", hidden ? { placement, sharedBackground: "hidden" } : { placement }, void 0, items);
5241
5952
  }
5242
5953
  const ToolbarItemGroup = ToolbarItem;
5243
5954
  function ToolbarSpacer(props) {
@@ -5439,14 +6150,14 @@ function canSignInWithApple() {
5439
6150
  return browserEnvironment.appleSignIn !== void 0;
5440
6151
  }
5441
6152
  async function signInWithApple(options = {}) {
5442
- const host = browserEnvironment.appleSignIn;
5443
- if (!host) throw new Error("unsupported: Sign in with Apple is not available here (configureSignInWithApple on a page)");
5444
- return host.signIn({ scopes: options.scopes ?? ["fullName", "email"], ...options.nonce ? { nonce: options.nonce } : {}, ...options.state ? { state: options.state } : {} });
6153
+ const host2 = browserEnvironment.appleSignIn;
6154
+ if (!host2) throw new Error("unsupported: Sign in with Apple is not available here (configureSignInWithApple on a page)");
6155
+ return host2.signIn({ scopes: options.scopes ?? ["fullName", "email"], ...options.nonce ? { nonce: options.nonce } : {}, ...options.state ? { state: options.state } : {} });
5445
6156
  }
5446
6157
  async function appleCredentialState(user) {
5447
- const host = browserEnvironment.appleSignIn;
5448
- if (!host) return "unknown";
5449
- return host.credentialState(user);
6158
+ const host2 = browserEnvironment.appleSignIn;
6159
+ if (!host2) return "unknown";
6160
+ return host2.credentialState(user);
5450
6161
  }
5451
6162
  const LABELS = { signIn: "Sign in with Apple", signUp: "Sign up with Apple", continue: "Continue with Apple" };
5452
6163
  function SignInWithAppleButton(props) {
@@ -5777,16 +6488,16 @@ function hostData(contents) {
5777
6488
  if (contents instanceof ArrayBuffer) return new Uint8Array(contents);
5778
6489
  return contents;
5779
6490
  }
5780
- function createFileManager(host) {
6491
+ function createFileManager(host2) {
5781
6492
  const required = () => {
5782
- const files = host();
6493
+ const files = host2();
5783
6494
  if (!files) throw new Error("unsupported: this page keeps no files");
5784
6495
  return files;
5785
6496
  };
5786
- const directories = () => host()?.directories ?? NONE;
6497
+ const directories = () => host2()?.directories ?? NONE;
5787
6498
  return {
5788
6499
  get available() {
5789
- return host() !== void 0;
6500
+ return host2() !== void 0;
5790
6501
  },
5791
6502
  get documentsDirectory() {
5792
6503
  return directories().documents;
@@ -5823,8 +6534,8 @@ function createFileManager(host) {
5823
6534
  copyItem: async (from, to) => required().copy(from, to),
5824
6535
  removeItem: async (url) => required().remove(url),
5825
6536
  loadURL: async (url) => required().loadURL(url),
5826
- iCloudDirectory: async () => await host()?.iCloud?.() ?? null,
5827
- watch: (listener) => host()?.watch?.(listener) ?? (() => {
6537
+ iCloudDirectory: async () => await host2()?.iCloud?.() ?? null,
6538
+ watch: (listener) => host2()?.watch?.(listener) ?? (() => {
5828
6539
  })
5829
6540
  };
5830
6541
  }
@@ -7420,6 +8131,19 @@ function KeyframeAnimator({
7420
8131
  return children(value);
7421
8132
  }
7422
8133
  export {
8134
+ AVAsset,
8135
+ AVAssetExportPreset1280x720,
8136
+ AVAssetExportPreset1920x1080,
8137
+ AVAssetExportPreset3840x2160,
8138
+ AVAssetExportPreset640x480,
8139
+ AVAssetExportPreset960x540,
8140
+ AVAssetExportPresetHighestQuality,
8141
+ AVAssetExportPresetLowQuality,
8142
+ AVAssetExportPresetMediumQuality,
8143
+ AVAssetExportPresetPassthrough,
8144
+ AVAssetExportSession,
8145
+ AVAssetImageGenerator,
8146
+ AVAssetTrack,
7423
8147
  AVAudioEngine,
7424
8148
  AVAudioFile,
7425
8149
  AVAudioInputNode,
@@ -7437,8 +8161,12 @@ export {
7437
8161
  AVAudioUnitReverb,
7438
8162
  AVAudioUnitSampler,
7439
8163
  AVAudioUnitTimePitch,
8164
+ AVFileType,
8165
+ AVMediaType,
8166
+ AVMutableComposition,
7440
8167
  AVPlayer,
7441
8168
  AVQueuePlayer,
8169
+ AVURLAsset,
7442
8170
  AngularGradient,
7443
8171
  AreaMark,
7444
8172
  AsyncImage,
@@ -7446,6 +8174,9 @@ export {
7446
8174
  AudioPlayer,
7447
8175
  BarMark,
7448
8176
  Button,
8177
+ CMTime,
8178
+ CMTimeRange,
8179
+ CameraPreview,
7449
8180
  Canvas,
7450
8181
  Capsule,
7451
8182
  Chart,
@@ -7550,6 +8281,8 @@ export {
7550
8281
  answerLayout,
7551
8282
  appStorage,
7552
8283
  appleCredentialState,
8284
+ bitmapExtent,
8285
+ bitmapSource,
7553
8286
  canMeasureText,
7554
8287
  canSignInWithApple,
7555
8288
  color,
@@ -7566,10 +8299,12 @@ export {
7566
8299
  geometryProxy,
7567
8300
  gradient,
7568
8301
  hex,
8302
+ imageHeader,
7569
8303
  injectIntoDevTools,
7570
8304
  insets,
7571
8305
  installTextMeasurer,
7572
8306
  keyframeValues,
8307
+ liveBitmapSource,
7573
8308
  measureText,
7574
8309
  mount,
7575
8310
  moveItems,
@@ -7583,6 +8318,7 @@ export {
7583
8318
  plainText,
7584
8319
  providingScrollSettings,
7585
8320
  readBinding,
8321
+ releaseBitmapSource,
7586
8322
  rgb,
7587
8323
  routeOf,
7588
8324
  samplerZones,
@@ -7617,6 +8353,7 @@ export {
7617
8353
  useSpeechSynthesis,
7618
8354
  useSystemLanguageModel,
7619
8355
  useUndoManager,
8356
+ useVideoRecorder,
7620
8357
  useViewEnvironment,
7621
8358
  useWakeLock,
7622
8359
  weighted,