@swiftbrowser/react 0.28.0 → 0.28.1

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
@@ -221,6 +221,7 @@ class HostTree {
221
221
  };
222
222
  const visit = (child, sink, owner, ctx, hoistedTo) => {
223
223
  if (child.hidden) return;
224
+ if (child.kind === PREF_KIND || child.kind === HOIST_KIND) child.stale = false;
224
225
  if (child.kind === PREF_KIND) {
225
226
  contribute({ kind: "pref", target: child.target, props: child.props }, ctx);
226
227
  return;
@@ -4036,7 +4037,7 @@ function protocolRuns(text) {
4036
4037
  }
4037
4038
  function useAudioRecorder(options = {}) {
4038
4039
  const interval = options.meterInterval ?? 50;
4039
- const host = browserEnvironment.audioRecorder;
4040
+ const host2 = browserEnvironment.audioRecorder;
4040
4041
  const [status, setStatus] = useState("idle");
4041
4042
  const [time, setTime] = useState(0);
4042
4043
  const [levels, setLevels] = useState([]);
@@ -4045,13 +4046,13 @@ function useAudioRecorder(options = {}) {
4045
4046
  statusRef.current = status;
4046
4047
  const fail = (reason) => setError(reason instanceof Error ? reason.message : String(reason));
4047
4048
  useEffect(() => {
4048
- if (status !== "recording" || !host) return;
4049
+ if (status !== "recording" || !host2) return;
4049
4050
  let reading = false;
4050
4051
  let live = true;
4051
4052
  const timer = setInterval(() => {
4052
4053
  if (reading) return;
4053
4054
  reading = true;
4054
- host.meter().then((meter) => {
4055
+ host2.meter().then((meter) => {
4055
4056
  if (!live) return;
4056
4057
  setTime(meter.time);
4057
4058
  setLevels((previous) => [...previous, meter.level]);
@@ -4063,15 +4064,15 @@ function useAudioRecorder(options = {}) {
4063
4064
  live = false;
4064
4065
  clearInterval(timer);
4065
4066
  };
4066
- }, [status, host, interval]);
4067
+ }, [status, host2, interval]);
4067
4068
  useEffect(
4068
4069
  () => () => {
4069
- if (statusRef.current !== "idle") void host?.cancel().catch(() => void 0);
4070
+ if (statusRef.current !== "idle") void host2?.cancel().catch(() => void 0);
4070
4071
  },
4071
- [host]
4072
+ [host2]
4072
4073
  );
4073
4074
  const record = useCallback(async () => {
4074
- if (!host) {
4075
+ if (!host2) {
4075
4076
  setError("unsupported: this page cannot record");
4076
4077
  return;
4077
4078
  }
@@ -4081,28 +4082,28 @@ function useAudioRecorder(options = {}) {
4081
4082
  setLevels([]);
4082
4083
  setStatus("starting");
4083
4084
  try {
4084
- await host.start();
4085
+ await host2.start();
4085
4086
  setStatus("recording");
4086
4087
  } catch (reason) {
4087
4088
  fail(reason);
4088
4089
  setStatus("idle");
4089
4090
  }
4090
- }, [host]);
4091
+ }, [host2]);
4091
4092
  const pause = useCallback(async () => {
4092
- if (!host || statusRef.current !== "recording") return;
4093
- await host.pause().catch(fail);
4093
+ if (!host2 || statusRef.current !== "recording") return;
4094
+ await host2.pause().catch(fail);
4094
4095
  setStatus("paused");
4095
- }, [host]);
4096
+ }, [host2]);
4096
4097
  const resume = useCallback(async () => {
4097
- if (!host || statusRef.current !== "paused") return;
4098
- await host.resume().catch(fail);
4098
+ if (!host2 || statusRef.current !== "paused") return;
4099
+ await host2.resume().catch(fail);
4099
4100
  setStatus("recording");
4100
- }, [host]);
4101
+ }, [host2]);
4101
4102
  const stop = useCallback(async () => {
4102
- if (!host || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
4103
+ if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
4103
4104
  setStatus("stopping");
4104
4105
  try {
4105
- const recording = await host.stop();
4106
+ const recording = await host2.stop();
4106
4107
  setTime(recording.duration);
4107
4108
  return recording;
4108
4109
  } catch (reason) {
@@ -4111,30 +4112,538 @@ function useAudioRecorder(options = {}) {
4111
4112
  } finally {
4112
4113
  setStatus("idle");
4113
4114
  }
4114
- }, [host]);
4115
+ }, [host2]);
4115
4116
  const cancel = useCallback(async () => {
4116
- if (!host || statusRef.current === "idle") return;
4117
- await host.cancel().catch(fail);
4117
+ if (!host2 || statusRef.current === "idle") return;
4118
+ await host2.cancel().catch(fail);
4118
4119
  setStatus("idle");
4119
4120
  setTime(0);
4120
4121
  setLevels([]);
4121
- }, [host]);
4122
+ }, [host2]);
4122
4123
  const discard = useCallback(async (url) => {
4123
- await host?.discard(url).catch(fail);
4124
- }, [host]);
4124
+ await host2?.discard(url).catch(fail);
4125
+ }, [host2]);
4125
4126
  const trim = useCallback(
4126
4127
  async (url, keep) => {
4127
- if (!host) throw new Error("unsupported: this page cannot cut audio");
4128
+ if (!host2) throw new Error("unsupported: this page cannot cut audio");
4128
4129
  try {
4129
- return await host.trim(url, keep);
4130
+ return await host2.trim(url, keep);
4130
4131
  } catch (reason) {
4131
4132
  fail(reason);
4132
4133
  throw reason;
4133
4134
  }
4134
4135
  },
4135
- [host]
4136
+ [host2]
4137
+ );
4138
+ return { available: host2 !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
4139
+ }
4140
+ function host() {
4141
+ const found = browserEnvironment.avAsset;
4142
+ if (!found) throw new Error("unsupported: this page cannot read or write video");
4143
+ return found;
4144
+ }
4145
+ class CMTime {
4146
+ constructor(value, timescale) {
4147
+ this.value = value;
4148
+ this.timescale = timescale;
4149
+ }
4150
+ value;
4151
+ timescale;
4152
+ static zero = new CMTime(0, 1);
4153
+ static invalid = new CMTime(0, 0);
4154
+ static positiveInfinity = new CMTime(Infinity, 1);
4155
+ /** `CMTime(value:timescale:)`. */
4156
+ static make(value, timescale) {
4157
+ return new CMTime(value, timescale);
4158
+ }
4159
+ /** `CMTime(seconds:preferredTimescale:)`: rounded to the timescale. */
4160
+ static seconds(seconds, preferredTimescale = 600) {
4161
+ if (!Number.isFinite(seconds)) return seconds > 0 ? CMTime.positiveInfinity : CMTime.invalid;
4162
+ return new CMTime(Math.round(seconds * preferredTimescale), preferredTimescale);
4163
+ }
4164
+ get isValid() {
4165
+ return this.timescale > 0;
4166
+ }
4167
+ /** `CMTimeGetSeconds`. */
4168
+ get seconds() {
4169
+ return this.timescale > 0 ? this.value / this.timescale : Number.NaN;
4170
+ }
4171
+ add(other) {
4172
+ return CMTime.seconds(this.seconds + other.seconds, Math.max(this.timescale, other.timescale, 1));
4173
+ }
4174
+ subtract(other) {
4175
+ return CMTime.seconds(this.seconds - other.seconds, Math.max(this.timescale, other.timescale, 1));
4176
+ }
4177
+ /** `CMTimeCompare`: -1, 0 or 1. */
4178
+ compare(other) {
4179
+ const a = this.seconds;
4180
+ const b = other.seconds;
4181
+ return a < b ? -1 : a > b ? 1 : 0;
4182
+ }
4183
+ static minimum(a, b) {
4184
+ return a.compare(b) <= 0 ? a : b;
4185
+ }
4186
+ static maximum(a, b) {
4187
+ return a.compare(b) >= 0 ? a : b;
4188
+ }
4189
+ }
4190
+ class CMTimeRange {
4191
+ constructor(start, duration) {
4192
+ this.start = start;
4193
+ this.duration = duration;
4194
+ }
4195
+ start;
4196
+ duration;
4197
+ static zero = new CMTimeRange(CMTime.zero, CMTime.zero);
4198
+ /** `CMTimeRange(start:end:)`. */
4199
+ static fromTo(start, end) {
4200
+ return new CMTimeRange(start, CMTime.maximum(end.subtract(start), CMTime.zero));
4201
+ }
4202
+ /** `CMTimeRange(start:duration:)` in seconds. */
4203
+ static seconds(start, duration, preferredTimescale = 600) {
4204
+ return new CMTimeRange(CMTime.seconds(start, preferredTimescale), CMTime.seconds(duration, preferredTimescale));
4205
+ }
4206
+ get end() {
4207
+ return this.start.add(this.duration);
4208
+ }
4209
+ get isEmpty() {
4210
+ return this.duration.seconds <= 0;
4211
+ }
4212
+ containsTime(time) {
4213
+ return time.compare(this.start) >= 0 && time.compare(this.end) < 0;
4214
+ }
4215
+ }
4216
+ const AVMediaType = { video: "vide", audio: "soun" };
4217
+ class AVAssetTrack {
4218
+ constructor(trackID, mediaType, naturalSize, nominalFrameRate, timeRange, codec) {
4219
+ this.trackID = trackID;
4220
+ this.mediaType = mediaType;
4221
+ this.naturalSize = naturalSize;
4222
+ this.nominalFrameRate = nominalFrameRate;
4223
+ this.timeRange = timeRange;
4224
+ this.codec = codec;
4225
+ }
4226
+ trackID;
4227
+ mediaType;
4228
+ naturalSize;
4229
+ nominalFrameRate;
4230
+ timeRange;
4231
+ codec;
4232
+ }
4233
+ class AVAsset {
4234
+ /** @internal */
4235
+ segmentsForExport() {
4236
+ return this.segments();
4237
+ }
4238
+ /** `loadTracks(withMediaType:)`. */
4239
+ async loadTracks(mediaType) {
4240
+ return (await this.load("tracks")).filter((track) => track.mediaType === mediaType);
4241
+ }
4242
+ }
4243
+ const tracksOf = (info) => info.tracks.map(
4244
+ (t) => new AVAssetTrack(
4245
+ t.trackID,
4246
+ t.mediaType === "video" ? AVMediaType.video : AVMediaType.audio,
4247
+ t.naturalSize,
4248
+ t.nominalFrameRate,
4249
+ CMTimeRange.seconds(t.timeRange.start, t.timeRange.duration),
4250
+ t.codec
4251
+ )
4252
+ );
4253
+ class AVURLAsset extends AVAsset {
4254
+ constructor(url) {
4255
+ super();
4256
+ this.url = url;
4257
+ }
4258
+ url;
4259
+ info = null;
4260
+ loaded() {
4261
+ if (!this.info) {
4262
+ this.info = host().load(this.url);
4263
+ this.info.catch(() => {
4264
+ this.info = null;
4265
+ });
4266
+ }
4267
+ return this.info;
4268
+ }
4269
+ async segments() {
4270
+ return [{ url: this.url, start: 0, end: (await this.loaded()).duration }];
4271
+ }
4272
+ async load(key) {
4273
+ const info = await this.loaded();
4274
+ const values = {
4275
+ duration: CMTime.seconds(info.duration),
4276
+ tracks: tracksOf(info),
4277
+ naturalSize: info.naturalSize,
4278
+ isPlayable: info.tracks.length > 0,
4279
+ isExportable: info.tracks.length > 0
4280
+ };
4281
+ return values[key];
4282
+ }
4283
+ }
4284
+ class AVMutableComposition extends AVAsset {
4285
+ parts = [];
4286
+ /** The composition's length. */
4287
+ get duration() {
4288
+ return CMTime.seconds(this.parts.reduce((sum, part) => sum + part.range.duration.seconds, 0));
4289
+ }
4290
+ /** `insertTimeRange(_:of:at:)`: `range` of `asset` (an `AVURLAsset`, or another composition's parts) placed at `at`. */
4291
+ async insertTimeRange(range, asset, at) {
4292
+ const inserted = [];
4293
+ if (asset instanceof AVURLAsset) {
4294
+ const length = (await asset.load("duration")).seconds;
4295
+ const start = Math.max(0, range.start.seconds);
4296
+ const end = Math.min(length, range.end.seconds);
4297
+ if (end - start > 1e-3) inserted.push({ asset, range: CMTimeRange.seconds(start, end - start) });
4298
+ } else if (asset instanceof AVMutableComposition) {
4299
+ inserted.push(...asset.partsIn(range.start.seconds, range.end.seconds));
4300
+ } else {
4301
+ throw new Error("unsupported: only an AVURLAsset or an AVMutableComposition can be inserted");
4302
+ }
4303
+ const time = Math.max(0, Math.min(at.seconds, this.duration.seconds));
4304
+ const [before, after] = this.splitAt(time);
4305
+ this.parts = [...before, ...inserted, ...after];
4306
+ }
4307
+ /** `removeTimeRange(_:)`: the media in `range` cut out, the rest closing up. */
4308
+ removeTimeRange(range) {
4309
+ const [before] = this.splitAt(range.start.seconds);
4310
+ const [, after] = this.splitAt(range.end.seconds);
4311
+ this.parts = [...before, ...after];
4312
+ }
4313
+ /** The parts' media from `start` to `end` of the composition's timeline. */
4314
+ partsIn(start, end) {
4315
+ const [, from] = this.splitAt(start);
4316
+ const out = [];
4317
+ let cursor = start;
4318
+ for (const part of from) {
4319
+ if (cursor >= end) break;
4320
+ const take = Math.min(part.range.duration.seconds, end - cursor);
4321
+ if (take > 1e-3) out.push({ asset: part.asset, range: CMTimeRange.seconds(part.range.start.seconds, take) });
4322
+ cursor += take;
4323
+ }
4324
+ return out;
4325
+ }
4326
+ /** The parts before `time` and from it, a part across it split in two. */
4327
+ splitAt(time) {
4328
+ const before = [];
4329
+ const after = [];
4330
+ let cursor = 0;
4331
+ for (const part of this.parts) {
4332
+ const length = part.range.duration.seconds;
4333
+ const start = part.range.start.seconds;
4334
+ if (cursor + length <= time + 1e-9) before.push(part);
4335
+ else if (cursor >= time - 1e-9) after.push(part);
4336
+ else {
4337
+ const head = time - cursor;
4338
+ before.push({ asset: part.asset, range: CMTimeRange.seconds(start, head) });
4339
+ after.push({ asset: part.asset, range: CMTimeRange.seconds(start + head, length - head) });
4340
+ }
4341
+ cursor += length;
4342
+ }
4343
+ return [before, after];
4344
+ }
4345
+ async segments() {
4346
+ return this.parts.map((part) => ({ url: part.asset.url, start: part.range.start.seconds, end: part.range.end.seconds }));
4347
+ }
4348
+ async load(key) {
4349
+ const first = this.parts[0]?.asset;
4350
+ const tracks = first ? await first.load("tracks") : [];
4351
+ const values = {
4352
+ duration: this.duration,
4353
+ tracks: tracks.map((t) => new AVAssetTrack(t.trackID, t.mediaType, t.naturalSize, t.nominalFrameRate, new CMTimeRange(CMTime.zero, this.duration), t.codec)),
4354
+ naturalSize: first ? await first.load("naturalSize") : { width: 0, height: 0 },
4355
+ isPlayable: this.parts.length > 0,
4356
+ isExportable: this.parts.length > 0
4357
+ };
4358
+ return values[key];
4359
+ }
4360
+ }
4361
+ class AVAssetImageGenerator {
4362
+ constructor(asset) {
4363
+ this.asset = asset;
4364
+ }
4365
+ asset;
4366
+ maximumSize = { width: 0, height: 0 };
4367
+ /** Stills come upright (the track's rotation applied), as with `appliesPreferredTrackTransform = true`. */
4368
+ appliesPreferredTrackTransform = true;
4369
+ made = [];
4370
+ /** The file and the time in it that `time` on the asset's timeline falls on (a composition's part). */
4371
+ async source(time) {
4372
+ let cursor = 0;
4373
+ const segments = await this.asset.segmentsForExport();
4374
+ for (const segment of segments) {
4375
+ const length = segment.end - segment.start;
4376
+ if (time < cursor + length || segment === segments[segments.length - 1]) return { url: segment.url, time: segment.start + Math.max(0, Math.min(length, time - cursor)) };
4377
+ cursor += length;
4378
+ }
4379
+ throw new Error("empty: the asset has no media");
4380
+ }
4381
+ /** `image(at:)`: the still at `time`. */
4382
+ async image(at) {
4383
+ const { url, time } = await this.source(Math.max(0, at.seconds));
4384
+ const options = {
4385
+ ...this.maximumSize.width > 0 ? { maxWidth: this.maximumSize.width } : {},
4386
+ ...this.maximumSize.height > 0 ? { maxHeight: this.maximumSize.height } : {}
4387
+ };
4388
+ const still = await host().image(url, time, options);
4389
+ this.made.push(still.url);
4390
+ return { image: still.url, actualTime: CMTime.seconds(at.seconds + (still.actualTime - time)), width: still.width, height: still.height };
4391
+ }
4392
+ /** `images(for:)`: a still for each time, in turn. */
4393
+ async *images(times) {
4394
+ for (const time of times) yield { ...await this.image(time), requestedTime: time };
4395
+ }
4396
+ /** Lets go of the stills this generator made (their `blob:` URLs on a page). */
4397
+ async release() {
4398
+ const urls = this.made.splice(0);
4399
+ await Promise.all(urls.map((url) => host().release(url)));
4400
+ }
4401
+ }
4402
+ const AVFileType = { mp4: "public.mpeg-4", mov: "com.apple.quicktime-movie", m4v: "com.apple.m4v-video", webm: "org.webmproject.webm" };
4403
+ const FILE_TYPES = {
4404
+ "public.mpeg-4": "mp4",
4405
+ "com.apple.m4v-video": "mp4",
4406
+ "com.apple.quicktime-movie": "mov",
4407
+ "org.webmproject.webm": "webm"
4408
+ };
4409
+ const AVAssetExportPresetPassthrough = "AVAssetExportPresetPassthrough";
4410
+ const AVAssetExportPresetHighestQuality = "AVAssetExportPresetHighestQuality";
4411
+ const AVAssetExportPresetMediumQuality = "AVAssetExportPresetMediumQuality";
4412
+ const AVAssetExportPresetLowQuality = "AVAssetExportPresetLowQuality";
4413
+ const AVAssetExportPreset640x480 = "AVAssetExportPreset640x480";
4414
+ const AVAssetExportPreset960x540 = "AVAssetExportPreset960x540";
4415
+ const AVAssetExportPreset1280x720 = "AVAssetExportPreset1280x720";
4416
+ const AVAssetExportPreset1920x1080 = "AVAssetExportPreset1920x1080";
4417
+ const AVAssetExportPreset3840x2160 = "AVAssetExportPreset3840x2160";
4418
+ let exports$1 = 0;
4419
+ class AVAssetExportSession {
4420
+ constructor(asset, presetName) {
4421
+ this.asset = asset;
4422
+ this.presetName = presetName;
4423
+ }
4424
+ asset;
4425
+ presetName;
4426
+ outputURL = null;
4427
+ outputFileType = AVFileType.mp4;
4428
+ timeRange = null;
4429
+ status = "unknown";
4430
+ progress = 0;
4431
+ error = null;
4432
+ /** Heard as `progress` changes (a few times a second). */
4433
+ onProgress = null;
4434
+ /** The written file's length and MIME type, once completed. */
4435
+ result = null;
4436
+ id = `export-${++exports$1}`;
4437
+ /** `AVAssetExportSession.allExportPresets()`. */
4438
+ static allExportPresets() {
4439
+ return [
4440
+ AVAssetExportPresetPassthrough,
4441
+ AVAssetExportPresetHighestQuality,
4442
+ AVAssetExportPresetMediumQuality,
4443
+ AVAssetExportPresetLowQuality,
4444
+ AVAssetExportPreset640x480,
4445
+ AVAssetExportPreset960x540,
4446
+ AVAssetExportPreset1280x720,
4447
+ AVAssetExportPreset1920x1080,
4448
+ AVAssetExportPreset3840x2160
4449
+ ];
4450
+ }
4451
+ /** The asset's parts, cut to `timeRange` when it is set. */
4452
+ async parts() {
4453
+ const all = await this.asset.segmentsForExport();
4454
+ if (!this.timeRange) return all;
4455
+ const from = this.timeRange.start.seconds;
4456
+ const to = this.timeRange.end.seconds;
4457
+ const out = [];
4458
+ let cursor = 0;
4459
+ for (const segment of all) {
4460
+ const length = segment.end - segment.start;
4461
+ const start = Math.max(from, cursor);
4462
+ const end = Math.min(to, cursor + length);
4463
+ if (end - start > 1e-3) out.push({ url: segment.url, start: segment.start + (start - cursor), end: segment.start + (end - cursor) });
4464
+ cursor += length;
4465
+ }
4466
+ return out;
4467
+ }
4468
+ /** `export()`: writes the file and answers its URL (also `outputURL`); rejects with why it could not. */
4469
+ async export() {
4470
+ if (this.status === "exporting") throw new Error("busy: already exporting");
4471
+ this.status = "exporting";
4472
+ this.error = null;
4473
+ this.setProgress(0);
4474
+ const assets = host();
4475
+ const ticker = setInterval(() => {
4476
+ void assets.progress(this.id).then((p) => this.status === "exporting" && this.setProgress(p));
4477
+ }, 200);
4478
+ try {
4479
+ const result = await assets.export(this.id, {
4480
+ segments: await this.parts(),
4481
+ preset: this.presetName,
4482
+ fileType: FILE_TYPES[this.outputFileType] ?? "mp4",
4483
+ ...this.outputURL ? { outputURL: this.outputURL } : {}
4484
+ });
4485
+ this.outputURL = result.url;
4486
+ this.result = { duration: result.duration, type: result.type };
4487
+ this.status = "completed";
4488
+ this.setProgress(1);
4489
+ return result.url;
4490
+ } catch (reason) {
4491
+ const error = reason instanceof Error ? reason : new Error(String(reason));
4492
+ this.error = error;
4493
+ this.status = error.message.startsWith("cancelled") ? "cancelled" : "failed";
4494
+ throw error;
4495
+ } finally {
4496
+ clearInterval(ticker);
4497
+ }
4498
+ }
4499
+ /** `cancelExport()`. */
4500
+ cancelExport() {
4501
+ if (this.status !== "exporting") return;
4502
+ void host().cancel(this.id);
4503
+ }
4504
+ setProgress(progress) {
4505
+ if (progress === this.progress) return;
4506
+ this.progress = progress;
4507
+ this.onProgress?.(progress);
4508
+ }
4509
+ }
4510
+ function useVideoRecorder(options = {}) {
4511
+ const interval = options.timeInterval ?? 100;
4512
+ const host2 = browserEnvironment.videoRecorder;
4513
+ const [status, setStatus] = useState("closed");
4514
+ const [stream, setStream] = useState(null);
4515
+ const [facing, setFacing] = useState("user");
4516
+ const [time, setTime] = useState(0);
4517
+ const [error, setError] = useState(null);
4518
+ const statusRef = useRef(status);
4519
+ statusRef.current = status;
4520
+ const facingRef = useRef(facing);
4521
+ facingRef.current = facing;
4522
+ const fail = (reason) => setError(reason instanceof Error ? reason.message : String(reason));
4523
+ useEffect(() => {
4524
+ if (status !== "recording" || !host2) return;
4525
+ let reading = false;
4526
+ let live = true;
4527
+ const timer = setInterval(() => {
4528
+ if (reading) return;
4529
+ reading = true;
4530
+ host2.time().then((seconds) => live && setTime(seconds)).catch(() => void 0).finally(() => {
4531
+ reading = false;
4532
+ });
4533
+ }, interval);
4534
+ return () => {
4535
+ live = false;
4536
+ clearInterval(timer);
4537
+ };
4538
+ }, [status, host2, interval]);
4539
+ useEffect(
4540
+ () => () => {
4541
+ if (statusRef.current !== "closed") void host2?.close().catch(() => void 0);
4542
+ },
4543
+ [host2]
4544
+ );
4545
+ const open = useCallback(
4546
+ async (wanted) => {
4547
+ if (!host2) {
4548
+ setError("unsupported: this page cannot record video");
4549
+ return;
4550
+ }
4551
+ if (statusRef.current === "recording" || statusRef.current === "paused" || statusRef.current === "stopping") return;
4552
+ setError(null);
4553
+ setStatus("opening");
4554
+ try {
4555
+ const opened = await host2.open({ facing: wanted ?? facingRef.current });
4556
+ setStream(opened.stream);
4557
+ setFacing(opened.facing);
4558
+ setStatus("ready");
4559
+ } catch (reason) {
4560
+ fail(reason);
4561
+ setStream(null);
4562
+ setStatus("closed");
4563
+ }
4564
+ },
4565
+ [host2]
4566
+ );
4567
+ const close = useCallback(async () => {
4568
+ if (!host2) return;
4569
+ await host2.close().catch(fail);
4570
+ setStream(null);
4571
+ setStatus("closed");
4572
+ setTime(0);
4573
+ }, [host2]);
4574
+ const flip = useCallback(async () => {
4575
+ if (statusRef.current !== "ready") return;
4576
+ await open(facingRef.current === "user" ? "environment" : "user");
4577
+ }, [open]);
4578
+ const record = useCallback(async () => {
4579
+ if (!host2 || statusRef.current !== "ready") return;
4580
+ setError(null);
4581
+ setTime(0);
4582
+ try {
4583
+ await host2.start();
4584
+ setStatus("recording");
4585
+ } catch (reason) {
4586
+ fail(reason);
4587
+ }
4588
+ }, [host2]);
4589
+ const pause = useCallback(async () => {
4590
+ if (!host2 || statusRef.current !== "recording") return;
4591
+ await host2.pause().catch(fail);
4592
+ setStatus("paused");
4593
+ }, [host2]);
4594
+ const resume = useCallback(async () => {
4595
+ if (!host2 || statusRef.current !== "paused") return;
4596
+ await host2.resume().catch(fail);
4597
+ setStatus("recording");
4598
+ }, [host2]);
4599
+ const stop = useCallback(async () => {
4600
+ if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
4601
+ setStatus("stopping");
4602
+ try {
4603
+ const recording = await host2.stop();
4604
+ setTime(recording.duration);
4605
+ return recording;
4606
+ } catch (reason) {
4607
+ fail(reason);
4608
+ return null;
4609
+ } finally {
4610
+ setStatus("ready");
4611
+ }
4612
+ }, [host2]);
4613
+ const cancel = useCallback(async () => {
4614
+ if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return;
4615
+ await host2.cancel().catch(fail);
4616
+ setStatus("ready");
4617
+ setTime(0);
4618
+ }, [host2]);
4619
+ const discard = useCallback(
4620
+ async (url) => {
4621
+ await host2?.discard(url).catch(fail);
4622
+ },
4623
+ [host2]
4624
+ );
4625
+ return { available: host2 !== void 0, status, stream, facing, time, error, open, close, flip, record, pause, resume, stop, cancel, discard };
4626
+ }
4627
+ function CameraPreview(props) {
4628
+ const { own, modifiers } = splitProps(props);
4629
+ const stream = own.recorder.stream;
4630
+ if (!stream) return applyModifiers(sb("color", { color: { r: 0, g: 0, b: 0, a: 1 } }), modifiers);
4631
+ return applyModifiers(
4632
+ sb("video", {
4633
+ url: null,
4634
+ playing: false,
4635
+ muted: true,
4636
+ volume: 0,
4637
+ rate: 1,
4638
+ loop: false,
4639
+ gravity: own.gravity ?? "resizeAspectFill",
4640
+ controls: false,
4641
+ seek: { time: 0, serial: 0 },
4642
+ stream,
4643
+ mirrored: own.mirrored ?? own.recorder.facing === "user"
4644
+ }),
4645
+ modifiers
4136
4646
  );
4137
- return { available: host !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
4138
4647
  }
4139
4648
  let nextId$1 = 1;
4140
4649
  const newId = () => nextId$1++;
@@ -4143,9 +4652,9 @@ function unsupported() {
4143
4652
  return new Error("unsupported: this page or app has no audio engine");
4144
4653
  }
4145
4654
  function perform$1(command2) {
4146
- const host = hostNow$1();
4147
- if (!host) return Promise.reject(unsupported());
4148
- return host.perform(command2);
4655
+ const host2 = hostNow$1();
4656
+ if (!host2) return Promise.reject(unsupported());
4657
+ return host2.perform(command2);
4149
4658
  }
4150
4659
  class AVAudioTime {
4151
4660
  spec;
@@ -4751,9 +5260,9 @@ function useAudioEngine(build) {
4751
5260
  let nextId = 1;
4752
5261
  const hostNow = () => browserEnvironment.languageModel;
4753
5262
  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) => {
5263
+ const host2 = hostNow();
5264
+ if (!host2) return Promise.reject(new GenerationError("assetsUnavailable", "This page or app has no language model."));
5265
+ return host2.perform(command2).catch((error) => {
4757
5266
  throw GenerationError.from(error);
4758
5267
  });
4759
5268
  }
@@ -5236,8 +5745,9 @@ function ProgressView(props) {
5236
5745
  }
5237
5746
  function ToolbarItem(props) {
5238
5747
  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);
5748
+ const placement = toolbarPlacement(props.placement);
5749
+ const items = placement === "secondary" ? props.children : withEnvironment({ labelStyle: "iconOnly" }, props.children);
5750
+ return sb("toolbar", hidden ? { placement, sharedBackground: "hidden" } : { placement }, void 0, items);
5241
5751
  }
5242
5752
  const ToolbarItemGroup = ToolbarItem;
5243
5753
  function ToolbarSpacer(props) {
@@ -5439,14 +5949,14 @@ function canSignInWithApple() {
5439
5949
  return browserEnvironment.appleSignIn !== void 0;
5440
5950
  }
5441
5951
  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 } : {} });
