@pickleball/expo-sdk 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +155 -101
- package/dist/advanced-DcbHBCGb.d.ts +300 -0
- package/dist/advanced-DeQwg7MK.d.cts +300 -0
- package/dist/advanced.cjs +14 -2
- package/dist/advanced.d.cts +2 -2
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.js +17 -5
- package/dist/camera-controls-By8tHigY.d.cts +97 -0
- package/dist/camera-controls-rCBUGGPC.d.ts +97 -0
- package/dist/chunk-62YV3PCD.cjs +139 -0
- package/dist/chunk-6F3HN6US.js +50 -0
- package/dist/chunk-6YE5BL3A.cjs +50 -0
- package/dist/chunk-7Z7TN5N3.js +318 -0
- package/dist/{chunk-TRLASNPQ.cjs → chunk-GXQYMPHE.cjs} +1 -1
- package/dist/{chunk-TBPR2QND.cjs → chunk-IUIRVKJI.cjs} +736 -399
- package/dist/chunk-TBUEXRFQ.cjs +318 -0
- package/dist/{chunk-FODE2W2H.js → chunk-XGP2MN5R.js} +688 -351
- package/dist/{chunk-6JOMESNT.js → chunk-XL4CISVQ.js} +1 -1
- package/dist/chunk-ZSKSXOCZ.js +139 -0
- package/dist/convex-command-subscription-Cabh9tND.d.ts +41 -0
- package/dist/convex-command-subscription-DUgpu75l.d.cts +41 -0
- package/dist/device-agent/index.cjs +973 -44
- package/dist/device-agent/index.d.cts +339 -3
- package/dist/device-agent/index.d.ts +339 -3
- package/dist/device-agent/index.js +940 -11
- package/dist/{engine-CCSSqPbZ.d.cts → engine-BSvSdCSE.d.cts} +227 -2
- package/dist/{engine-CCSSqPbZ.d.ts → engine-BSvSdCSE.d.ts} +227 -2
- package/dist/index.cjs +419 -937
- package/dist/index.d.cts +315 -32
- package/dist/index.d.ts +315 -32
- package/dist/index.js +423 -941
- package/dist/plugin.cjs +11 -13
- package/dist/plugin.d.cts +15 -1
- package/dist/plugin.d.ts +15 -1
- package/dist/plugin.js +7 -9
- package/dist/realtime/index.cjs +31 -0
- package/dist/realtime/index.d.cts +15 -0
- package/dist/realtime/index.d.ts +15 -0
- package/dist/realtime/index.js +31 -0
- package/dist/ui/index.cjs +1297 -0
- package/dist/ui/index.d.cts +493 -0
- package/dist/ui/index.d.ts +493 -0
- package/dist/ui/index.js +1297 -0
- package/dist/version.cjs +2 -2
- package/dist/version.js +1 -1
- package/package.json +31 -15
- package/src/advanced.ts +11 -9
- package/src/auto-resume.tsx +259 -0
- package/src/camera-controls.ts +99 -0
- package/src/camera-grant-provider.ts +392 -0
- package/src/camera-settings.ts +133 -0
- package/src/contracts.ts +73 -0
- package/src/device-agent/agent.ts +262 -9
- package/src/device-agent/beat-policy.ts +46 -0
- package/src/device-agent/component.tsx +11 -0
- package/src/device-agent/convex-beat-transport.ts +140 -0
- package/src/device-agent/convex-command-subscription.ts +101 -0
- package/src/device-agent/convex-control-channel.ts +7 -1
- package/src/device-agent/http-transport.ts +22 -1
- package/src/device-agent/index.ts +22 -0
- package/src/device-agent/mqtt-channel.ts +379 -0
- package/src/device-agent/mqtt-codec.ts +347 -0
- package/src/device-agent/tournament.ts +352 -0
- package/src/engine.ts +580 -15
- package/src/http-session-provider.ts +21 -189
- package/src/index.ts +84 -5
- package/src/local-video-quality.ts +43 -0
- package/src/native-runtime.ts +234 -78
- package/src/plugin.ts +19 -6
- package/src/provider.tsx +94 -18
- package/src/realtime/index.ts +56 -0
- package/src/resume-policy.ts +85 -0
- package/src/resume.ts +132 -0
- package/src/rtmp-room-adapter.ts +164 -12
- package/src/session-grant.ts +256 -0
- package/src/ui/camera-screen.tsx +484 -0
- package/src/ui/controls-sheet.tsx +445 -0
- package/src/ui/copy.ts +326 -0
- package/src/ui/helpers.ts +105 -0
- package/src/ui/hooks.ts +59 -0
- package/src/ui/index.ts +42 -0
- package/src/ui/preview-view.tsx +39 -0
- package/src/ui/primitives.tsx +304 -0
- package/src/uplink-check.ts +173 -0
- package/dist/advanced-BXDrDl1h.d.ts +0 -316
- package/dist/advanced-BdAa2Erv.d.cts +0 -316
- package/src/livekit-room-adapter.ts +0 -390
- package/src/livestream-screen.tsx +0 -1002
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _chunkIUIRVKJIcjs = require('./chunk-IUIRVKJI.cjs');
|
|
10
|
+
|
|
11
|
+
// src/session-grant.ts
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function isFiniteNumber(value) {
|
|
16
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
17
|
+
}
|
|
18
|
+
function isBootstrap(value) {
|
|
19
|
+
return isRecord(value) && _chunkIUIRVKJIcjs.isSdkRemoteConfig.call(void 0, value.config) && isFiniteNumber(value.serverTime) && typeof value.rolloutEnabled === "boolean" && (value.message === void 0 || typeof value.message === "string");
|
|
20
|
+
}
|
|
21
|
+
function isTelemetryCredentials(value) {
|
|
22
|
+
return isRecord(value) && typeof value.endpoint === "string" && /^https:\/\//.test(value.endpoint) && typeof value.token === "string" && value.token.length > 0 && isFiniteNumber(value.expiresAt);
|
|
23
|
+
}
|
|
24
|
+
function isSessionGrant(value) {
|
|
25
|
+
return isRecord(value) && typeof value.sessionId === "string" && value.sessionId.length > 0 && typeof value.serverUrl === "string" && /^rtmps?:\/\//i.test(value.serverUrl) && typeof value.participantToken === "string" && value.participantToken.length > 0 && (value.playbackUrl === null || typeof value.playbackUrl === "string") && isFiniteNumber(value.tokenExpiresAt) && isTelemetryCredentials(value.telemetry) && _chunkIUIRVKJIcjs.isSdkRemoteConfig.call(void 0, value.config);
|
|
26
|
+
}
|
|
27
|
+
function optionalNullableString(value) {
|
|
28
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
29
|
+
}
|
|
30
|
+
function isCameraGrant(value) {
|
|
31
|
+
return isSessionGrant(value) && /^rtmps?:\/\//i.test(value.serverUrl) && (value.status === "scheduled" || value.status === "live") && optionalNullableString(value.matchRef) && optionalNullableString(value.courtRef) && optionalNullableString(value.watchUrl) && optionalNullableString(value.convexUrl);
|
|
32
|
+
}
|
|
33
|
+
function isSessionRefresh(value) {
|
|
34
|
+
if (!isRecord(value) || !_chunkIUIRVKJIcjs.isSdkRemoteConfig.call(void 0, value.config)) return false;
|
|
35
|
+
if (value.participantToken !== void 0 && (typeof value.participantToken !== "string" || !value.participantToken)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (value.tokenExpiresAt !== void 0 && !isFiniteNumber(value.tokenExpiresAt)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return value.telemetry === void 0 || isTelemetryCredentials(value.telemetry);
|
|
42
|
+
}
|
|
43
|
+
function isSessionStatus(value) {
|
|
44
|
+
return isRecord(value) && typeof value.sessionId === "string" && value.sessionId.length > 0 && typeof value.status === "string" && value.status.length > 0;
|
|
45
|
+
}
|
|
46
|
+
function sessionApiBaseFromTelemetryEndpoint(endpoint) {
|
|
47
|
+
if (!/^https:\/\//.test(endpoint) || !endpoint.endsWith("/telemetry")) {
|
|
48
|
+
throw new (0, _chunkIUIRVKJIcjs.InvalidResponseError)("The telemetry endpoint must be an HTTPS \u2026/telemetry URL");
|
|
49
|
+
}
|
|
50
|
+
return endpoint.slice(0, -"/telemetry".length);
|
|
51
|
+
}
|
|
52
|
+
var PRIVATE_LAN_HOST = /^(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})$/;
|
|
53
|
+
function normalizeBaseUrl(value) {
|
|
54
|
+
try {
|
|
55
|
+
const url = new URL(value);
|
|
56
|
+
const loopbackHosts = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
57
|
+
const secure = url.protocol === "https:";
|
|
58
|
+
const localDevelopment = url.protocol === "http:" && loopbackHosts.has(url.hostname);
|
|
59
|
+
const devPrivateLan = typeof __DEV__ !== "undefined" && __DEV__ === true && url.protocol === "http:" && PRIVATE_LAN_HOST.test(url.hostname);
|
|
60
|
+
if (!secure && !localDevelopment && !devPrivateLan || url.username || url.password) {
|
|
61
|
+
throw new Error();
|
|
62
|
+
}
|
|
63
|
+
return value.replace(/\/+$/, "");
|
|
64
|
+
} catch (e) {
|
|
65
|
+
throw new (0, _chunkIUIRVKJIcjs.ExpoSdkConfigurationError)(
|
|
66
|
+
"baseUrl must use HTTPS (HTTP is allowed only for localhost loopback)"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function joinUrl(base, path) {
|
|
71
|
+
return `${base}/${path.replace(/^\/+/, "")}`;
|
|
72
|
+
}
|
|
73
|
+
function parseEnvelopeError(envelope, httpStatus) {
|
|
74
|
+
const body = isRecord(envelope) && isRecord(envelope.error) ? envelope.error : null;
|
|
75
|
+
const error = body && typeof body.code === "string" ? _chunkIUIRVKJIcjs.errorFromCode.call(void 0, body.code, typeof body.message === "string" ? body.message : void 0) : new (0, _chunkIUIRVKJIcjs.InvalidResponseError)();
|
|
76
|
+
return Object.assign(
|
|
77
|
+
error,
|
|
78
|
+
{ httpStatus },
|
|
79
|
+
body && typeof body.reason === "string" ? { reason: body.reason } : {},
|
|
80
|
+
body && typeof body.retryAfterMs === "number" ? { retryAfterMs: body.retryAfterMs } : {}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function createJsonPoster(options = {}) {
|
|
84
|
+
const fetchImplementation = _nullishCoalesce(options.fetch, () => ( globalThis.fetch));
|
|
85
|
+
if (typeof fetchImplementation !== "function") {
|
|
86
|
+
throw new (0, _chunkIUIRVKJIcjs.ExpoSdkConfigurationError)("A Fetch API implementation is required");
|
|
87
|
+
}
|
|
88
|
+
const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( 1e4));
|
|
89
|
+
return async function post(url, body, headers = {}) {
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetchImplementation(url, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
redirect: "error"
|
|
99
|
+
});
|
|
100
|
+
const envelope = await response.json().catch(() => {
|
|
101
|
+
throw new (0, _chunkIUIRVKJIcjs.InvalidResponseError)();
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) throw parseEnvelopeError(envelope, response.status);
|
|
104
|
+
if (!isRecord(envelope) || !("data" in envelope)) {
|
|
105
|
+
throw new (0, _chunkIUIRVKJIcjs.InvalidResponseError)();
|
|
106
|
+
}
|
|
107
|
+
return envelope.data;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (controller.signal.aborted) throw new (0, _chunkIUIRVKJIcjs.ExpoSdkTimeoutError)();
|
|
110
|
+
if (error instanceof _chunkIUIRVKJIcjs.PickleballExpoError) throw error;
|
|
111
|
+
if (error instanceof TypeError) {
|
|
112
|
+
throw new (0, _chunkIUIRVKJIcjs.NetworkUnavailableError)(void 0, { cause: error });
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function httpStatusOf(error) {
|
|
121
|
+
const status = _optionalChain([error, 'optionalAccess', _ => _.httpStatus]);
|
|
122
|
+
return typeof status === "number" ? status : void 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
exports.isRecord = isRecord; exports.isFiniteNumber = isFiniteNumber; exports.isBootstrap = isBootstrap; exports.isTelemetryCredentials = isTelemetryCredentials; exports.isSessionGrant = isSessionGrant; exports.isCameraGrant = isCameraGrant; exports.isSessionRefresh = isSessionRefresh; exports.isSessionStatus = isSessionStatus; exports.sessionApiBaseFromTelemetryEndpoint = sessionApiBaseFromTelemetryEndpoint; exports.normalizeBaseUrl = normalizeBaseUrl; exports.joinUrl = joinUrl; exports.createJsonPoster = createJsonPoster; exports.httpStatusOf = httpStatusOf;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/device-agent/convex-command-subscription.ts
|
|
2
|
+
import { ConvexClient } from "convex/browser";
|
|
3
|
+
import { anyApi } from "convex/server";
|
|
4
|
+
function createConvexCommandSubscription(convexUrl) {
|
|
5
|
+
return createConvexDeviceRealtime(convexUrl).commandSubscription;
|
|
6
|
+
}
|
|
7
|
+
function createConvexDeviceRealtime(convexUrl) {
|
|
8
|
+
const client = new ConvexClient(convexUrl);
|
|
9
|
+
return {
|
|
10
|
+
commandSubscription: {
|
|
11
|
+
subscribe(credentials, sessionId, onCommands, onError) {
|
|
12
|
+
return client.onUpdate(
|
|
13
|
+
anyApi.sessionCommands.pendingForSession,
|
|
14
|
+
{ sessionId, token: credentials.token, after: 0 },
|
|
15
|
+
(value) => onCommands(toCommands(value)),
|
|
16
|
+
(error) => onError?.(error)
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
// Client dùng chung với beat — đóng ở dispose() của cả cụm, không ở đây.
|
|
20
|
+
dispose() {
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
async beat(args) {
|
|
24
|
+
const result = await client.mutation(anyApi.deviceApp.beat, args);
|
|
25
|
+
return result !== null && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
26
|
+
},
|
|
27
|
+
dispose() {
|
|
28
|
+
void client.close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function toCommands(value) {
|
|
33
|
+
if (value === null || typeof value !== "object") return [];
|
|
34
|
+
const commands = value.commands;
|
|
35
|
+
if (!Array.isArray(commands)) return [];
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const item of commands) {
|
|
38
|
+
if (item === null || typeof item !== "object") continue;
|
|
39
|
+
const record = item;
|
|
40
|
+
if (typeof record.id === "string" && (record.action === "start" || record.action === "stop" || record.action === "end") && typeof record.issuedAt === "number" && Number.isFinite(record.issuedAt)) {
|
|
41
|
+
out.push({ id: record.id, action: record.action, issuedAt: record.issuedAt });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
createConvexCommandSubscription,
|
|
49
|
+
createConvexDeviceRealtime
|
|
50
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/device-agent/convex-command-subscription.ts
|
|
2
|
+
var _browser = require('convex/browser');
|
|
3
|
+
var _server = require('convex/server');
|
|
4
|
+
function createConvexCommandSubscription(convexUrl) {
|
|
5
|
+
return createConvexDeviceRealtime(convexUrl).commandSubscription;
|
|
6
|
+
}
|
|
7
|
+
function createConvexDeviceRealtime(convexUrl) {
|
|
8
|
+
const client = new (0, _browser.ConvexClient)(convexUrl);
|
|
9
|
+
return {
|
|
10
|
+
commandSubscription: {
|
|
11
|
+
subscribe(credentials, sessionId, onCommands, onError) {
|
|
12
|
+
return client.onUpdate(
|
|
13
|
+
_server.anyApi.sessionCommands.pendingForSession,
|
|
14
|
+
{ sessionId, token: credentials.token, after: 0 },
|
|
15
|
+
(value) => onCommands(toCommands(value)),
|
|
16
|
+
(error) => _optionalChain([onError, 'optionalCall', _ => _(error)])
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
// Client dùng chung với beat — đóng ở dispose() của cả cụm, không ở đây.
|
|
20
|
+
dispose() {
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
async beat(args) {
|
|
24
|
+
const result = await client.mutation(_server.anyApi.deviceApp.beat, args);
|
|
25
|
+
return result !== null && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
26
|
+
},
|
|
27
|
+
dispose() {
|
|
28
|
+
void client.close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function toCommands(value) {
|
|
33
|
+
if (value === null || typeof value !== "object") return [];
|
|
34
|
+
const commands = value.commands;
|
|
35
|
+
if (!Array.isArray(commands)) return [];
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const item of commands) {
|
|
38
|
+
if (item === null || typeof item !== "object") continue;
|
|
39
|
+
const record = item;
|
|
40
|
+
if (typeof record.id === "string" && (record.action === "start" || record.action === "stop" || record.action === "end") && typeof record.issuedAt === "number" && Number.isFinite(record.issuedAt)) {
|
|
41
|
+
out.push({ id: record.id, action: record.action, issuedAt: record.issuedAt });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
exports.createConvexCommandSubscription = createConvexCommandSubscription; exports.createConvexDeviceRealtime = createConvexDeviceRealtime;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDefaultStorage,
|
|
3
|
+
createRtmpRoomFactory,
|
|
4
|
+
useLivestream,
|
|
5
|
+
useLivestreamHost
|
|
6
|
+
} from "./chunk-XGP2MN5R.js";
|
|
7
|
+
import {
|
|
8
|
+
__require
|
|
9
|
+
} from "./chunk-KFQGP6VL.js";
|
|
10
|
+
|
|
11
|
+
// src/resume-policy.ts
|
|
12
|
+
function classifyResumeStatus(status) {
|
|
13
|
+
if (status === 404 || status === 409 || status === 410) return "gone";
|
|
14
|
+
if (status >= 200 && status < 300) return "ok";
|
|
15
|
+
return "retry";
|
|
16
|
+
}
|
|
17
|
+
var RESUME_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
18
|
+
function resumeRetryDelayMs(attempt) {
|
|
19
|
+
const index = Math.min(Math.max(attempt, 0), RESUME_RETRY_DELAYS_MS.length - 1);
|
|
20
|
+
return RESUME_RETRY_DELAYS_MS[index];
|
|
21
|
+
}
|
|
22
|
+
function resumeBlocksArm(input) {
|
|
23
|
+
if (input.engineSessionId !== null) return false;
|
|
24
|
+
return input.adoptableSessionId !== null || input.serverSessionId !== null;
|
|
25
|
+
}
|
|
26
|
+
function resumeExternalSessionId(sessionId) {
|
|
27
|
+
return `resume-${sessionId}`;
|
|
28
|
+
}
|
|
29
|
+
function resumeGrantMatches(externalSessionId, grantSessionId) {
|
|
30
|
+
return externalSessionId === resumeExternalSessionId(grantSessionId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/auto-resume.tsx
|
|
34
|
+
import { useEffect, useRef, useState } from "react";
|
|
35
|
+
function useLatest(value) {
|
|
36
|
+
const ref = useRef(value);
|
|
37
|
+
ref.current = value;
|
|
38
|
+
return ref;
|
|
39
|
+
}
|
|
40
|
+
function PickleballAutoResume(props) {
|
|
41
|
+
const {
|
|
42
|
+
provider,
|
|
43
|
+
resumeInput,
|
|
44
|
+
enabled = true,
|
|
45
|
+
sessionIdHint = null,
|
|
46
|
+
retryDelayMs = resumeRetryDelayMs,
|
|
47
|
+
onAttempt
|
|
48
|
+
} = props;
|
|
49
|
+
const live = useLivestream();
|
|
50
|
+
const { engine } = useLivestreamHost();
|
|
51
|
+
const goneRef = useRef(null);
|
|
52
|
+
const attemptRef = useRef(0);
|
|
53
|
+
const [retryTick, setRetryTick] = useState(0);
|
|
54
|
+
const { state, sessionId, adoptableSessionId } = live;
|
|
55
|
+
const resumeId = adoptableSessionId ?? sessionIdHint;
|
|
56
|
+
const liveRef = useLatest(live);
|
|
57
|
+
const onAttemptRef = useLatest(onAttempt);
|
|
58
|
+
const resumeInputRef = useLatest(resumeInput);
|
|
59
|
+
const retryDelayRef = useLatest(retryDelayMs);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!enabled) return;
|
|
62
|
+
if (!resumeId || sessionId !== null || state !== "preview") return;
|
|
63
|
+
if (goneRef.current === resumeId) return;
|
|
64
|
+
let cancelled = false;
|
|
65
|
+
let timer;
|
|
66
|
+
const scheduleRetry = () => {
|
|
67
|
+
const delay = retryDelayRef.current(attemptRef.current);
|
|
68
|
+
attemptRef.current += 1;
|
|
69
|
+
timer = setTimeout(() => {
|
|
70
|
+
if (!cancelled) setRetryTick((tick) => tick + 1);
|
|
71
|
+
}, delay);
|
|
72
|
+
};
|
|
73
|
+
void (async () => {
|
|
74
|
+
const result = await provider.resume(resumeId);
|
|
75
|
+
if (cancelled) return;
|
|
76
|
+
onAttemptRef.current?.(resumeId, result.kind);
|
|
77
|
+
if (result.kind === "gone") {
|
|
78
|
+
goneRef.current = resumeId;
|
|
79
|
+
attemptRef.current = 0;
|
|
80
|
+
void engine.abandonAdoptableSession(resumeId).catch(() => void 0);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (result.kind === "retry") {
|
|
84
|
+
scheduleRetry();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
attemptRef.current = 0;
|
|
88
|
+
provider.adoptResumeGrant(result.grant);
|
|
89
|
+
const resumeInput2 = resumeInputRef.current;
|
|
90
|
+
await liveRef.current.start(
|
|
91
|
+
{
|
|
92
|
+
externalSessionId: resumeExternalSessionId(resumeId),
|
|
93
|
+
title: resumeInput2.titleFor?.(resumeId) ?? "Phi\xEAn \u0111ang kh\xF4i ph\u1EE5c",
|
|
94
|
+
consentVersion: resumeInput2.consentVersion,
|
|
95
|
+
visibility: resumeInput2.visibility ?? "public",
|
|
96
|
+
...resumeInput2.metadata ? { metadata: resumeInput2.metadata } : {}
|
|
97
|
+
},
|
|
98
|
+
// Trạng thái lấy từ chính phản hồi refresh, KHÔNG đoán: gán cứng "live" là
|
|
99
|
+
// camera tự lên sóng khi chủ nó chỉ đang trực sân (18/08/2026).
|
|
100
|
+
{ autoPublish: result.live }
|
|
101
|
+
).catch(() => {
|
|
102
|
+
onAttemptRef.current?.(resumeId, "start_failed");
|
|
103
|
+
if (!cancelled) scheduleRetry();
|
|
104
|
+
});
|
|
105
|
+
})();
|
|
106
|
+
return () => {
|
|
107
|
+
cancelled = true;
|
|
108
|
+
if (timer) clearTimeout(timer);
|
|
109
|
+
};
|
|
110
|
+
}, [enabled, provider, resumeId, sessionId, state, retryTick, engine, liveRef, onAttemptRef, resumeInputRef, retryDelayRef]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!enabled || state !== "error" || sessionId === null) return;
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
onAttemptRef.current?.(sessionId, "publish_retry");
|
|
115
|
+
void liveRef.current.retry().catch(() => void 0);
|
|
116
|
+
}, retryDelayRef.current(attemptRef.current++));
|
|
117
|
+
return () => clearTimeout(timer);
|
|
118
|
+
}, [enabled, state, sessionId, liveRef, onAttemptRef, retryDelayRef]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!enabled || state !== "error" || sessionId !== null || !adoptableSessionId) return;
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
onAttemptRef.current?.(adoptableSessionId, "reprepare");
|
|
123
|
+
void liveRef.current.prepare().catch(() => void 0);
|
|
124
|
+
}, 2e3);
|
|
125
|
+
return () => clearTimeout(timer);
|
|
126
|
+
}, [enabled, state, sessionId, adoptableSessionId, liveRef, onAttemptRef]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (state === "live" || state === "standby") attemptRef.current = 0;
|
|
129
|
+
}, [state]);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function PickleballAutoPrepare(props) {
|
|
133
|
+
const { enabled = true, captureOrientation } = props;
|
|
134
|
+
const live = useLivestream();
|
|
135
|
+
const { state } = live;
|
|
136
|
+
const liveRef = useLatest(live);
|
|
137
|
+
const orientedRef = useRef(false);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!enabled || !captureOrientation || orientedRef.current) return;
|
|
140
|
+
orientedRef.current = true;
|
|
141
|
+
void liveRef.current.setCaptureOrientation(captureOrientation).catch(() => void 0);
|
|
142
|
+
}, [enabled, captureOrientation, liveRef]);
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (!enabled) return;
|
|
145
|
+
if (state !== "idle" && state !== "ended") return;
|
|
146
|
+
void liveRef.current.prepare().catch(() => void 0);
|
|
147
|
+
}, [enabled, state, liveRef]);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function useStandbySession(options) {
|
|
151
|
+
const { input, enabled = true, resumeSessionId = null } = options;
|
|
152
|
+
const live = useLivestream();
|
|
153
|
+
const { state, sessionId, adoptableSessionId } = live;
|
|
154
|
+
const liveRef = useLatest(live);
|
|
155
|
+
const armedForRef = useRef(null);
|
|
156
|
+
const inputRef = useLatest(input);
|
|
157
|
+
const externalSessionId = input.externalSessionId;
|
|
158
|
+
const armed = sessionId !== null && armedForRef.current === externalSessionId;
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!enabled) return;
|
|
161
|
+
if (state !== "preview" || sessionId !== null) return;
|
|
162
|
+
if (armedForRef.current === externalSessionId) return;
|
|
163
|
+
if (resumeBlocksArm({
|
|
164
|
+
adoptableSessionId,
|
|
165
|
+
serverSessionId: resumeSessionId,
|
|
166
|
+
engineSessionId: sessionId
|
|
167
|
+
})) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
armedForRef.current = externalSessionId;
|
|
171
|
+
void liveRef.current.start(inputRef.current, { autoPublish: false }).catch(() => {
|
|
172
|
+
armedForRef.current = null;
|
|
173
|
+
});
|
|
174
|
+
}, [enabled, state, sessionId, adoptableSessionId, resumeSessionId, externalSessionId, liveRef, inputRef]);
|
|
175
|
+
return { armed };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/camera-settings.ts
|
|
179
|
+
import { useSyncExternalStore } from "react";
|
|
180
|
+
var CAMERA_SETTINGS_STORAGE_KEY = "@pickleball/expo-sdk/camera-settings";
|
|
181
|
+
var DEFAULT_CAMERA_SETTINGS = Object.freeze({
|
|
182
|
+
resolution: 720,
|
|
183
|
+
fps: 30,
|
|
184
|
+
antiFlicker: "50hz",
|
|
185
|
+
cameraId: null
|
|
186
|
+
});
|
|
187
|
+
function parseCameraSettings(raw, defaults = DEFAULT_CAMERA_SETTINGS) {
|
|
188
|
+
if (!raw) return { ...defaults };
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(raw);
|
|
191
|
+
if (parsed === null || typeof parsed !== "object") return { ...defaults };
|
|
192
|
+
return {
|
|
193
|
+
resolution: parsed.resolution === 1080 ? 1080 : parsed.resolution === 720 ? 720 : defaults.resolution,
|
|
194
|
+
fps: parsed.fps === 24 || parsed.fps === 30 || parsed.fps === 60 ? parsed.fps : defaults.fps,
|
|
195
|
+
antiFlicker: parsed.antiFlicker === "auto" || parsed.antiFlicker === "60hz" || parsed.antiFlicker === "50hz" ? parsed.antiFlicker : defaults.antiFlicker,
|
|
196
|
+
cameraId: typeof parsed.cameraId === "string" ? parsed.cameraId : null
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return { ...defaults };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function createCameraSettingsStore(options = {}) {
|
|
203
|
+
const storage = options.storage ?? createDefaultStorage();
|
|
204
|
+
const key = options.key ?? CAMERA_SETTINGS_STORAGE_KEY;
|
|
205
|
+
const defaults = { ...DEFAULT_CAMERA_SETTINGS, ...options.defaults };
|
|
206
|
+
let settings = { ...defaults };
|
|
207
|
+
let restored = false;
|
|
208
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
209
|
+
const emit = () => {
|
|
210
|
+
for (const listener of listeners) listener();
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
key,
|
|
214
|
+
get: () => settings,
|
|
215
|
+
set(patch) {
|
|
216
|
+
settings = { ...settings, ...patch };
|
|
217
|
+
void storage.setItem(key, JSON.stringify(settings)).catch(() => void 0);
|
|
218
|
+
emit();
|
|
219
|
+
},
|
|
220
|
+
subscribe(listener) {
|
|
221
|
+
listeners.add(listener);
|
|
222
|
+
return () => {
|
|
223
|
+
listeners.delete(listener);
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
async restore() {
|
|
227
|
+
const raw = await storage.getItem(key).catch(() => null);
|
|
228
|
+
settings = parseCameraSettings(raw, defaults);
|
|
229
|
+
restored = true;
|
|
230
|
+
emit();
|
|
231
|
+
},
|
|
232
|
+
isRestored: () => restored
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function useCameraSettings(store) {
|
|
236
|
+
return useSyncExternalStore(store.subscribe, store.get, store.get);
|
|
237
|
+
}
|
|
238
|
+
function cameraSettingsKey(settings) {
|
|
239
|
+
return `${settings.resolution}:${settings.fps}:${settings.cameraId ?? "default"}`;
|
|
240
|
+
}
|
|
241
|
+
function rtmpRoomFactoryFor(settings) {
|
|
242
|
+
return createRtmpRoomFactory({
|
|
243
|
+
frameRate: settings.fps,
|
|
244
|
+
...settings.cameraId ? { cameraId: settings.cameraId } : {}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/camera-controls.ts
|
|
249
|
+
function nativeModule() {
|
|
250
|
+
try {
|
|
251
|
+
const loaded = __require("@pickleball/rtmp-native");
|
|
252
|
+
const candidate = loaded?.default ?? loaded;
|
|
253
|
+
return candidate && typeof candidate === "object" ? candidate : null;
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function callOptional(name, invoke, fallback) {
|
|
259
|
+
const native = nativeModule();
|
|
260
|
+
if (!native || typeof native[name] !== "function") return fallback;
|
|
261
|
+
try {
|
|
262
|
+
const result = await invoke(native);
|
|
263
|
+
return result === void 0 ? fallback : result;
|
|
264
|
+
} catch {
|
|
265
|
+
return fallback;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function getCameraCapabilities() {
|
|
269
|
+
return callOptional("getCameraCapabilities", (n) => n.getCameraCapabilities?.(), null);
|
|
270
|
+
}
|
|
271
|
+
function setZoom(factor) {
|
|
272
|
+
return callOptional("setZoom", (n) => n.setZoom?.(factor), void 0);
|
|
273
|
+
}
|
|
274
|
+
function setExposureBias(ev) {
|
|
275
|
+
return callOptional("setExposureBias", (n) => n.setExposureBias?.(ev), void 0);
|
|
276
|
+
}
|
|
277
|
+
function setFocusLocked(locked) {
|
|
278
|
+
return callOptional("setFocusLocked", (n) => n.setFocusLocked?.(locked), void 0);
|
|
279
|
+
}
|
|
280
|
+
function setExposureLocked(locked) {
|
|
281
|
+
return callOptional("setExposureLocked", (n) => n.setExposureLocked?.(locked), void 0);
|
|
282
|
+
}
|
|
283
|
+
function setTorch(enabled) {
|
|
284
|
+
return callOptional("setTorch", (n) => n.setTorch?.(enabled), void 0);
|
|
285
|
+
}
|
|
286
|
+
function applyAntiFlicker(mode) {
|
|
287
|
+
return callOptional("setAntiFlicker", (n) => n.setAntiFlicker?.(mode), void 0);
|
|
288
|
+
}
|
|
289
|
+
function requestNativePermissions() {
|
|
290
|
+
return callOptional("requestPermissions", (n) => n.requestPermissions?.(), null);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export {
|
|
294
|
+
classifyResumeStatus,
|
|
295
|
+
RESUME_RETRY_DELAYS_MS,
|
|
296
|
+
resumeRetryDelayMs,
|
|
297
|
+
resumeBlocksArm,
|
|
298
|
+
resumeExternalSessionId,
|
|
299
|
+
resumeGrantMatches,
|
|
300
|
+
PickleballAutoResume,
|
|
301
|
+
PickleballAutoPrepare,
|
|
302
|
+
useStandbySession,
|
|
303
|
+
CAMERA_SETTINGS_STORAGE_KEY,
|
|
304
|
+
DEFAULT_CAMERA_SETTINGS,
|
|
305
|
+
parseCameraSettings,
|
|
306
|
+
createCameraSettingsStore,
|
|
307
|
+
useCameraSettings,
|
|
308
|
+
cameraSettingsKey,
|
|
309
|
+
rtmpRoomFactoryFor,
|
|
310
|
+
getCameraCapabilities,
|
|
311
|
+
setZoom,
|
|
312
|
+
setExposureBias,
|
|
313
|
+
setFocusLocked,
|
|
314
|
+
setExposureLocked,
|
|
315
|
+
setTorch,
|
|
316
|
+
applyAntiFlicker,
|
|
317
|
+
requestNativePermissions
|
|
318
|
+
};
|