@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
|
@@ -2,23 +2,20 @@ import type {
|
|
|
2
2
|
BootstrapInput,
|
|
3
3
|
EndLivestreamInput,
|
|
4
4
|
LivestreamSessionProvider,
|
|
5
|
-
SdkBootstrap,
|
|
6
|
-
SdkSessionGrant,
|
|
7
|
-
SdkSessionRefresh,
|
|
8
|
-
SdkTelemetryCredentials,
|
|
9
5
|
StartLivestreamInput,
|
|
10
6
|
} from "./contracts";
|
|
11
|
-
import {
|
|
7
|
+
import { InvalidResponseError } from "./errors";
|
|
12
8
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
createJsonPoster,
|
|
10
|
+
isBootstrap,
|
|
11
|
+
isSessionGrant,
|
|
12
|
+
isSessionRefresh,
|
|
13
|
+
isSessionStatus,
|
|
14
|
+
joinUrl,
|
|
15
|
+
normalizeBaseUrl,
|
|
16
|
+
type Fetch,
|
|
17
|
+
} from "./session-grant";
|
|
20
18
|
|
|
21
|
-
type Fetch = typeof globalThis.fetch;
|
|
22
19
|
type HeadersInput = Record<string, string>;
|
|
23
20
|
|
|
24
21
|
export interface HttpSessionProviderPaths {
|
|
@@ -31,7 +28,11 @@ export interface HttpSessionProviderPaths {
|
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
export interface HttpSessionProviderOptions {
|
|
34
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Base URL của proxy do đối tác host (mô hình cũ) hoặc namespace first-party
|
|
33
|
+
* `/api/v2/device-app` (app fleet). Không bao giờ là URL PickLive kèm API key.
|
|
34
|
+
* App đối tác dùng camera grant thì dùng createCameraGrantProvider thay vì đây.
|
|
35
|
+
*/
|
|
35
36
|
baseUrl: string;
|
|
36
37
|
fetch?: Fetch;
|
|
37
38
|
headers?: HeadersInput | (() => HeadersInput | Promise<HeadersInput>);
|
|
@@ -53,48 +54,15 @@ export function createHttpSessionProvider(
|
|
|
53
54
|
options: HttpSessionProviderOptions,
|
|
54
55
|
): LivestreamSessionProvider {
|
|
55
56
|
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
56
|
-
const
|
|
57
|
-
if (typeof fetchImplementation !== "function") {
|
|
58
|
-
throw new ExpoSdkConfigurationError("A Fetch API implementation is required");
|
|
59
|
-
}
|
|
57
|
+
const postJson = createJsonPoster({ fetch: options.fetch, timeoutMs: options.timeoutMs });
|
|
60
58
|
const paths = { ...defaultPaths, ...options.paths };
|
|
61
59
|
|
|
62
60
|
async function post(path: string, body: unknown): Promise<unknown> {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
68
|
-
try {
|
|
69
|
-
const extraHeaders =
|
|
70
|
-
typeof options.headers === "function"
|
|
71
|
-
? await options.headers()
|
|
72
|
-
: (options.headers ?? {});
|
|
73
|
-
const response = await fetchImplementation(joinUrl(baseUrl, path), {
|
|
74
|
-
method: "POST",
|
|
75
|
-
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
76
|
-
body: JSON.stringify(body),
|
|
77
|
-
signal: controller.signal,
|
|
78
|
-
redirect: "error",
|
|
79
|
-
});
|
|
80
|
-
const envelope = await response.json().catch(() => {
|
|
81
|
-
throw new InvalidResponseError();
|
|
82
|
-
});
|
|
83
|
-
if (!response.ok) throw parseProxyError(envelope, response.status);
|
|
84
|
-
if (!isRecord(envelope) || !("data" in envelope)) {
|
|
85
|
-
throw new InvalidResponseError();
|
|
86
|
-
}
|
|
87
|
-
return envelope.data;
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (controller.signal.aborted) throw new ExpoSdkTimeoutError();
|
|
90
|
-
if (error instanceof PickleballExpoError) throw error;
|
|
91
|
-
if (error instanceof TypeError) {
|
|
92
|
-
throw new NetworkUnavailableError(undefined, { cause: error });
|
|
93
|
-
}
|
|
94
|
-
throw error;
|
|
95
|
-
} finally {
|
|
96
|
-
clearTimeout(timeout);
|
|
97
|
-
}
|
|
61
|
+
const extraHeaders =
|
|
62
|
+
typeof options.headers === "function"
|
|
63
|
+
? await options.headers()
|
|
64
|
+
: (options.headers ?? {});
|
|
65
|
+
return postJson(joinUrl(baseUrl, path), body, extraHeaders);
|
|
98
66
|
}
|
|
99
67
|
|
|
100
68
|
return {
|
|
@@ -127,139 +95,3 @@ export function createHttpSessionProvider(
|
|
|
127
95
|
},
|
|
128
96
|
};
|
|
129
97
|
}
|
|
130
|
-
|
|
131
|
-
declare const __DEV__: boolean | undefined;
|
|
132
|
-
|
|
133
|
-
// RFC 1918 private ranges: 10/8, 172.16/12, 192.168/16.
|
|
134
|
-
const PRIVATE_LAN_HOST =
|
|
135
|
-
/^(?: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})$/;
|
|
136
|
-
|
|
137
|
-
function normalizeBaseUrl(value: string) {
|
|
138
|
-
try {
|
|
139
|
-
const url = new URL(value);
|
|
140
|
-
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
141
|
-
const secure = url.protocol === "https:";
|
|
142
|
-
const localDevelopment =
|
|
143
|
-
url.protocol === "http:" && loopbackHosts.has(url.hostname);
|
|
144
|
-
// Dev bundle only (Metro/dev client): a device on the same network must
|
|
145
|
-
// reach the dev proxy over plain HTTP at a private LAN address. Release
|
|
146
|
-
// bundles compile __DEV__ to false, so production keeps the HTTPS rule.
|
|
147
|
-
const devPrivateLan =
|
|
148
|
-
typeof __DEV__ !== "undefined" &&
|
|
149
|
-
__DEV__ === true &&
|
|
150
|
-
url.protocol === "http:" &&
|
|
151
|
-
PRIVATE_LAN_HOST.test(url.hostname);
|
|
152
|
-
if (
|
|
153
|
-
(!secure && !localDevelopment && !devPrivateLan) ||
|
|
154
|
-
url.username ||
|
|
155
|
-
url.password
|
|
156
|
-
) {
|
|
157
|
-
throw new Error();
|
|
158
|
-
}
|
|
159
|
-
return value.replace(/\/+$/, "");
|
|
160
|
-
} catch {
|
|
161
|
-
throw new ExpoSdkConfigurationError(
|
|
162
|
-
"baseUrl must use HTTPS (HTTP is allowed only for localhost loopback)",
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function joinUrl(base: string, path: string) {
|
|
168
|
-
return `${base}/${path.replace(/^\/+/, "")}`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function parseProxyError(envelope: unknown, httpStatus: number) {
|
|
172
|
-
const error =
|
|
173
|
-
isRecord(envelope) &&
|
|
174
|
-
isRecord(envelope.error) &&
|
|
175
|
-
typeof envelope.error.code === "string"
|
|
176
|
-
? errorFromCode(
|
|
177
|
-
envelope.error.code,
|
|
178
|
-
typeof envelope.error.message === "string"
|
|
179
|
-
? envelope.error.message
|
|
180
|
-
: undefined,
|
|
181
|
-
)
|
|
182
|
-
: new InvalidResponseError();
|
|
183
|
-
// Mã lỗi trong envelope không phân biệt được "phiên đã biến mất" (404) với
|
|
184
|
-
// "server đang trục trặc" (5xx) — cả hai đều là UNKNOWN. Giữ lại mã HTTP để
|
|
185
|
-
// engine quyết được: 404 thì xoá dấu pending-end, 5xx thì giữ và thử lại.
|
|
186
|
-
return Object.assign(error, { httpStatus });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
191
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function isFiniteNumber(value: unknown): value is number {
|
|
195
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function isBootstrap(value: unknown): value is SdkBootstrap {
|
|
199
|
-
return (
|
|
200
|
-
isRecord(value) &&
|
|
201
|
-
isSdkRemoteConfig(value.config) &&
|
|
202
|
-
isFiniteNumber(value.serverTime) &&
|
|
203
|
-
typeof value.rolloutEnabled === "boolean" &&
|
|
204
|
-
(value.message === undefined || typeof value.message === "string")
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function isTelemetry(value: unknown): value is SdkTelemetryCredentials {
|
|
209
|
-
return (
|
|
210
|
-
isRecord(value) &&
|
|
211
|
-
typeof value.endpoint === "string" &&
|
|
212
|
-
/^https:\/\//.test(value.endpoint) &&
|
|
213
|
-
typeof value.token === "string" &&
|
|
214
|
-
value.token.length > 0 &&
|
|
215
|
-
isFiniteNumber(value.expiresAt)
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function isSessionGrant(value: unknown): value is SdkSessionGrant {
|
|
220
|
-
return (
|
|
221
|
-
isRecord(value) &&
|
|
222
|
-
typeof value.sessionId === "string" &&
|
|
223
|
-
value.sessionId.length > 0 &&
|
|
224
|
-
typeof value.serverUrl === "string" &&
|
|
225
|
-
// wss:// cho mode LiveKit, rtmp(s):// cho mode device-rtmp. Trước đây chỉ
|
|
226
|
-
// chấp nhận wss:// nên grant RTMP bị coi là "phản hồi máy chủ không hợp lệ".
|
|
227
|
-
// Vẫn chặn ws:// trần để không rơi xuống WebSocket không mã hoá.
|
|
228
|
-
/^(wss|rtmps?):\/\//.test(value.serverUrl) &&
|
|
229
|
-
typeof value.participantToken === "string" &&
|
|
230
|
-
value.participantToken.length > 0 &&
|
|
231
|
-
(value.playbackUrl === null || typeof value.playbackUrl === "string") &&
|
|
232
|
-
isFiniteNumber(value.tokenExpiresAt) &&
|
|
233
|
-
isTelemetry(value.telemetry) &&
|
|
234
|
-
isSdkRemoteConfig(value.config)
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function isSessionStatus(
|
|
239
|
-
value: unknown,
|
|
240
|
-
): value is { sessionId: string; status: string } {
|
|
241
|
-
return (
|
|
242
|
-
isRecord(value) &&
|
|
243
|
-
typeof value.sessionId === "string" &&
|
|
244
|
-
value.sessionId.length > 0 &&
|
|
245
|
-
typeof value.status === "string" &&
|
|
246
|
-
value.status.length > 0
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function isSessionRefresh(value: unknown): value is SdkSessionRefresh {
|
|
251
|
-
if (!isRecord(value) || !isSdkRemoteConfig(value.config)) return false;
|
|
252
|
-
if (
|
|
253
|
-
value.participantToken !== undefined &&
|
|
254
|
-
(typeof value.participantToken !== "string" || !value.participantToken)
|
|
255
|
-
) {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
if (
|
|
259
|
-
value.tokenExpiresAt !== undefined &&
|
|
260
|
-
!isFiniteNumber(value.tokenExpiresAt)
|
|
261
|
-
) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
return value.telemetry === undefined || isTelemetry(value.telemetry);
|
|
265
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Entry gọn cho tích hợp livestream cơ bản
|
|
2
|
+
* Entry gọn cho tích hợp livestream cơ bản (SDK 1.0: RTMP-only, camera grant).
|
|
3
|
+
* Các tính năng khác nằm ở subpath:
|
|
4
|
+
* @pickleball/expo-sdk/ui — màn hình camera + sheet cấu hình dựng sẵn
|
|
5
|
+
* @pickleball/expo-sdk/realtime — nhận lệnh qua websocket Convex (kéo `convex`)
|
|
3
6
|
* @pickleball/expo-sdk/match — scoreboard + persistence
|
|
4
7
|
* @pickleball/expo-sdk/match/voice — đọc điểm bằng loa
|
|
5
8
|
* @pickleball/expo-sdk/match/remote — remote BLE/HID
|
|
6
|
-
* @pickleball/expo-sdk/device-agent — điều khiển từ dashboard
|
|
9
|
+
* @pickleball/expo-sdk/device-agent — điều khiển từ dashboard (app fleet)
|
|
7
10
|
* @pickleball/expo-sdk/advanced — engine internals, DI, mock/test
|
|
8
11
|
* Metro không tree-shake nên chỉ import subpath nào app thật sự dùng.
|
|
9
12
|
*/
|
|
@@ -17,14 +20,88 @@ export {
|
|
|
17
20
|
type LivestreamContextValue,
|
|
18
21
|
type LivestreamTheme,
|
|
19
22
|
} from "./provider";
|
|
20
|
-
export { LivestreamScreen, type LivestreamScreenProps } from "./livestream-screen";
|
|
21
23
|
export {
|
|
22
24
|
createHttpSessionProvider,
|
|
23
25
|
type HttpSessionProviderOptions,
|
|
24
26
|
type HttpSessionProviderPaths,
|
|
25
27
|
} from "./http-session-provider";
|
|
26
|
-
//
|
|
27
|
-
export {
|
|
28
|
+
// Điện thoại đối tác — camera grant từ backend đối tác (ADR-0002)
|
|
29
|
+
export {
|
|
30
|
+
CAMERA_GRANT_STORAGE_KEY,
|
|
31
|
+
DEFAULT_LOCAL_CONFIG,
|
|
32
|
+
createCameraGrantProvider,
|
|
33
|
+
useCameraGrant,
|
|
34
|
+
type CameraGrantHandle,
|
|
35
|
+
type CameraGrantPhase,
|
|
36
|
+
type CameraGrantProvider,
|
|
37
|
+
type CameraGrantProviderOptions,
|
|
38
|
+
} from "./camera-grant-provider";
|
|
39
|
+
// Nhận lại phiên sau khi app bị kill
|
|
40
|
+
export {
|
|
41
|
+
createHttpResumeFetcher,
|
|
42
|
+
grantFromRefreshPayload,
|
|
43
|
+
withResume,
|
|
44
|
+
type HttpResumeFetcherOptions,
|
|
45
|
+
type ResumableSessionProvider,
|
|
46
|
+
type ResumeAttempt,
|
|
47
|
+
} from "./resume";
|
|
48
|
+
export {
|
|
49
|
+
RESUME_RETRY_DELAYS_MS,
|
|
50
|
+
classifyResumeStatus,
|
|
51
|
+
resumeBlocksArm,
|
|
52
|
+
resumeExternalSessionId,
|
|
53
|
+
resumeGrantMatches,
|
|
54
|
+
resumeRetryDelayMs,
|
|
55
|
+
type ResumeVerdict,
|
|
56
|
+
} from "./resume-policy";
|
|
57
|
+
export {
|
|
58
|
+
PickleballAutoPrepare,
|
|
59
|
+
PickleballAutoResume,
|
|
60
|
+
useStandbySession,
|
|
61
|
+
type PickleballAutoPrepareProps,
|
|
62
|
+
type PickleballAutoResumeProps,
|
|
63
|
+
type ResumeAttemptOutcome,
|
|
64
|
+
type UseStandbySessionOptions,
|
|
65
|
+
} from "./auto-resume";
|
|
66
|
+
export { withLocalVideoQuality } from "./local-video-quality";
|
|
67
|
+
// Cấu hình camera local + điều khiển runtime + đo mạng
|
|
68
|
+
export {
|
|
69
|
+
CAMERA_SETTINGS_STORAGE_KEY,
|
|
70
|
+
DEFAULT_CAMERA_SETTINGS,
|
|
71
|
+
cameraSettingsKey,
|
|
72
|
+
createCameraSettingsStore,
|
|
73
|
+
parseCameraSettings,
|
|
74
|
+
rtmpRoomFactoryFor,
|
|
75
|
+
useCameraSettings,
|
|
76
|
+
type AntiFlickerMode,
|
|
77
|
+
type CameraSettings,
|
|
78
|
+
type CameraSettingsStore,
|
|
79
|
+
type CameraSettingsStoreOptions,
|
|
80
|
+
} from "./camera-settings";
|
|
81
|
+
export {
|
|
82
|
+
applyAntiFlicker,
|
|
83
|
+
getCameraCapabilities,
|
|
84
|
+
requestNativePermissions,
|
|
85
|
+
setExposureBias,
|
|
86
|
+
setExposureLocked,
|
|
87
|
+
setFocusLocked,
|
|
88
|
+
setTorch,
|
|
89
|
+
setZoom,
|
|
90
|
+
type CameraCapabilities,
|
|
91
|
+
type CameraCapabilityRange,
|
|
92
|
+
} from "./camera-controls";
|
|
93
|
+
export {
|
|
94
|
+
AUDIO_BITRATE_BPS,
|
|
95
|
+
DEFAULT_UPLOAD_URL,
|
|
96
|
+
VIDEO_BITRATE_BPS,
|
|
97
|
+
requiredMbpsFor,
|
|
98
|
+
runUplinkCheck,
|
|
99
|
+
verdictFor,
|
|
100
|
+
type UplinkCheckOptions,
|
|
101
|
+
type UplinkCheckResult,
|
|
102
|
+
} from "./uplink-check";
|
|
103
|
+
// Runtime RTMP-only + roomFactory RTMP (mode mặc định cho app camera)
|
|
104
|
+
export { createDefaultStorage, createRtmpRoomFactory, createRtmpRuntime } from "./native-runtime";
|
|
28
105
|
// Type-only: xuất hiện trong LivestreamContextValue/ProviderProps
|
|
29
106
|
export type {
|
|
30
107
|
CameraDeviceInfo,
|
|
@@ -35,6 +112,8 @@ export type {
|
|
|
35
112
|
SdkNetwork,
|
|
36
113
|
SdkRuntime,
|
|
37
114
|
SdkStorage,
|
|
115
|
+
SessionCommandChannel,
|
|
116
|
+
SessionCommandSubscription,
|
|
38
117
|
TelemetrySink,
|
|
39
118
|
} from "./engine";
|
|
40
119
|
export { PICKLEBALL_EXPO_SDK_VERSION } from "./version";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { LivestreamSessionProvider } from "./contracts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Độ phân giải là lựa chọn LOCAL của máy — override `videoQuality` mà server trả
|
|
5
|
+
* trong bootstrap/start/refresh (engine truyền config.videoQuality vào
|
|
6
|
+
* room.preparePreview nên không cần sửa engine).
|
|
7
|
+
*
|
|
8
|
+
* Lúc TẠO phiên phải khai ngược lên server (`quality` trong StartLivestreamInput),
|
|
9
|
+
* nếu không bản ghi phiên nói dối: mặc định local 720 còn server 1080 — dashboard
|
|
10
|
+
* báo 1080 trong khi máy phát 720 suốt (18/08/2026). Chốt MỘT lần lúc start:
|
|
11
|
+
* gọi `resolution()` hai lần có thể ra hai giá trị khác nhau, khai một đằng dùng
|
|
12
|
+
* một nẻo thì tệ hơn là không khai.
|
|
13
|
+
*/
|
|
14
|
+
export function withLocalVideoQuality<P extends LivestreamSessionProvider>(
|
|
15
|
+
provider: P,
|
|
16
|
+
resolution: () => 720 | 1080,
|
|
17
|
+
): P {
|
|
18
|
+
// Spread giữ nguyên MỌI method khác của provider (resume/adoptResumeGrant của
|
|
19
|
+
// ResumableSessionProvider, getActiveGrant/subscribe của CameraGrantProvider)
|
|
20
|
+
// — bọc mà rơi mất chúng là đường nhận lại phiên gãy im lặng ở tầng type.
|
|
21
|
+
// Provider là object thuần (closure) nên spread đủ; class có method prototype
|
|
22
|
+
// thì không hỗ trợ.
|
|
23
|
+
return {
|
|
24
|
+
...provider,
|
|
25
|
+
async bootstrap(input) {
|
|
26
|
+
const result = await provider.bootstrap(input);
|
|
27
|
+
return { ...result, config: { ...result.config, videoQuality: resolution() } };
|
|
28
|
+
},
|
|
29
|
+
async start(input) {
|
|
30
|
+
const quality = resolution();
|
|
31
|
+
const result = await provider.start({ ...input, quality });
|
|
32
|
+
return { ...result, config: { ...result.config, videoQuality: quality } };
|
|
33
|
+
},
|
|
34
|
+
async refresh(sessionId) {
|
|
35
|
+
const result = await provider.refresh(sessionId);
|
|
36
|
+
return { ...result, config: { ...result.config, videoQuality: resolution() } };
|
|
37
|
+
},
|
|
38
|
+
end: (input) => provider.end(input),
|
|
39
|
+
// publish/unpublish là optional trong contract — forward nguyên trạng.
|
|
40
|
+
publish: provider.publish ? (sessionId) => provider.publish!(sessionId) : undefined,
|
|
41
|
+
unpublish: provider.unpublish ? (sessionId) => provider.unpublish!(sessionId) : undefined,
|
|
42
|
+
} as P;
|
|
43
|
+
}
|