5952
+ const host2 = browserEnvironment.appleSignIn;
5953
+ if (!host2) throw new Error("unsupported: Sign in with Apple is not available here (configureSignInWithApple on a page)");
5954
+ return host2.signIn({ scopes: options.scopes ?? ["fullName", "email"], ...options.nonce ? { nonce: options.nonce } : {}, ...options.state ? { state: options.state } : {} });
5445
5955
  }
5446
5956
  async function appleCredentialState(user) {
5447
- const host = browserEnvironment.appleSignIn;
5448
- if (!host) return "unknown";
5449
- return host.credentialState(user);
5957
+ const host2 = browserEnvironment.appleSignIn;
5958
+ if (!host2) return "unknown";
5959
+ return host2.credentialState(user);
5450
5960
  }
5451
5961
  const LABELS = { signIn: "Sign in with Apple", signUp: "Sign up with Apple", continue: "Continue with Apple" };
5452
5962
  function SignInWithAppleButton(props) {
@@ -5777,16 +6287,16 @@ function hostData(contents) {
5777
6287
  if (contents instanceof ArrayBuffer) return new Uint8Array(contents);
5778
6288
  return contents;
5779
6289
  }
5780
- function createFileManager(host) {
6290
+ function createFileManager(host2) {
5781
6291
  const required = () => {
5782
- const files = host();
6292
+ const files = host2();
5783
6293
  if (!files) throw new Error("unsupported: this page keeps no files");
5784
6294
  return files;
5785
6295
  };
5786
- const directories = () => host()?.directories ?? NONE;
6296
+ const directories = () => host2()?.directories ?? NONE;
5787
6297
  return {
5788
6298
  get available() {
5789
- return host() !== void 0;
6299
+ return host2() !== void 0;
5790
6300
  },
5791
6301
  get documentsDirectory() {
5792
6302
  return directories().documents;
@@ -5823,8 +6333,8 @@ function createFileManager(host) {
5823
6333
  copyItem: async (from, to) => required().copy(from, to),
5824
6334
  removeItem: async (url) => required().remove(url),
5825
6335
  loadURL: async (url) => required().loadURL(url),
5826
- iCloudDirectory: async () => await host()?.iCloud?.() ?? null,
5827
- watch: (listener) => host()?.watch?.(listener) ?? (() => {
6336
+ iCloudDirectory: async () => await host2()?.iCloud?.() ?? null,
6337
+ watch: (listener) => host2()?.watch?.(listener) ?? (() => {
5828
6338
  })
5829
6339
  };
5830
6340
  }
@@ -7420,6 +7930,19 @@ function KeyframeAnimator({
7420
7930
  return children(value);
7421
7931
  }
7422
7932
  export {
7933
+ AVAsset,
7934
+ AVAssetExportPreset1280x720,
7935
+ AVAssetExportPreset1920x1080,
7936
+ AVAssetExportPreset3840x2160,
7937
+ AVAssetExportPreset640x480,
7938
+ AVAssetExportPreset960x540,
7939
+ AVAssetExportPresetHighestQuality,
7940
+ AVAssetExportPresetLowQuality,
7941
+ AVAssetExportPresetMediumQuality,
7942
+ AVAssetExportPresetPassthrough,
7943
+ AVAssetExportSession,
7944
+ AVAssetImageGenerator,
7945
+ AVAssetTrack,
7423
7946
  AVAudioEngine,
7424
7947
  AVAudioFile,
7425
7948
  AVAudioInputNode,
@@ -7437,8 +7960,12 @@ export {
7437
7960
  AVAudioUnitReverb,
7438
7961
  AVAudioUnitSampler,
7439
7962
  AVAudioUnitTimePitch,
7963
+ AVFileType,
7964
+ AVMediaType,
7965
+ AVMutableComposition,
7440
7966
  AVPlayer,
7441
7967
  AVQueuePlayer,
7968
+ AVURLAsset,
7442
7969
  AngularGradient,
7443
7970
  AreaMark,
7444
7971
  AsyncImage,
@@ -7446,6 +7973,9 @@ export {
7446
7973
  AudioPlayer,
7447
7974
  BarMark,
7448
7975
  Button,
7976
+ CMTime,
7977
+ CMTimeRange,
7978
+ CameraPreview,
7449
7979
  Canvas,
7450
7980
  Capsule,
7451
7981
  Chart,
@@ -7617,6 +8147,7 @@ export {
7617
8147
  useSpeechSynthesis,
7618
8148
  useSystemLanguageModel,
7619
8149
  useUndoManager,
8150
+ useVideoRecorder,
7620
8151
  useViewEnvironment,
7621
8152
  useWakeLock,
7622
8153
  weighted,