@swiftbrowser/react 0.27.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/README.md +56 -0
- package/dist/avAsset.d.ts +218 -0
- package/dist/avAsset.d.ts.map +1 -0
- package/dist/components/controls.d.ts +2 -1
- package/dist/components/controls.d.ts.map +1 -1
- package/dist/components/remoteTree.d.ts.map +1 -1
- package/dist/host/tree.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +646 -53
- package/dist/index.js.map +1 -1
- package/dist/inheritedStyle.d.ts +31 -0
- package/dist/inheritedStyle.d.ts.map +1 -0
- package/dist/island.d.ts +8 -1
- package/dist/island.d.ts.map +1 -1
- package/dist/modifiers.d.ts.map +1 -1
- package/dist/mount.d.ts +7 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/presentation.d.ts +10 -2
- package/dist/presentation.d.ts.map +1 -1
- package/dist/videoRecorder.d.ts +65 -0
- package/dist/videoRecorder.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, createElement, useSyncExternalStore, useLayoutEffect, useRef, Fragment, useState, Children, useEffect,
|
|
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
4
|
import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
|
|
@@ -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;
|
|
@@ -746,6 +747,37 @@ function sb(kind, props, on, children, target) {
|
|
|
746
747
|
if (target) hostProps.target = target;
|
|
747
748
|
return children === void 0 ? createElement("sb", hostProps) : createElement("sb", hostProps, children);
|
|
748
749
|
}
|
|
750
|
+
const INHERITED_STYLE_KEYS = /* @__PURE__ */ new Set([
|
|
751
|
+
"font",
|
|
752
|
+
"fontWeight",
|
|
753
|
+
"bold",
|
|
754
|
+
"monospacedDigit",
|
|
755
|
+
"tint",
|
|
756
|
+
"lineLimit",
|
|
757
|
+
"multilineTextAlignment",
|
|
758
|
+
"imageScale",
|
|
759
|
+
"defaultMinListRowHeight"
|
|
760
|
+
]);
|
|
761
|
+
const PRESENTATION_KEYS = /* @__PURE__ */ new Set(["sheet", "fullScreenCover", "popover"]);
|
|
762
|
+
const InheritedStylesContext = createContext([]);
|
|
763
|
+
function useInheritedStyles() {
|
|
764
|
+
return useContext(InheritedStylesContext);
|
|
765
|
+
}
|
|
766
|
+
function styleOf(node) {
|
|
767
|
+
if (!isValidElement(node) || node.props.kind !== "styled") return {};
|
|
768
|
+
return node.props.props?.style ?? {};
|
|
769
|
+
}
|
|
770
|
+
function InheritStyle({ style, children }) {
|
|
771
|
+
const parent = useContext(InheritedStylesContext);
|
|
772
|
+
const key = JSON.stringify(style);
|
|
773
|
+
const value = useMemo(() => key === "{}" ? parent : [...parent, style], [parent, key]);
|
|
774
|
+
return createElement(InheritedStylesContext.Provider, { value }, children);
|
|
775
|
+
}
|
|
776
|
+
function replayStyles(styles, content) {
|
|
777
|
+
if (styles.length === 0) return content;
|
|
778
|
+
const provided = createElement(InheritedStylesContext.Provider, { value: styles }, content);
|
|
779
|
+
return styles.reduceRight((child, style) => sb("styled", { style }, void 0, child), provided);
|
|
780
|
+
}
|
|
749
781
|
const NavigationContext = createContext(null);
|
|
750
782
|
const ScreenDepthContext = createContext(0);
|
|
751
783
|
function useScreenDepth() {
|
|
@@ -786,6 +818,10 @@ function renderNode(root, node, stack) {
|
|
|
786
818
|
const children = node.children.length ? node.children.map((child) => renderNode(root, child, stack)) : void 0;
|
|
787
819
|
return createElement(Fragment, { key: node.id }, sb(node.kind, node.props, root.handlersFor(node.id), children));
|
|
788
820
|
}
|
|
821
|
+
function renderSheet(root, node, stack, inherited) {
|
|
822
|
+
const children = node.children.map((child) => renderNode(root, child, stack));
|
|
823
|
+
return createElement(Fragment, { key: node.id }, sb(node.kind, node.props, root.handlersFor(node.id), replayStyles(inherited, children)));
|
|
824
|
+
}
|
|
789
825
|
function renderScreen(root, screen, stack) {
|
|
790
826
|
const out = [screenPrefs(screen.props, "$screen")];
|
|
791
827
|
for (const child of screen.children) {
|
|
@@ -804,6 +840,7 @@ function RemoteScreen({ root, id }) {
|
|
|
804
840
|
function RemoteTree({ root, initialPath }) {
|
|
805
841
|
useSyncExternalStore(root.subscribe, root.getVersion, root.getVersion);
|
|
806
842
|
const environment = useContext(EnvironmentContext);
|
|
843
|
+
const inherited = useInheritedStyles();
|
|
807
844
|
const tree = useContext(HostTreeContext);
|
|
808
845
|
const navigation = useNavigation();
|
|
809
846
|
const depth = useScreenDepth();
|
|
@@ -859,7 +896,7 @@ function RemoteTree({ root, initialPath }) {
|
|
|
859
896
|
if ("colorScheme" in root.rootProps)
|
|
860
897
|
out.push(createElement(Fragment, { key: "$scheme" }, sb(PREF_KIND, { colorScheme: root.rootProps["colorScheme"] }, void 0, void 0, "root")));
|
|
861
898
|
for (const child of root.root.children) {
|
|
862
|
-
const drawn = renderNode(root, child, stack);
|
|
899
|
+
const drawn = child.kind === "sheet" && inherited.length > 0 ? renderSheet(root, child, stack, inherited) : renderNode(root, child, stack);
|
|
863
900
|
if (child.kind === "sheet" || child.kind === "alert") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "root")));
|
|
864
901
|
else if (child.kind === "toolbar" || child.kind === "searchfield") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "navscreen")));
|
|
865
902
|
else out.push(drawn);
|
|
@@ -1052,6 +1089,7 @@ function mount(screen, element, options = {}) {
|
|
|
1052
1089
|
platform: options.platform === void 0 || options.platform === "auto" ? decidePlatform(width) : options.platform
|
|
1053
1090
|
};
|
|
1054
1091
|
renderer.contentHeight = options.contentHeight ?? false;
|
|
1092
|
+
if (options.sidebarTitleBar === false) renderer.sidebarTitleBar = false;
|
|
1055
1093
|
if (options.assets) renderer.setAssetCatalog(options.assets);
|
|
1056
1094
|
if (options.app) browserEnvironment.files?.setApp?.(options.app);
|
|
1057
1095
|
renderer.setEnvironment({ colorScheme: environment.colorScheme, dynamicTypeSize: environment.dynamicTypeSize, platform: environment.platform });
|
|
@@ -1360,6 +1398,7 @@ function SwiftUIView({
|
|
|
1360
1398
|
safeArea,
|
|
1361
1399
|
history = true,
|
|
1362
1400
|
keyboard = fit === "fill",
|
|
1401
|
+
sidebarTitleBar = true,
|
|
1363
1402
|
className,
|
|
1364
1403
|
style
|
|
1365
1404
|
}) {
|
|
@@ -1387,6 +1426,9 @@ function SwiftUIView({
|
|
|
1387
1426
|
instance.unmount();
|
|
1388
1427
|
};
|
|
1389
1428
|
}, [fit, history, keyboard]);
|
|
1429
|
+
useLayoutEffect(() => {
|
|
1430
|
+
if (mounted.current) mounted.current.renderer.sidebarTitleBar = sidebarTitleBar;
|
|
1431
|
+
}, [sidebarTitleBar, fit, history, keyboard]);
|
|
1390
1432
|
useLayoutEffect(() => {
|
|
1391
1433
|
mounted.current?.root.render(tree);
|
|
1392
1434
|
});
|
|
@@ -2334,7 +2376,8 @@ function useReportSplitSelection(split, selected, clear, active2 = true) {
|
|
|
2334
2376
|
function OutsideSplit({ children }) {
|
|
2335
2377
|
return /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children });
|
|
2336
2378
|
}
|
|
2337
|
-
function SheetPresenter({ isPresented, onDismiss, content, fullScreen }) {
|
|
2379
|
+
function SheetPresenter({ isPresented, onDismiss, content, fullScreen, inherited }) {
|
|
2380
|
+
const around = useInheritedStyles();
|
|
2338
2381
|
const [presented, setPresented] = readBinding(isPresented);
|
|
2339
2382
|
if (!presented) return null;
|
|
2340
2383
|
const dismiss = () => {
|
|
@@ -2350,7 +2393,10 @@ function SheetPresenter({ isPresented, onDismiss, content, fullScreen }) {
|
|
|
2350
2393
|
"sheet",
|
|
2351
2394
|
{ detents },
|
|
2352
2395
|
{ tap: dismiss },
|
|
2353
|
-
|
|
2396
|
+
replayStyles(
|
|
2397
|
+
inherited && inherited.length > 0 ? [...around, ...inherited] : around,
|
|
2398
|
+
/* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(OutsideSplit, { children: /* @__PURE__ */ jsx(DismissContext.Provider, { value: dismiss, children: content }) }) })
|
|
2399
|
+
)
|
|
2354
2400
|
),
|
|
2355
2401
|
"root"
|
|
2356
2402
|
);
|
|
@@ -2857,13 +2903,30 @@ function applyModifiers(element, modifiers) {
|
|
|
2857
2903
|
const isPreference = (side) => isValidElement(side) && side.props.kind === PREF_KIND;
|
|
2858
2904
|
function wrap(element, modifiers, sides) {
|
|
2859
2905
|
let node = element;
|
|
2906
|
+
const holds = holdsViews(element);
|
|
2907
|
+
const presentations = [];
|
|
2860
2908
|
for (const [key, value] of modifiers) {
|
|
2861
2909
|
const inactive = value === void 0 || value === null && key !== "lineLimit" && key !== "badge";
|
|
2910
|
+
const before = sides.length;
|
|
2862
2911
|
const next = inactive ? node : apply(key, value, node, sides);
|
|
2863
2912
|
node = next === node ? neutral(key, node) : next;
|
|
2913
|
+
if (INHERITED_STYLE_KEYS.has(key)) {
|
|
2914
|
+
const style = styleOf(node);
|
|
2915
|
+
if (Object.keys(style).length > 0) for (const presentation of presentations) presentation.after.push(style);
|
|
2916
|
+
if (holds) node = createElement(InheritStyle, { style }, node);
|
|
2917
|
+
}
|
|
2918
|
+
if (PRESENTATION_KEYS.has(key) && sides.length > before) presentations.push({ at: before, after: [] });
|
|
2919
|
+
}
|
|
2920
|
+
for (const { at, after } of presentations) {
|
|
2921
|
+
const side = sides[at];
|
|
2922
|
+
if (after.length > 0 && isValidElement(side)) sides[at] = cloneElement(side, { inherited: after.reverse() });
|
|
2864
2923
|
}
|
|
2865
2924
|
return node;
|
|
2866
2925
|
}
|
|
2926
|
+
function holdsViews(element) {
|
|
2927
|
+
if (!isValidElement(element)) return false;
|
|
2928
|
+
return element.type !== "sb" || element.props.children !== void 0;
|
|
2929
|
+
}
|
|
2867
2930
|
const STYLED_KEYS = /* @__PURE__ */ new Set([
|
|
2868
2931
|
"padding",
|
|
2869
2932
|
"font",
|
|
@@ -3974,7 +4037,7 @@ function protocolRuns(text) {
|
|
|
3974
4037
|
}
|
|
3975
4038
|
function useAudioRecorder(options = {}) {
|
|
3976
4039
|
const interval = options.meterInterval ?? 50;
|
|
3977
|
-
const
|
|
4040
|
+
const host2 = browserEnvironment.audioRecorder;
|
|
3978
4041
|
const [status, setStatus] = useState("idle");
|
|
3979
4042
|
const [time, setTime] = useState(0);
|
|
3980
4043
|
const [levels, setLevels] = useState([]);
|
|
@@ -3983,13 +4046,13 @@ function useAudioRecorder(options = {}) {
|
|
|
3983
4046
|
statusRef.current = status;
|
|
3984
4047
|
const fail = (reason) => setError(reason instanceof Error ? reason.message : String(reason));
|
|
3985
4048
|
useEffect(() => {
|
|
3986
|
-
if (status !== "recording" || !
|
|
4049
|
+
if (status !== "recording" || !host2) return;
|
|
3987
4050
|
let reading = false;
|
|
3988
4051
|
let live = true;
|
|
3989
4052
|
const timer = setInterval(() => {
|
|
3990
4053
|
if (reading) return;
|
|
3991
4054
|
reading = true;
|
|
3992
|
-
|
|
4055
|
+
host2.meter().then((meter) => {
|
|
3993
4056
|
if (!live) return;
|
|
3994
4057
|
setTime(meter.time);
|
|
3995
4058
|
setLevels((previous) => [...previous, meter.level]);
|
|
@@ -4001,15 +4064,15 @@ function useAudioRecorder(options = {}) {
|
|
|
4001
4064
|
live = false;
|
|
4002
4065
|
clearInterval(timer);
|
|
4003
4066
|
};
|
|
4004
|
-
}, [status,
|
|
4067
|
+
}, [status, host2, interval]);
|
|
4005
4068
|
useEffect(
|
|
4006
4069
|
() => () => {
|
|
4007
|
-
if (statusRef.current !== "idle") void
|
|
4070
|
+
if (statusRef.current !== "idle") void host2?.cancel().catch(() => void 0);
|
|
4008
4071
|
},
|
|
4009
|
-
[
|
|
4072
|
+
[host2]
|
|
4010
4073
|
);
|
|
4011
4074
|
const record = useCallback(async () => {
|
|
4012
|
-
if (!
|
|
4075
|
+
if (!host2) {
|
|
4013
4076
|
setError("unsupported: this page cannot record");
|
|
4014
4077
|
return;
|
|
4015
4078
|
}
|
|
@@ -4019,28 +4082,28 @@ function useAudioRecorder(options = {}) {
|
|
|
4019
4082
|
setLevels([]);
|
|
4020
4083
|
setStatus("starting");
|
|
4021
4084
|
try {
|
|
4022
|
-
await
|
|
4085
|
+
await host2.start();
|
|
4023
4086
|
setStatus("recording");
|
|
4024
4087
|
} catch (reason) {
|
|
4025
4088
|
fail(reason);
|
|
4026
4089
|
setStatus("idle");
|
|
4027
4090
|
}
|
|
4028
|
-
}, [
|
|
4091
|
+
}, [host2]);
|
|
4029
4092
|
const pause = useCallback(async () => {
|
|
4030
|
-
if (!
|
|
4031
|
-
await
|
|
4093
|
+
if (!host2 || statusRef.current !== "recording") return;
|
|
4094
|
+
await host2.pause().catch(fail);
|
|
4032
4095
|
setStatus("paused");
|
|
4033
|
-
}, [
|
|
4096
|
+
}, [host2]);
|
|
4034
4097
|
const resume = useCallback(async () => {
|
|
4035
|
-
if (!
|
|
4036
|
-
await
|
|
4098
|
+
if (!host2 || statusRef.current !== "paused") return;
|
|
4099
|
+
await host2.resume().catch(fail);
|
|
4037
4100
|
setStatus("recording");
|
|
4038
|
-
}, [
|
|
4101
|
+
}, [host2]);
|
|
4039
4102
|
const stop = useCallback(async () => {
|
|
4040
|
-
if (!
|
|
4103
|
+
if (!host2 || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
|
|
4041
4104
|
setStatus("stopping");
|
|
4042
4105
|
try {
|
|
4043
|
-
const recording = await
|
|
4106
|
+
const recording = await host2.stop();
|
|
4044
4107
|
setTime(recording.duration);
|
|
4045
4108
|
return recording;
|
|
4046
4109
|
} catch (reason) {
|
|
@@ -4049,30 +4112,538 @@ function useAudioRecorder(options = {}) {
|
|
|
4049
4112
|
} finally {
|
|
4050
4113
|
setStatus("idle");
|
|
4051
4114
|
}
|
|
4052
|
-
}, [
|
|
4115
|
+
}, [host2]);
|
|
4053
4116
|
const cancel = useCallback(async () => {
|
|
4054
|
-
if (!
|
|
4055
|
-
await
|
|
4117
|
+
if (!host2 || statusRef.current === "idle") return;
|
|
4118
|
+
await host2.cancel().catch(fail);
|
|
4056
4119
|
setStatus("idle");
|
|
4057
4120
|
setTime(0);
|
|
4058
4121
|
setLevels([]);
|
|
4059
|
-
}, [
|
|
4122
|
+
}, [host2]);
|
|
4060
4123
|
const discard = useCallback(async (url) => {
|
|
4061
|
-
await
|
|
4062
|
-
}, [
|
|
4124
|
+
await host2?.discard(url).catch(fail);
|
|
4125
|
+
}, [host2]);
|
|
4063
4126
|
const trim = useCallback(
|
|
4064
4127
|
async (url, keep) => {
|
|
4065
|
-
if (!
|
|
4128
|
+
if (!host2) throw new Error("unsupported: this page cannot cut audio");
|
|
4066
4129
|
try {
|
|
4067
|
-
return await
|
|
4130
|
+
return await host2.trim(url, keep);
|
|
4068
4131
|
} catch (reason) {
|
|
4069
4132
|
fail(reason);
|
|
4070
4133
|
throw reason;
|
|
4071
4134
|
}
|
|
4072
4135
|
},
|
|
4073
|
-
[
|
|
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
|
|
4074
4646
|
);
|
|
4075
|
-
return { available: host !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
|
|
4076
4647
|
}
|
|
4077
4648
|
let nextId$1 = 1;
|
|
4078
4649
|
const newId = () => nextId$1++;
|
|
@@ -4081,9 +4652,9 @@ function unsupported() {
|
|
|
4081
4652
|
return new Error("unsupported: this page or app has no audio engine");
|
|
4082
4653
|
}
|
|
4083
4654
|
function perform$1(command2) {
|
|
4084
|
-
const
|
|
4085
|
-
if (!
|
|
4086
|
-
return
|
|
4655
|
+
const host2 = hostNow$1();
|
|
4656
|
+
if (!host2) return Promise.reject(unsupported());
|
|
4657
|
+
return host2.perform(command2);
|
|
4087
4658
|
}
|
|
4088
4659
|
class AVAudioTime {
|
|
4089
4660
|
spec;
|
|
@@ -4689,9 +5260,9 @@ function useAudioEngine(build) {
|
|
|
4689
5260
|
let nextId = 1;
|
|
4690
5261
|
const hostNow = () => browserEnvironment.languageModel;
|
|
4691
5262
|
function perform(command2) {
|
|
4692
|
-
const
|
|
4693
|
-
if (!
|
|
4694
|
-
return
|
|
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) => {
|
|
4695
5266
|
throw GenerationError.from(error);
|
|
4696
5267
|
});
|
|
4697
5268
|
}
|
|
@@ -5174,8 +5745,9 @@ function ProgressView(props) {
|
|
|
5174
5745
|
}
|
|
5175
5746
|
function ToolbarItem(props) {
|
|
5176
5747
|
const hidden = props.sharedBackgroundVisibility === "hidden";
|
|
5177
|
-
const
|
|
5178
|
-
|
|
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);
|
|
5179
5751
|
}
|
|
5180
5752
|
const ToolbarItemGroup = ToolbarItem;
|
|
5181
5753
|
function ToolbarSpacer(props) {
|
|
@@ -5377,14 +5949,14 @@ function canSignInWithApple() {
|
|
|
5377
5949
|
return browserEnvironment.appleSignIn !== void 0;
|
|
5378
5950
|
}
|
|
5379
5951
|
async function signInWithApple(options = {}) {
|
|
5380
|
-
const
|
|
5381
|
-
if (!
|
|
5382
|
-
return
|
|
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 } : {} });
|
|
5383
5955
|
}
|
|
5384
5956
|
async function appleCredentialState(user) {
|
|
5385
|
-
const
|
|
5386
|
-
if (!
|
|
5387
|
-
return
|
|
5957
|
+
const host2 = browserEnvironment.appleSignIn;
|
|
5958
|
+
if (!host2) return "unknown";
|
|
5959
|
+
return host2.credentialState(user);
|
|
5388
5960
|
}
|
|
5389
5961
|
const LABELS = { signIn: "Sign in with Apple", signUp: "Sign up with Apple", continue: "Continue with Apple" };
|
|
5390
5962
|
function SignInWithAppleButton(props) {
|
|
@@ -5715,16 +6287,16 @@ function hostData(contents) {
|
|
|
5715
6287
|
if (contents instanceof ArrayBuffer) return new Uint8Array(contents);
|
|
5716
6288
|
return contents;
|
|
5717
6289
|
}
|
|
5718
|
-
function createFileManager(
|
|
6290
|
+
function createFileManager(host2) {
|
|
5719
6291
|
const required = () => {
|
|
5720
|
-
const files =
|
|
6292
|
+
const files = host2();
|
|
5721
6293
|
if (!files) throw new Error("unsupported: this page keeps no files");
|
|
5722
6294
|
return files;
|
|
5723
6295
|
};
|
|
5724
|
-
const directories = () =>
|
|
6296
|
+
const directories = () => host2()?.directories ?? NONE;
|
|
5725
6297
|
return {
|
|
5726
6298
|
get available() {
|
|
5727
|
-
return
|
|
6299
|
+
return host2() !== void 0;
|
|
5728
6300
|
},
|
|
5729
6301
|
get documentsDirectory() {
|
|
5730
6302
|
return directories().documents;
|
|
@@ -5761,8 +6333,8 @@ function createFileManager(host) {
|
|
|
5761
6333
|
copyItem: async (from, to) => required().copy(from, to),
|
|
5762
6334
|
removeItem: async (url) => required().remove(url),
|
|
5763
6335
|
loadURL: async (url) => required().loadURL(url),
|
|
5764
|
-
iCloudDirectory: async () => await
|
|
5765
|
-
watch: (listener) =>
|
|
6336
|
+
iCloudDirectory: async () => await host2()?.iCloud?.() ?? null,
|
|
6337
|
+
watch: (listener) => host2()?.watch?.(listener) ?? (() => {
|
|
5766
6338
|
})
|
|
5767
6339
|
};
|
|
5768
6340
|
}
|
|
@@ -6327,7 +6899,7 @@ function Chart(props) {
|
|
|
6327
6899
|
const key = mark.props.foregroundStyleBy;
|
|
6328
6900
|
if (key !== void 0 && !series.includes(key)) series.push(key);
|
|
6329
6901
|
}
|
|
6330
|
-
const
|
|
6902
|
+
const styleOf2 = (mark) => {
|
|
6331
6903
|
if (mark.props.foregroundStyle !== void 0) return shapeStyle(mark.props.foregroundStyle);
|
|
6332
6904
|
const key = mark.props.foregroundStyleBy;
|
|
6333
6905
|
if (key !== void 0) return { name: PALETTE[series.indexOf(key) % PALETTE.length] };
|
|
@@ -6385,7 +6957,7 @@ function Chart(props) {
|
|
|
6385
6957
|
);
|
|
6386
6958
|
};
|
|
6387
6959
|
const common = (mark) => {
|
|
6388
|
-
const base = { style:
|
|
6960
|
+
const base = { style: styleOf2(mark) };
|
|
6389
6961
|
if (mark.props.opacity !== void 0) base.opacity = mark.props.opacity;
|
|
6390
6962
|
return base;
|
|
6391
6963
|
};
|
|
@@ -7358,6 +7930,19 @@ function KeyframeAnimator({
|
|
|
7358
7930
|
return children(value);
|
|
7359
7931
|
}
|
|
7360
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,
|
|
7361
7946
|
AVAudioEngine,
|
|
7362
7947
|
AVAudioFile,
|
|
7363
7948
|
AVAudioInputNode,
|
|
@@ -7375,8 +7960,12 @@ export {
|
|
|
7375
7960
|
AVAudioUnitReverb,
|
|
7376
7961
|
AVAudioUnitSampler,
|
|
7377
7962
|
AVAudioUnitTimePitch,
|
|
7963
|
+
AVFileType,
|
|
7964
|
+
AVMediaType,
|
|
7965
|
+
AVMutableComposition,
|
|
7378
7966
|
AVPlayer,
|
|
7379
7967
|
AVQueuePlayer,
|
|
7968
|
+
AVURLAsset,
|
|
7380
7969
|
AngularGradient,
|
|
7381
7970
|
AreaMark,
|
|
7382
7971
|
AsyncImage,
|
|
@@ -7384,6 +7973,9 @@ export {
|
|
|
7384
7973
|
AudioPlayer,
|
|
7385
7974
|
BarMark,
|
|
7386
7975
|
Button,
|
|
7976
|
+
CMTime,
|
|
7977
|
+
CMTimeRange,
|
|
7978
|
+
CameraPreview,
|
|
7387
7979
|
Canvas,
|
|
7388
7980
|
Capsule,
|
|
7389
7981
|
Chart,
|
|
@@ -7555,6 +8147,7 @@ export {
|
|
|
7555
8147
|
useSpeechSynthesis,
|
|
7556
8148
|
useSystemLanguageModel,
|
|
7557
8149
|
useUndoManager,
|
|
8150
|
+
useVideoRecorder,
|
|
7558
8151
|
useViewEnvironment,
|
|
7559
8152
|
useWakeLock,
|
|
7560
8153
|
weighted,
|