@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
package/dist/index.js
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PICKLEBALL_EXPO_SDK_VERSION
|
|
3
|
+
} from "./chunk-XL4CISVQ.js";
|
|
4
|
+
import {
|
|
5
|
+
createJsonPoster,
|
|
6
|
+
httpStatusOf,
|
|
7
|
+
isBootstrap,
|
|
8
|
+
isCameraGrant,
|
|
9
|
+
isFiniteNumber,
|
|
10
|
+
isRecord,
|
|
11
|
+
isSessionGrant,
|
|
12
|
+
isSessionRefresh,
|
|
13
|
+
isSessionStatus,
|
|
14
|
+
isTelemetryCredentials,
|
|
15
|
+
joinUrl,
|
|
16
|
+
normalizeBaseUrl,
|
|
17
|
+
sessionApiBaseFromTelemetryEndpoint
|
|
18
|
+
} from "./chunk-ZSKSXOCZ.js";
|
|
19
|
+
import {
|
|
20
|
+
CAMERA_SETTINGS_STORAGE_KEY,
|
|
21
|
+
DEFAULT_CAMERA_SETTINGS,
|
|
22
|
+
PickleballAutoPrepare,
|
|
23
|
+
PickleballAutoResume,
|
|
24
|
+
RESUME_RETRY_DELAYS_MS,
|
|
25
|
+
applyAntiFlicker,
|
|
26
|
+
cameraSettingsKey,
|
|
27
|
+
classifyResumeStatus,
|
|
28
|
+
createCameraSettingsStore,
|
|
29
|
+
getCameraCapabilities,
|
|
30
|
+
parseCameraSettings,
|
|
31
|
+
requestNativePermissions,
|
|
32
|
+
resumeBlocksArm,
|
|
33
|
+
resumeExternalSessionId,
|
|
34
|
+
resumeGrantMatches,
|
|
35
|
+
resumeRetryDelayMs,
|
|
36
|
+
rtmpRoomFactoryFor,
|
|
37
|
+
setExposureBias,
|
|
38
|
+
setExposureLocked,
|
|
39
|
+
setFocusLocked,
|
|
40
|
+
setTorch,
|
|
41
|
+
setZoom,
|
|
42
|
+
useCameraSettings,
|
|
43
|
+
useStandbySession
|
|
44
|
+
} from "./chunk-7Z7TN5N3.js";
|
|
45
|
+
import {
|
|
46
|
+
AUDIO_BITRATE_BPS,
|
|
2
47
|
ConsentRequiredError,
|
|
48
|
+
DEFAULT_UPLOAD_URL,
|
|
3
49
|
ExpoSdkConfigurationError,
|
|
4
50
|
ExpoSdkTimeoutError,
|
|
5
51
|
InvalidResponseError,
|
|
@@ -15,874 +61,26 @@ import {
|
|
|
15
61
|
UnknownSdkError,
|
|
16
62
|
UnsupportedRuntimeError,
|
|
17
63
|
UnsupportedSdkError,
|
|
64
|
+
VIDEO_BITRATE_BPS,
|
|
18
65
|
compareSemver,
|
|
19
66
|
createDefaultStorage,
|
|
67
|
+
createRtmpRoomFactory,
|
|
68
|
+
createRtmpRuntime,
|
|
20
69
|
defaultLivestreamTheme,
|
|
21
70
|
errorFromCode,
|
|
22
71
|
isSdkRemoteConfig,
|
|
23
72
|
isSdkVersionSupported,
|
|
24
73
|
isSessionGoneError,
|
|
25
74
|
normalizeError,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} from "./chunk-
|
|
75
|
+
requiredMbpsFor,
|
|
76
|
+
runUplinkCheck,
|
|
77
|
+
useLivestream,
|
|
78
|
+
verdictFor
|
|
79
|
+
} from "./chunk-XGP2MN5R.js";
|
|
31
80
|
import {
|
|
32
81
|
__require
|
|
33
82
|
} from "./chunk-KFQGP6VL.js";
|
|
34
83
|
|
|
35
|
-
// src/livestream-screen.tsx
|
|
36
|
-
import React, { useEffect, useState } from "react";
|
|
37
|
-
import {
|
|
38
|
-
Pressable,
|
|
39
|
-
ScrollView,
|
|
40
|
-
Text,
|
|
41
|
-
View,
|
|
42
|
-
useWindowDimensions
|
|
43
|
-
} from "react-native";
|
|
44
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
45
|
-
|
|
46
|
-
// ../shared/src/court-detection.ts
|
|
47
|
-
var COURT_DETECTION_MAX_IMAGE_BYTES = 3 * 1024 * 1024;
|
|
48
|
-
var KITCHEN_FAR_Y = 15 / 44;
|
|
49
|
-
var KITCHEN_NEAR_Y = 29 / 44;
|
|
50
|
-
|
|
51
|
-
// ../shared/src/index.ts
|
|
52
|
-
var MAX_LIVE_DURATION_MS = 6 * 60 * 60 * 1e3;
|
|
53
|
-
var EMPTY_ROOM_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
54
|
-
var STALE_SCHEDULED_MS = 48 * 60 * 60 * 1e3;
|
|
55
|
-
var NO_PUBLISHER_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
56
|
-
function sessionShortCode(sessionId) {
|
|
57
|
-
const AMBIGUOUS = /[01ilo]/g;
|
|
58
|
-
const cleaned = sessionId.toLowerCase().replace(AMBIGUOUS, "");
|
|
59
|
-
return cleaned.slice(-6).toUpperCase().padStart(6, "X");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// src/livestream-screen.tsx
|
|
63
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
64
|
-
var copy = {
|
|
65
|
-
vi: {
|
|
66
|
-
states: {
|
|
67
|
-
idle: "S\u1EB5n s\xE0ng",
|
|
68
|
-
preparing: "\u0110ang chu\u1EA9n b\u1ECB\u2026",
|
|
69
|
-
preview: "Xem tr\u01B0\u1EDBc",
|
|
70
|
-
connecting: "\u0110ang k\u1EBFt n\u1ED1i\u2026",
|
|
71
|
-
standby: "S\u1EB4N S\xC0NG PH\xC1T",
|
|
72
|
-
live: "\u0110ANG PH\xC1T",
|
|
73
|
-
reconnecting: "\u0110ang k\u1EBFt n\u1ED1i l\u1EA1i\u2026",
|
|
74
|
-
ending: "\u0110ang k\u1EBFt th\xFAc\u2026",
|
|
75
|
-
ended: "\u0110\xE3 k\u1EBFt th\xFAc",
|
|
76
|
-
error: "\u0110\xE3 x\u1EA3y ra l\u1ED7i"
|
|
77
|
-
},
|
|
78
|
-
consent: "\u0110\u1ED3ng \xFD ghi h\xECnh",
|
|
79
|
-
consentDetail: "T\xF4i \u0111\u1ED3ng \xFD phi\xEAn ph\xE1t n\xE0y \u0111\u01B0\u1EE3c ghi h\xECnh v\xE0 l\u01B0u th\xE0nh video.",
|
|
80
|
-
start: "B\u1EAFt \u0111\u1EA7u k\u1EBFt n\u1ED1i",
|
|
81
|
-
startStandby: "Ch\u1EBF \u0111\u1ED9 ch\u1EDD",
|
|
82
|
-
startLive: "Ph\xE1t ngay",
|
|
83
|
-
confirmStart: "X\xE1c nh\u1EADn b\u1EAFt \u0111\u1EA7u",
|
|
84
|
-
afterThree: "K\u1EBFt n\u1ED1i sau 3 gi\xE2y",
|
|
85
|
-
live: "\u0110ANG PH\xC1T",
|
|
86
|
-
endLabel: "K\u1EBFt th\xFAc ph\xE1t",
|
|
87
|
-
endQuestion: "K\u1EBFt th\xFAc phi\xEAn ph\xE1t?",
|
|
88
|
-
end: "K\u1EBFt th\xFAc",
|
|
89
|
-
cancel: "H\u1EE7y",
|
|
90
|
-
ended: "\u0110\xE3 k\u1EBFt th\xFAc",
|
|
91
|
-
retry: "Th\u1EED l\u1EA1i",
|
|
92
|
-
errorDetail: "Chi ti\u1EBFt k\u1EF9 thu\u1EADt",
|
|
93
|
-
offline: "M\u1EA5t k\u1EBFt n\u1ED1i m\u1EA1ng",
|
|
94
|
-
flip: "\u0110\u1ED5i camera",
|
|
95
|
-
playLabel: "B\u1EAFt \u0111\u1EA7u ph\xE1t",
|
|
96
|
-
pauseLabel: "T\u1EA1m d\u1EEBng ph\xE1t",
|
|
97
|
-
mic: "Micr\xF4",
|
|
98
|
-
orientationLabel: "Khung h\xECnh",
|
|
99
|
-
portrait: "9:16 D\u1ECDc",
|
|
100
|
-
landscape: "16:9 Ngang",
|
|
101
|
-
portraitHint: "C\u1EA7m m\xE1y d\u1ECDc trong su\u1ED1t bu\u1ED5i quay.",
|
|
102
|
-
landscapeHint: "C\u1EA7m m\xE1y ngang trong su\u1ED1t bu\u1ED5i quay.",
|
|
103
|
-
rotatePortrait: "Xoay m\xE1y d\u1ECDc theo khung 9:16 \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u ph\xE1t.",
|
|
104
|
-
rotateLandscape: "Xoay m\xE1y ngang theo khung 16:9 \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u ph\xE1t.",
|
|
105
|
-
lensLabel: "\u1ED0ng k\xEDnh",
|
|
106
|
-
cameraLabel: "Camera",
|
|
107
|
-
cameraFront: "Tr\u01B0\u1EDBc",
|
|
108
|
-
cameraBack: "Sau",
|
|
109
|
-
visibilityLabel: "Ch\u1EBF \u0111\u1ED9",
|
|
110
|
-
visPublic: "C\xF4ng khai",
|
|
111
|
-
visPrivate: "Ri\xEAng t\u01B0",
|
|
112
|
-
backToPreview: "Quay l\u1EA1i m\xE0n h\xECnh ch\u1EDD"
|
|
113
|
-
},
|
|
114
|
-
en: {
|
|
115
|
-
states: {
|
|
116
|
-
idle: "Ready",
|
|
117
|
-
preparing: "Preparing\u2026",
|
|
118
|
-
preview: "Preview",
|
|
119
|
-
connecting: "Connecting\u2026",
|
|
120
|
-
standby: "READY",
|
|
121
|
-
live: "LIVE",
|
|
122
|
-
reconnecting: "Reconnecting\u2026",
|
|
123
|
-
ending: "Ending\u2026",
|
|
124
|
-
ended: "Livestream ended",
|
|
125
|
-
error: "Something went wrong"
|
|
126
|
-
},
|
|
127
|
-
consent: "Recording consent",
|
|
128
|
-
consentDetail: "I consent to recording and saving this livestream as video.",
|
|
129
|
-
start: "Connect",
|
|
130
|
-
startStandby: "Standby Mode",
|
|
131
|
-
startLive: "Go Live Now",
|
|
132
|
-
confirmStart: "Confirm start",
|
|
133
|
-
afterThree: "Connect in 3 seconds",
|
|
134
|
-
live: "LIVE",
|
|
135
|
-
endLabel: "End livestream",
|
|
136
|
-
endQuestion: "End this livestream?",
|
|
137
|
-
end: "End",
|
|
138
|
-
cancel: "Cancel",
|
|
139
|
-
ended: "Livestream ended",
|
|
140
|
-
retry: "Try again",
|
|
141
|
-
errorDetail: "Technical details",
|
|
142
|
-
offline: "Network offline",
|
|
143
|
-
flip: "Flip camera",
|
|
144
|
-
playLabel: "Start broadcasting",
|
|
145
|
-
pauseLabel: "Pause broadcasting",
|
|
146
|
-
mic: "Microphone",
|
|
147
|
-
orientationLabel: "Frame",
|
|
148
|
-
portrait: "9:16 Tall",
|
|
149
|
-
landscape: "16:9 Wide",
|
|
150
|
-
portraitHint: "Hold the phone upright while filming.",
|
|
151
|
-
landscapeHint: "Hold the phone sideways while filming.",
|
|
152
|
-
rotatePortrait: "Rotate the phone upright to match the 9:16 frame before going live.",
|
|
153
|
-
rotateLandscape: "Rotate the phone sideways to match the 16:9 frame before going live.",
|
|
154
|
-
lensLabel: "Lens",
|
|
155
|
-
cameraLabel: "Camera",
|
|
156
|
-
cameraFront: "Front",
|
|
157
|
-
cameraBack: "Back",
|
|
158
|
-
visibilityLabel: "Mode",
|
|
159
|
-
visPublic: "Public",
|
|
160
|
-
visPrivate: "Private",
|
|
161
|
-
backToPreview: "Back to standby"
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
var errorTitle = {
|
|
165
|
-
vi: {
|
|
166
|
-
PERMISSION_DENIED: "C\u1EA7n quy\u1EC1n Camera v\xE0 Micr\xF4",
|
|
167
|
-
NETWORK_UNAVAILABLE: "Kh\xF4ng c\xF3 k\u1EBFt n\u1ED1i m\u1EA1ng",
|
|
168
|
-
TOKEN_EXPIRED: "Phi\xEAn x\xE1c th\u1EF1c \u0111\xE3 h\u1EBFt h\u1EA1n",
|
|
169
|
-
SDK_UPGRADE_REQUIRED: "C\u1EA7n c\u1EADp nh\u1EADt \u1EE9ng d\u1EE5ng",
|
|
170
|
-
UNSUPPORTED_RUNTIME: "M\xF4i tr\u01B0\u1EDDng ch\u1EA1y kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3",
|
|
171
|
-
SESSION_CONFLICT: "\u0110ang c\xF3 phi\xEAn ph\xE1t kh\xE1c",
|
|
172
|
-
RECORDING_FAILED: "Kh\xF4ng th\u1EC3 ghi h\xECnh",
|
|
173
|
-
CONSENT_REQUIRED: "C\u1EA7n \u0111\u1ED3ng \xFD ghi h\xECnh",
|
|
174
|
-
SDK_DISABLED: "T\xEDnh n\u0103ng ph\xE1t tr\u1EF1c ti\u1EBFp \u0111ang t\u1EAFt",
|
|
175
|
-
CONFIGURATION_ERROR: "C\u1EA5u h\xECnh SDK ch\u01B0a \u0111\xFAng",
|
|
176
|
-
INVALID_RESPONSE: "Ph\u1EA3n h\u1ED3i m\xE1y ch\u1EE7 kh\xF4ng h\u1EE3p l\u1EC7",
|
|
177
|
-
TIMEOUT: "Y\xEAu c\u1EA7u qu\xE1 th\u1EDDi gian",
|
|
178
|
-
UNKNOWN: "\u0110\xE3 x\u1EA3y ra l\u1ED7i"
|
|
179
|
-
},
|
|
180
|
-
en: {
|
|
181
|
-
PERMISSION_DENIED: "Camera and Microphone access required",
|
|
182
|
-
NETWORK_UNAVAILABLE: "No network connection",
|
|
183
|
-
TOKEN_EXPIRED: "Authentication expired",
|
|
184
|
-
SDK_UPGRADE_REQUIRED: "App update required",
|
|
185
|
-
UNSUPPORTED_RUNTIME: "Unsupported runtime",
|
|
186
|
-
SESSION_CONFLICT: "Another livestream is active",
|
|
187
|
-
RECORDING_FAILED: "Recording failed",
|
|
188
|
-
CONSENT_REQUIRED: "Recording consent required",
|
|
189
|
-
SDK_DISABLED: "Livestreaming is disabled",
|
|
190
|
-
CONFIGURATION_ERROR: "SDK configuration error",
|
|
191
|
-
INVALID_RESPONSE: "Invalid server response",
|
|
192
|
-
TIMEOUT: "Request timed out",
|
|
193
|
-
UNKNOWN: "Something went wrong"
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
var remediation = {
|
|
197
|
-
vi: {
|
|
198
|
-
PERMISSION_DENIED: "M\u1EDF C\xE0i \u0111\u1EB7t v\xE0 cho ph\xE9p Camera c\xF9ng Micr\xF4, sau \u0111\xF3 th\u1EED l\u1EA1i.",
|
|
199
|
-
NETWORK_UNAVAILABLE: "Ki\u1EC3m tra Wi-Fi ho\u1EB7c d\u1EEF li\u1EC7u di \u0111\u1ED9ng r\u1ED3i th\u1EED l\u1EA1i.",
|
|
200
|
-
TOKEN_EXPIRED: "Phi\xEAn x\xE1c th\u1EF1c \u0111\xE3 h\u1EBFt h\u1EA1n. H\xE3y k\u1EBFt n\u1ED1i m\u1EA1ng v\xE0 th\u1EED l\u1EA1i.",
|
|
201
|
-
SDK_UPGRADE_REQUIRED: "C\u1EADp nh\u1EADt \u1EE9ng d\u1EE5ng l\xEAn b\u1EA3n m\u1EDBi nh\u1EA5t v\xE0 c\xE0i l\u1EA1i Development Build.",
|
|
202
|
-
UNSUPPORTED_RUNTIME: "C\xE0i Development Build tr\xEAn iOS ho\u1EB7c Android; Expo Go v\xE0 web kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3.",
|
|
203
|
-
SESSION_CONFLICT: "K\u1EBFt th\xFAc phi\xEAn ph\xE1t \u0111ang ho\u1EA1t \u0111\u1ED9ng tr\u01B0\u1EDBc khi b\u1EAFt \u0111\u1EA7u phi\xEAn m\u1EDBi.",
|
|
204
|
-
RECORDING_FAILED: "Kh\xF4ng th\u1EC3 t\u1EA1o b\u1EA3n ghi m\xE1y ch\u1EE7. H\xE3y th\u1EED l\u1EA1i ho\u1EB7c li\xEAn h\u1EC7 h\u1ED7 tr\u1EE3.",
|
|
205
|
-
CONSENT_REQUIRED: "\u0110\u1ED3ng \xFD ghi h\xECnh tr\u01B0\u1EDBc khi b\u1EAFt \u0111\u1EA7u ph\xE1t tr\u1EF1c ti\u1EBFp.",
|
|
206
|
-
SDK_DISABLED: "T\xEDnh n\u0103ng \u0111ang b\u1ECB t\u1EAFt cho \u1EE9ng d\u1EE5ng n\xE0y. H\xE3y li\xEAn h\u1EC7 qu\u1EA3n tr\u1ECB vi\xEAn.",
|
|
207
|
-
CONFIGURATION_ERROR: "Ki\u1EC3m tra c\u1EA5u h\xECnh Provider v\xE0 config plugin, sau \u0111\xF3 rebuild \u1EE9ng d\u1EE5ng.",
|
|
208
|
-
INVALID_RESPONSE: "M\xE1y ch\u1EE7 tr\u1EA3 v\u1EC1 d\u1EEF li\u1EC7u kh\xF4ng h\u1EE3p l\u1EC7. H\xE3y th\u1EED l\u1EA1i ho\u1EB7c li\xEAn h\u1EC7 h\u1ED7 tr\u1EE3.",
|
|
209
|
-
TIMEOUT: "Y\xEAu c\u1EA7u qu\xE1 th\u1EDDi gian. Ki\u1EC3m tra k\u1EBFt n\u1ED1i r\u1ED3i th\u1EED l\u1EA1i.",
|
|
210
|
-
UNKNOWN: "Th\u1EED l\u1EA1i. N\u1EBFu l\u1ED7i ti\u1EBFp di\u1EC5n, h\xE3y li\xEAn h\u1EC7 h\u1ED7 tr\u1EE3."
|
|
211
|
-
},
|
|
212
|
-
en: {
|
|
213
|
-
PERMISSION_DENIED: "Open Settings and allow Camera and Microphone, then try again.",
|
|
214
|
-
NETWORK_UNAVAILABLE: "Check Wi-Fi or mobile data, then try again.",
|
|
215
|
-
TOKEN_EXPIRED: "Your authentication has expired. Connect to the network and try again.",
|
|
216
|
-
SDK_UPGRADE_REQUIRED: "Update the app to the latest version and install a new Development Build.",
|
|
217
|
-
UNSUPPORTED_RUNTIME: "Install an iOS or Android Development Build; Expo Go and web are unsupported.",
|
|
218
|
-
SESSION_CONFLICT: "End the active livestream before starting another one.",
|
|
219
|
-
RECORDING_FAILED: "The server recording could not be created. Try again or contact support.",
|
|
220
|
-
CONSENT_REQUIRED: "Accept recording consent before going live.",
|
|
221
|
-
SDK_DISABLED: "Livestreaming is disabled for this app. Contact an administrator.",
|
|
222
|
-
CONFIGURATION_ERROR: "Check the Provider and config plugin setup, then rebuild the app.",
|
|
223
|
-
INVALID_RESPONSE: "The server returned invalid data. Try again or contact support.",
|
|
224
|
-
TIMEOUT: "The request timed out. Check your connection and try again.",
|
|
225
|
-
UNKNOWN: "Try again. If the problem continues, contact support."
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
function LivestreamScreen(props) {
|
|
229
|
-
const state = useLivestream();
|
|
230
|
-
const strings = copy[state.locale];
|
|
231
|
-
const insets = useSafeAreaInsets();
|
|
232
|
-
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
233
|
-
const landscapeUi = windowWidth > windowHeight;
|
|
234
|
-
const panelWidth = Math.min(360, Math.round(windowWidth * 0.42));
|
|
235
|
-
const selectedOrientation = state.captureOrientation ?? "portrait";
|
|
236
|
-
const orientationMismatch = props.physicalOrientation != null && props.physicalOrientation !== selectedOrientation;
|
|
237
|
-
const rotateWarning = selectedOrientation === "landscape" ? strings.rotateLandscape : strings.rotatePortrait;
|
|
238
|
-
const consentStorageKey = `@pickleball/consent:${props.input.consentVersion}`;
|
|
239
|
-
const storedConsent = (() => {
|
|
240
|
-
try {
|
|
241
|
-
return globalThis.localStorage?.getItem(consentStorageKey) === "yes";
|
|
242
|
-
} catch {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
})();
|
|
246
|
-
const [consented, setConsented] = useState(storedConsent);
|
|
247
|
-
const [confirmingEnd, setConfirmingEnd] = useState(false);
|
|
248
|
-
const [visibility, setVisibility] = useState(
|
|
249
|
-
props.input.visibility ?? "public"
|
|
250
|
-
);
|
|
251
|
-
const inLive = state.state === "standby" || state.state === "live" || state.state === "reconnecting";
|
|
252
|
-
const isPublishing = state.state === "live";
|
|
253
|
-
const [liveControlsVisible, setLiveControlsVisible] = useState(true);
|
|
254
|
-
const [controlsTick, setControlsTick] = useState(0);
|
|
255
|
-
const showLiveControls = () => {
|
|
256
|
-
setLiveControlsVisible(true);
|
|
257
|
-
setControlsTick((tick) => tick + 1);
|
|
258
|
-
};
|
|
259
|
-
useEffect(() => {
|
|
260
|
-
if (inLive) setLiveControlsVisible(true);
|
|
261
|
-
}, [inLive]);
|
|
262
|
-
useEffect(() => {
|
|
263
|
-
if (!inLive || !liveControlsVisible || confirmingEnd) return;
|
|
264
|
-
const timer = setTimeout(() => setLiveControlsVisible(false), 1e4);
|
|
265
|
-
return () => clearTimeout(timer);
|
|
266
|
-
}, [inLive, liveControlsVisible, confirmingEnd, controlsTick]);
|
|
267
|
-
useEffect(() => {
|
|
268
|
-
if (state.state === "idle") void state.prepare().catch(() => void 0);
|
|
269
|
-
}, [state.prepare, state.state]);
|
|
270
|
-
const connect = (autoPublish = false) => {
|
|
271
|
-
try {
|
|
272
|
-
globalThis.localStorage?.setItem(consentStorageKey, "yes");
|
|
273
|
-
} catch {
|
|
274
|
-
}
|
|
275
|
-
void state.start({ ...props.input, visibility }, { autoPublish }).catch(() => void 0);
|
|
276
|
-
};
|
|
277
|
-
const autoConnectedRef = React.useRef(false);
|
|
278
|
-
useEffect(() => {
|
|
279
|
-
if (props.autoConnect !== true || autoConnectedRef.current) return;
|
|
280
|
-
if (state.state !== "preview" || !storedConsent) return;
|
|
281
|
-
autoConnectedRef.current = true;
|
|
282
|
-
connect();
|
|
283
|
-
}, [props.autoConnect, state.state, storedConsent]);
|
|
284
|
-
const theme = state.theme;
|
|
285
|
-
const bodyFont = fontOf(theme);
|
|
286
|
-
const boldFont = fontOf(theme, true);
|
|
287
|
-
const displayFont = theme.displayFontFamily ? { fontFamily: theme.displayFontFamily } : boldFont;
|
|
288
|
-
const button = (color) => ({
|
|
289
|
-
minHeight: 40,
|
|
290
|
-
minWidth: 44,
|
|
291
|
-
paddingHorizontal: 16,
|
|
292
|
-
alignItems: "center",
|
|
293
|
-
justifyContent: "center",
|
|
294
|
-
backgroundColor: color,
|
|
295
|
-
borderRadius: 999
|
|
296
|
-
});
|
|
297
|
-
const secondaryButton = {
|
|
298
|
-
...button(theme.elevated),
|
|
299
|
-
borderWidth: 1,
|
|
300
|
-
borderColor: theme.hairline
|
|
301
|
-
};
|
|
302
|
-
const cta = (enabled) => ({
|
|
303
|
-
minHeight: 48,
|
|
304
|
-
alignItems: "center",
|
|
305
|
-
justifyContent: "center",
|
|
306
|
-
borderRadius: 999,
|
|
307
|
-
backgroundColor: enabled ? theme.accent : theme.elevated,
|
|
308
|
-
...enabled ? {
|
|
309
|
-
shadowColor: theme.accent,
|
|
310
|
-
shadowOpacity: 0.45,
|
|
311
|
-
shadowRadius: 14,
|
|
312
|
-
shadowOffset: { width: 0, height: 0 },
|
|
313
|
-
elevation: 8
|
|
314
|
-
} : { borderWidth: 1, borderColor: theme.hairline }
|
|
315
|
-
});
|
|
316
|
-
const fab = (bg) => ({
|
|
317
|
-
width: 48,
|
|
318
|
-
height: 48,
|
|
319
|
-
borderRadius: 24,
|
|
320
|
-
alignItems: "center",
|
|
321
|
-
justifyContent: "center",
|
|
322
|
-
backgroundColor: bg,
|
|
323
|
-
borderWidth: 1,
|
|
324
|
-
borderColor: theme.hairline
|
|
325
|
-
});
|
|
326
|
-
const statusLabel = statusText(state.state, strings);
|
|
327
|
-
return /* @__PURE__ */ jsxs(
|
|
328
|
-
View,
|
|
329
|
-
{
|
|
330
|
-
style: {
|
|
331
|
-
flex: 1,
|
|
332
|
-
flexDirection: landscapeUi ? "row" : "column",
|
|
333
|
-
backgroundColor: theme.background,
|
|
334
|
-
paddingTop: insets.top,
|
|
335
|
-
paddingBottom: insets.bottom,
|
|
336
|
-
paddingLeft: insets.left,
|
|
337
|
-
paddingRight: insets.right,
|
|
338
|
-
...props.style
|
|
339
|
-
},
|
|
340
|
-
children: [
|
|
341
|
-
/* @__PURE__ */ jsx(View, { style: { flex: 1 }, children: props.renderPreview ? props.renderPreview(state) : inLive ? (
|
|
342
|
-
// Live: camera full màn hình; chạm để hiện nút điều khiển.
|
|
343
|
-
/* @__PURE__ */ jsx(Pressable, { style: { flex: 1 }, onPress: showLiveControls, children: /* @__PURE__ */ jsx(DefaultPreview, { state }) })
|
|
344
|
-
) : /* @__PURE__ */ jsx(
|
|
345
|
-
AspectBox,
|
|
346
|
-
{
|
|
347
|
-
ratio: selectedOrientation === "landscape" ? 16 / 9 : 9 / 16,
|
|
348
|
-
background: theme.background,
|
|
349
|
-
children: /* @__PURE__ */ jsx(DefaultPreview, { state })
|
|
350
|
-
}
|
|
351
|
-
) }),
|
|
352
|
-
/* @__PURE__ */ jsx(
|
|
353
|
-
View,
|
|
354
|
-
{
|
|
355
|
-
style: {
|
|
356
|
-
position: "absolute",
|
|
357
|
-
top: insets.top + 12,
|
|
358
|
-
left: 12,
|
|
359
|
-
right: 12,
|
|
360
|
-
gap: 8
|
|
361
|
-
},
|
|
362
|
-
children: props.renderHeader ? props.renderHeader(state) : /* @__PURE__ */ jsxs(
|
|
363
|
-
View,
|
|
364
|
-
{
|
|
365
|
-
style: {
|
|
366
|
-
alignSelf: "flex-start",
|
|
367
|
-
flexDirection: "row",
|
|
368
|
-
alignItems: "center",
|
|
369
|
-
gap: 8,
|
|
370
|
-
paddingVertical: 6,
|
|
371
|
-
paddingHorizontal: 12,
|
|
372
|
-
borderRadius: 999,
|
|
373
|
-
borderWidth: 1,
|
|
374
|
-
borderColor: theme.hairline,
|
|
375
|
-
backgroundColor: theme.overlay,
|
|
376
|
-
...state.state === "live" ? { shadowColor: theme.accent, shadowOpacity: 0.4, shadowRadius: 10, shadowOffset: { width: 0, height: 0 }, elevation: 6 } : {}
|
|
377
|
-
},
|
|
378
|
-
children: [
|
|
379
|
-
state.state === "live" ? /* @__PURE__ */ jsx(View, { style: { width: 8, height: 8, borderRadius: 4, backgroundColor: theme.accent } }) : null,
|
|
380
|
-
/* @__PURE__ */ jsx(
|
|
381
|
-
Text,
|
|
382
|
-
{
|
|
383
|
-
selectable: true,
|
|
384
|
-
style: [
|
|
385
|
-
{
|
|
386
|
-
color: state.state === "live" ? theme.accent : theme.foreground,
|
|
387
|
-
fontSize: 12,
|
|
388
|
-
fontWeight: "700",
|
|
389
|
-
letterSpacing: 0.6
|
|
390
|
-
},
|
|
391
|
-
displayFont
|
|
392
|
-
],
|
|
393
|
-
children: statusLabel
|
|
394
|
-
}
|
|
395
|
-
),
|
|
396
|
-
state.state === "live" ? /* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.textSecondary, fontSize: 12, fontVariant: ["tabular-nums"] }, bodyFont], children: formatElapsed(state.elapsedMs) }) : null,
|
|
397
|
-
state.sessionId ? /* @__PURE__ */ jsx(
|
|
398
|
-
Text,
|
|
399
|
-
{
|
|
400
|
-
selectable: true,
|
|
401
|
-
style: [{ color: theme.textSecondary, fontSize: 12, letterSpacing: 1 }, bodyFont],
|
|
402
|
-
children: sessionShortCode(state.sessionId)
|
|
403
|
-
}
|
|
404
|
-
) : null,
|
|
405
|
-
inLive && state.deviceMetrics ? /* @__PURE__ */ jsx(
|
|
406
|
-
Text,
|
|
407
|
-
{
|
|
408
|
-
selectable: true,
|
|
409
|
-
numberOfLines: 1,
|
|
410
|
-
style: [{ color: theme.textSecondary, fontSize: 12 }, bodyFont],
|
|
411
|
-
children: [
|
|
412
|
-
state.deviceMetrics.batteryPct !== void 0 ? `\u{1F50B}${state.deviceMetrics.batteryPct}%${state.deviceMetrics.charging ? "\u26A1" : ""}` : null,
|
|
413
|
-
state.deviceMetrics.netType ? `\u{1F4F6}${state.deviceMetrics.netType}` : null,
|
|
414
|
-
state.deviceMetrics.connQuality ?? null
|
|
415
|
-
].filter(Boolean).join(" ")
|
|
416
|
-
}
|
|
417
|
-
) : null,
|
|
418
|
-
!state.isOnline ? /* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.danger, fontSize: 12 }, bodyFont], children: strings.offline }) : null
|
|
419
|
-
]
|
|
420
|
-
}
|
|
421
|
-
)
|
|
422
|
-
}
|
|
423
|
-
),
|
|
424
|
-
!inLive || state.error ? /* @__PURE__ */ jsx(View, { style: landscapeUi ? { width: panelWidth } : null, children: /* @__PURE__ */ jsx(
|
|
425
|
-
ScrollView,
|
|
426
|
-
{
|
|
427
|
-
style: landscapeUi ? { flex: 1 } : void 0,
|
|
428
|
-
scrollEnabled: landscapeUi,
|
|
429
|
-
contentContainerStyle: landscapeUi ? { flexGrow: 1, justifyContent: "center" } : void 0,
|
|
430
|
-
children: state.error ? props.renderError ? props.renderError(state) : /* @__PURE__ */ jsxs(View, { style: { padding: 14, gap: 10, backgroundColor: theme.overlay }, children: [
|
|
431
|
-
/* @__PURE__ */ jsx(Text, { selectable: true, accessibilityRole: "alert", style: [{ color: theme.foreground, fontSize: 15, fontWeight: "700" }, displayFont], children: errorTitle[state.locale][state.error.code] }),
|
|
432
|
-
/* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.textSecondary, fontSize: 13 }, bodyFont], children: remediation[state.locale][state.error.code] }),
|
|
433
|
-
/* @__PURE__ */ jsxs(Text, { selectable: true, style: [{ color: theme.muted, fontSize: 11 }, bodyFont], children: [
|
|
434
|
-
strings.errorDetail,
|
|
435
|
-
": ",
|
|
436
|
-
state.error.message
|
|
437
|
-
] }),
|
|
438
|
-
/* @__PURE__ */ jsx(Pressable, { accessibilityRole: "button", onPress: () => void state.retry(), style: cta(true), children: /* @__PURE__ */ jsx(Text, { style: [{ color: theme.ink, fontSize: 14, fontWeight: "800" }, boldFont], children: strings.retry }) })
|
|
439
|
-
] }) : props.renderControls ? props.renderControls(state) : /* @__PURE__ */ jsxs(View, { style: { padding: 14, gap: 10, backgroundColor: theme.overlay }, children: [
|
|
440
|
-
state.state === "preview" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
441
|
-
/* @__PURE__ */ jsx(ControlRow, { label: strings.orientationLabel, theme, children: /* @__PURE__ */ jsx(
|
|
442
|
-
Segmented,
|
|
443
|
-
{
|
|
444
|
-
theme,
|
|
445
|
-
value: selectedOrientation,
|
|
446
|
-
onSelect: (value) => void state.setCaptureOrientation(value),
|
|
447
|
-
options: [
|
|
448
|
-
{ value: "portrait", label: strings.portrait },
|
|
449
|
-
{ value: "landscape", label: strings.landscape }
|
|
450
|
-
]
|
|
451
|
-
}
|
|
452
|
-
) }),
|
|
453
|
-
/* @__PURE__ */ jsx(ControlRow, { label: strings.visibilityLabel, theme, children: /* @__PURE__ */ jsx(
|
|
454
|
-
Segmented,
|
|
455
|
-
{
|
|
456
|
-
theme,
|
|
457
|
-
value: visibility,
|
|
458
|
-
onSelect: (value) => setVisibility(value),
|
|
459
|
-
options: [
|
|
460
|
-
{ value: "public", label: strings.visPublic },
|
|
461
|
-
{ value: "private", label: strings.visPrivate }
|
|
462
|
-
]
|
|
463
|
-
}
|
|
464
|
-
) }),
|
|
465
|
-
/* @__PURE__ */ jsx(ControlRow, { label: strings.cameraLabel, theme, children: /* @__PURE__ */ jsx(
|
|
466
|
-
Segmented,
|
|
467
|
-
{
|
|
468
|
-
theme,
|
|
469
|
-
value: state.isCameraFront ? "front" : "back",
|
|
470
|
-
onSelect: (value) => {
|
|
471
|
-
if (value === "front" !== state.isCameraFront) {
|
|
472
|
-
void state.switchCamera();
|
|
473
|
-
}
|
|
474
|
-
},
|
|
475
|
-
options: [
|
|
476
|
-
{ value: "back", label: strings.cameraBack },
|
|
477
|
-
{ value: "front", label: strings.cameraFront }
|
|
478
|
-
]
|
|
479
|
-
}
|
|
480
|
-
) }),
|
|
481
|
-
(() => {
|
|
482
|
-
const backLenses = state.cameras.filter(
|
|
483
|
-
(camera) => camera.facing === "environment" && !/dual|triple/i.test(camera.label)
|
|
484
|
-
);
|
|
485
|
-
if (backLenses.length < 2 || state.isCameraFront) return null;
|
|
486
|
-
const lensName = (label) => /ultra\s*wide/i.test(label) ? "0.5x" : /telephoto/i.test(label) ? "Tele" : "1x";
|
|
487
|
-
return /* @__PURE__ */ jsx(ControlRow, { label: strings.lensLabel, theme, children: /* @__PURE__ */ jsx(
|
|
488
|
-
Segmented,
|
|
489
|
-
{
|
|
490
|
-
theme,
|
|
491
|
-
value: state.selectedCameraId,
|
|
492
|
-
onSelect: (deviceId) => void state.selectCamera(deviceId),
|
|
493
|
-
options: backLenses.map((camera) => ({
|
|
494
|
-
value: camera.deviceId,
|
|
495
|
-
label: lensName(camera.label),
|
|
496
|
-
accessibilityLabel: camera.label
|
|
497
|
-
}))
|
|
498
|
-
}
|
|
499
|
-
) });
|
|
500
|
-
})(),
|
|
501
|
-
/* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.muted, fontSize: 11 }, bodyFont], children: selectedOrientation === "landscape" ? strings.landscapeHint : strings.portraitHint }),
|
|
502
|
-
/* @__PURE__ */ jsxs(
|
|
503
|
-
Pressable,
|
|
504
|
-
{
|
|
505
|
-
accessibilityRole: "checkbox",
|
|
506
|
-
accessibilityLabel: strings.consent,
|
|
507
|
-
accessibilityState: { checked: consented },
|
|
508
|
-
onPress: () => setConsented((value) => !value),
|
|
509
|
-
style: { minHeight: 40, flexDirection: "row", alignItems: "center", gap: 10 },
|
|
510
|
-
children: [
|
|
511
|
-
/* @__PURE__ */ jsx(
|
|
512
|
-
View,
|
|
513
|
-
{
|
|
514
|
-
style: {
|
|
515
|
-
width: 22,
|
|
516
|
-
height: 22,
|
|
517
|
-
borderRadius: theme.radiusSm,
|
|
518
|
-
borderWidth: 1.5,
|
|
519
|
-
borderColor: consented ? theme.accent : theme.hairline,
|
|
520
|
-
backgroundColor: consented ? theme.accent : theme.surface,
|
|
521
|
-
alignItems: "center",
|
|
522
|
-
justifyContent: "center"
|
|
523
|
-
},
|
|
524
|
-
children: consented ? /* @__PURE__ */ jsx(Text, { style: { color: theme.ink, fontSize: 13, fontWeight: "800" }, children: "\u2713" }) : null
|
|
525
|
-
}
|
|
526
|
-
),
|
|
527
|
-
/* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.textSecondary, fontSize: 13, flex: 1 }, bodyFont], children: strings.consentDetail })
|
|
528
|
-
]
|
|
529
|
-
}
|
|
530
|
-
),
|
|
531
|
-
/* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
|
|
532
|
-
/* @__PURE__ */ jsx(
|
|
533
|
-
Pressable,
|
|
534
|
-
{
|
|
535
|
-
accessibilityRole: "button",
|
|
536
|
-
accessibilityLabel: strings.startStandby,
|
|
537
|
-
accessibilityState: { disabled: !consented },
|
|
538
|
-
disabled: !consented,
|
|
539
|
-
onPress: () => connect(false),
|
|
540
|
-
style: [
|
|
541
|
-
secondaryButton,
|
|
542
|
-
{
|
|
543
|
-
flex: 1,
|
|
544
|
-
minHeight: 48,
|
|
545
|
-
backgroundColor: consented ? theme.elevated : theme.surface
|
|
546
|
-
}
|
|
547
|
-
],
|
|
548
|
-
children: /* @__PURE__ */ jsx(
|
|
549
|
-
Text,
|
|
550
|
-
{
|
|
551
|
-
style: [
|
|
552
|
-
{
|
|
553
|
-
color: consented ? theme.foreground : theme.muted,
|
|
554
|
-
fontSize: 14,
|
|
555
|
-
fontWeight: "800"
|
|
556
|
-
},
|
|
557
|
-
boldFont
|
|
558
|
-
],
|
|
559
|
-
children: `\u{1F7E1} ${strings.startStandby}`
|
|
560
|
-
}
|
|
561
|
-
)
|
|
562
|
-
}
|
|
563
|
-
),
|
|
564
|
-
/* @__PURE__ */ jsx(
|
|
565
|
-
Pressable,
|
|
566
|
-
{
|
|
567
|
-
accessibilityRole: "button",
|
|
568
|
-
accessibilityLabel: strings.startLive,
|
|
569
|
-
accessibilityState: { disabled: !consented },
|
|
570
|
-
disabled: !consented,
|
|
571
|
-
onPress: () => connect(true),
|
|
572
|
-
style: [cta(consented), { flex: 1 }],
|
|
573
|
-
children: /* @__PURE__ */ jsx(
|
|
574
|
-
Text,
|
|
575
|
-
{
|
|
576
|
-
style: [
|
|
577
|
-
{
|
|
578
|
-
color: consented ? theme.ink : theme.muted,
|
|
579
|
-
fontSize: 14,
|
|
580
|
-
fontWeight: "800"
|
|
581
|
-
},
|
|
582
|
-
boldFont
|
|
583
|
-
],
|
|
584
|
-
children: `\u{1F7E2} ${strings.startLive}`
|
|
585
|
-
}
|
|
586
|
-
)
|
|
587
|
-
}
|
|
588
|
-
)
|
|
589
|
-
] })
|
|
590
|
-
] }) : null,
|
|
591
|
-
state.state === "ended" ? /* @__PURE__ */ jsxs(View, { style: { gap: 10 }, children: [
|
|
592
|
-
/* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.foreground, textAlign: "center", fontSize: 15, fontWeight: "700" }, displayFont], children: strings.ended }),
|
|
593
|
-
/* @__PURE__ */ jsx(
|
|
594
|
-
Pressable,
|
|
595
|
-
{
|
|
596
|
-
accessibilityRole: "button",
|
|
597
|
-
onPress: () => {
|
|
598
|
-
setConfirmingEnd(false);
|
|
599
|
-
props.onEnded?.();
|
|
600
|
-
void state.prepare();
|
|
601
|
-
},
|
|
602
|
-
style: cta(true),
|
|
603
|
-
children: /* @__PURE__ */ jsx(Text, { style: [{ color: theme.ink, fontSize: 14, fontWeight: "800" }, boldFont], children: strings.backToPreview })
|
|
604
|
-
}
|
|
605
|
-
)
|
|
606
|
-
] }) : null
|
|
607
|
-
] })
|
|
608
|
-
}
|
|
609
|
-
) }) : null,
|
|
610
|
-
inLive && liveControlsVisible && !confirmingEnd ? /* @__PURE__ */ jsxs(
|
|
611
|
-
View,
|
|
612
|
-
{
|
|
613
|
-
style: {
|
|
614
|
-
position: "absolute",
|
|
615
|
-
right: 16,
|
|
616
|
-
bottom: insets.bottom + 24,
|
|
617
|
-
gap: 12,
|
|
618
|
-
alignItems: "center"
|
|
619
|
-
},
|
|
620
|
-
children: [
|
|
621
|
-
state.state === "standby" && orientationMismatch ? /* @__PURE__ */ jsx(
|
|
622
|
-
Text,
|
|
623
|
-
{
|
|
624
|
-
style: [
|
|
625
|
-
{
|
|
626
|
-
maxWidth: 200,
|
|
627
|
-
textAlign: "center",
|
|
628
|
-
color: theme.danger,
|
|
629
|
-
fontSize: 11,
|
|
630
|
-
fontWeight: "700"
|
|
631
|
-
},
|
|
632
|
-
boldFont
|
|
633
|
-
],
|
|
634
|
-
children: rotateWarning
|
|
635
|
-
}
|
|
636
|
-
) : null,
|
|
637
|
-
/* @__PURE__ */ jsx(
|
|
638
|
-
Pressable,
|
|
639
|
-
{
|
|
640
|
-
accessibilityRole: "button",
|
|
641
|
-
accessibilityLabel: isPublishing ? strings.pauseLabel : strings.playLabel,
|
|
642
|
-
accessibilityState: {
|
|
643
|
-
disabled: state.state === "standby" && orientationMismatch
|
|
644
|
-
},
|
|
645
|
-
disabled: state.state === "standby" && orientationMismatch,
|
|
646
|
-
onPress: () => {
|
|
647
|
-
showLiveControls();
|
|
648
|
-
if (isPublishing) void state.stopPublishing();
|
|
649
|
-
else void state.startPublishing();
|
|
650
|
-
},
|
|
651
|
-
style: [
|
|
652
|
-
fab(
|
|
653
|
-
state.state === "standby" && orientationMismatch ? theme.elevated : theme.accent
|
|
654
|
-
),
|
|
655
|
-
{ borderColor: theme.accent }
|
|
656
|
-
],
|
|
657
|
-
children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 18, color: theme.ink, fontWeight: "800" }, children: isPublishing ? "\u23F8" : "\u25B6" })
|
|
658
|
-
}
|
|
659
|
-
),
|
|
660
|
-
/* @__PURE__ */ jsx(
|
|
661
|
-
Pressable,
|
|
662
|
-
{
|
|
663
|
-
accessibilityRole: "button",
|
|
664
|
-
accessibilityLabel: strings.flip,
|
|
665
|
-
onPress: () => {
|
|
666
|
-
showLiveControls();
|
|
667
|
-
void state.switchCamera();
|
|
668
|
-
},
|
|
669
|
-
style: fab(theme.overlay),
|
|
670
|
-
children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 20 }, children: "\u{1F504}" })
|
|
671
|
-
}
|
|
672
|
-
),
|
|
673
|
-
/* @__PURE__ */ jsx(
|
|
674
|
-
Pressable,
|
|
675
|
-
{
|
|
676
|
-
accessibilityRole: "switch",
|
|
677
|
-
accessibilityLabel: strings.mic,
|
|
678
|
-
accessibilityState: { checked: state.isMicrophoneEnabled },
|
|
679
|
-
onPress: () => {
|
|
680
|
-
showLiveControls();
|
|
681
|
-
void state.setMicrophoneEnabled(!state.isMicrophoneEnabled);
|
|
682
|
-
},
|
|
683
|
-
style: [
|
|
684
|
-
fab(theme.overlay),
|
|
685
|
-
state.isMicrophoneEnabled ? null : { borderColor: theme.danger }
|
|
686
|
-
],
|
|
687
|
-
children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 20 }, children: state.isMicrophoneEnabled ? "\u{1F399}\uFE0F" : "\u{1F507}" })
|
|
688
|
-
}
|
|
689
|
-
),
|
|
690
|
-
/* @__PURE__ */ jsx(
|
|
691
|
-
Pressable,
|
|
692
|
-
{
|
|
693
|
-
accessibilityRole: "button",
|
|
694
|
-
accessibilityLabel: strings.endLabel,
|
|
695
|
-
onPress: () => setConfirmingEnd(true),
|
|
696
|
-
style: [fab(theme.danger), { borderColor: theme.danger }],
|
|
697
|
-
children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 18, color: theme.ink, fontWeight: "800" }, children: "\u25A0" })
|
|
698
|
-
}
|
|
699
|
-
)
|
|
700
|
-
]
|
|
701
|
-
}
|
|
702
|
-
) : null,
|
|
703
|
-
confirmingEnd ? /* @__PURE__ */ jsx(
|
|
704
|
-
View,
|
|
705
|
-
{
|
|
706
|
-
style: {
|
|
707
|
-
position: "absolute",
|
|
708
|
-
inset: 0,
|
|
709
|
-
alignItems: "center",
|
|
710
|
-
justifyContent: "center",
|
|
711
|
-
backgroundColor: theme.overlay
|
|
712
|
-
},
|
|
713
|
-
children: /* @__PURE__ */ jsxs(
|
|
714
|
-
View,
|
|
715
|
-
{
|
|
716
|
-
style: {
|
|
717
|
-
width: 280,
|
|
718
|
-
gap: 10,
|
|
719
|
-
padding: 16,
|
|
720
|
-
borderRadius: theme.radius,
|
|
721
|
-
borderWidth: 1,
|
|
722
|
-
borderColor: theme.hairline,
|
|
723
|
-
backgroundColor: theme.surface
|
|
724
|
-
},
|
|
725
|
-
children: [
|
|
726
|
-
/* @__PURE__ */ jsx(Text, { selectable: true, style: [{ color: theme.foreground, fontSize: 15, fontWeight: "700" }, displayFont], children: strings.endQuestion }),
|
|
727
|
-
/* @__PURE__ */ jsx(
|
|
728
|
-
Pressable,
|
|
729
|
-
{
|
|
730
|
-
accessibilityRole: "button",
|
|
731
|
-
onPress: () => {
|
|
732
|
-
setConfirmingEnd(false);
|
|
733
|
-
void state.stop("user");
|
|
734
|
-
},
|
|
735
|
-
style: button(theme.danger),
|
|
736
|
-
children: /* @__PURE__ */ jsx(Text, { style: [{ color: theme.ink, fontSize: 14, fontWeight: "800" }, boldFont], children: strings.end })
|
|
737
|
-
}
|
|
738
|
-
),
|
|
739
|
-
/* @__PURE__ */ jsx(Pressable, { accessibilityRole: "button", onPress: () => setConfirmingEnd(false), style: secondaryButton, children: /* @__PURE__ */ jsx(Text, { style: [{ color: theme.textSecondary, fontSize: 13, fontWeight: "600" }, bodyFont], children: strings.cancel }) })
|
|
740
|
-
]
|
|
741
|
-
}
|
|
742
|
-
)
|
|
743
|
-
}
|
|
744
|
-
) : null
|
|
745
|
-
]
|
|
746
|
-
}
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
var fontOf = (theme, bold = false) => bold ? { fontFamily: theme.fontFamilyBold ?? theme.fontFamily } : { fontFamily: theme.fontFamily };
|
|
750
|
-
function Segmented({
|
|
751
|
-
options,
|
|
752
|
-
value,
|
|
753
|
-
onSelect,
|
|
754
|
-
theme
|
|
755
|
-
}) {
|
|
756
|
-
return /* @__PURE__ */ jsx(
|
|
757
|
-
View,
|
|
758
|
-
{
|
|
759
|
-
style: {
|
|
760
|
-
flex: 1,
|
|
761
|
-
flexDirection: "row",
|
|
762
|
-
padding: 3,
|
|
763
|
-
gap: 3,
|
|
764
|
-
borderRadius: 999,
|
|
765
|
-
borderWidth: 1,
|
|
766
|
-
borderColor: theme.hairline,
|
|
767
|
-
backgroundColor: theme.surface
|
|
768
|
-
},
|
|
769
|
-
children: options.map((option) => {
|
|
770
|
-
const selected = option.value === value;
|
|
771
|
-
return /* @__PURE__ */ jsx(
|
|
772
|
-
Pressable,
|
|
773
|
-
{
|
|
774
|
-
accessibilityRole: "button",
|
|
775
|
-
accessibilityLabel: option.accessibilityLabel ?? option.label,
|
|
776
|
-
accessibilityState: { selected },
|
|
777
|
-
onPress: () => onSelect(option.value),
|
|
778
|
-
style: {
|
|
779
|
-
flex: 1,
|
|
780
|
-
minHeight: 32,
|
|
781
|
-
alignItems: "center",
|
|
782
|
-
justifyContent: "center",
|
|
783
|
-
borderRadius: 999,
|
|
784
|
-
backgroundColor: selected ? theme.accent : "transparent"
|
|
785
|
-
},
|
|
786
|
-
children: /* @__PURE__ */ jsx(
|
|
787
|
-
Text,
|
|
788
|
-
{
|
|
789
|
-
numberOfLines: 1,
|
|
790
|
-
style: [
|
|
791
|
-
{
|
|
792
|
-
fontSize: 12,
|
|
793
|
-
fontWeight: "700",
|
|
794
|
-
color: selected ? theme.ink : theme.textSecondary
|
|
795
|
-
},
|
|
796
|
-
fontOf(theme, true)
|
|
797
|
-
],
|
|
798
|
-
children: option.label
|
|
799
|
-
}
|
|
800
|
-
)
|
|
801
|
-
},
|
|
802
|
-
option.value
|
|
803
|
-
);
|
|
804
|
-
})
|
|
805
|
-
}
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
function ControlRow({
|
|
809
|
-
label,
|
|
810
|
-
theme,
|
|
811
|
-
children
|
|
812
|
-
}) {
|
|
813
|
-
return /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", alignItems: "center", gap: 10 }, children: [
|
|
814
|
-
/* @__PURE__ */ jsx(
|
|
815
|
-
Text,
|
|
816
|
-
{
|
|
817
|
-
numberOfLines: 1,
|
|
818
|
-
style: [
|
|
819
|
-
{
|
|
820
|
-
width: 78,
|
|
821
|
-
fontSize: 10,
|
|
822
|
-
letterSpacing: 1,
|
|
823
|
-
textTransform: "uppercase",
|
|
824
|
-
fontWeight: "700",
|
|
825
|
-
color: theme.muted
|
|
826
|
-
},
|
|
827
|
-
fontOf(theme, true)
|
|
828
|
-
],
|
|
829
|
-
children: label
|
|
830
|
-
}
|
|
831
|
-
),
|
|
832
|
-
children
|
|
833
|
-
] });
|
|
834
|
-
}
|
|
835
|
-
function AspectBox({
|
|
836
|
-
ratio,
|
|
837
|
-
background,
|
|
838
|
-
children
|
|
839
|
-
}) {
|
|
840
|
-
const [size, setSize] = useState(null);
|
|
841
|
-
return /* @__PURE__ */ jsx(
|
|
842
|
-
View,
|
|
843
|
-
{
|
|
844
|
-
style: { flex: 1, alignItems: "center", justifyContent: "center", backgroundColor: background },
|
|
845
|
-
onLayout: (event) => {
|
|
846
|
-
const { width, height } = event.nativeEvent.layout;
|
|
847
|
-
setSize({ w: width, h: height });
|
|
848
|
-
},
|
|
849
|
-
children: size ? /* @__PURE__ */ jsx(
|
|
850
|
-
View,
|
|
851
|
-
{
|
|
852
|
-
style: {
|
|
853
|
-
width: Math.min(size.w, size.h * ratio),
|
|
854
|
-
height: Math.min(size.h, size.w / ratio),
|
|
855
|
-
overflow: "hidden"
|
|
856
|
-
},
|
|
857
|
-
children
|
|
858
|
-
}
|
|
859
|
-
) : null
|
|
860
|
-
}
|
|
861
|
-
);
|
|
862
|
-
}
|
|
863
|
-
function DefaultPreview({ state }) {
|
|
864
|
-
const preview = state.previewTrack;
|
|
865
|
-
if (!preview) return /* @__PURE__ */ jsx(View, { style: { flex: 1, backgroundColor: state.theme.background } });
|
|
866
|
-
if (preview.kind === "rtmp") {
|
|
867
|
-
const rtmp = __require("@pickleball/rtmp-native");
|
|
868
|
-
return /* @__PURE__ */ jsx(rtmp.PickleballRtmpPreviewView, { style: { flex: 1 } });
|
|
869
|
-
}
|
|
870
|
-
const liveKit = __require("@livekit/react-native");
|
|
871
|
-
const liveKitPreview = preview;
|
|
872
|
-
const remountKey = `${state.selectedCameraId ?? "auto"}:${state.isCameraFront ? "front" : "back"}`;
|
|
873
|
-
return liveKitPreview.trackRef ? /* @__PURE__ */ jsx(liveKit.VideoTrack, { trackRef: liveKitPreview.trackRef, mirror: state.isCameraFront, objectFit: "cover", style: { flex: 1 } }, remountKey) : /* @__PURE__ */ jsx(liveKit.VideoView, { videoTrack: liveKitPreview.videoTrack, mirror: state.isCameraFront, objectFit: "cover", style: { flex: 1 } }, remountKey);
|
|
874
|
-
}
|
|
875
|
-
function formatElapsed(milliseconds) {
|
|
876
|
-
const total = Math.floor(milliseconds / 1e3);
|
|
877
|
-
const hours = Math.floor(total / 3600);
|
|
878
|
-
const minutes = Math.floor(total % 3600 / 60);
|
|
879
|
-
const seconds = total % 60;
|
|
880
|
-
return [hours, minutes, seconds].map((value) => String(value).padStart(2, "0")).join(":");
|
|
881
|
-
}
|
|
882
|
-
function statusText(state, strings) {
|
|
883
|
-
return strings.states[state];
|
|
884
|
-
}
|
|
885
|
-
|
|
886
84
|
// src/http-session-provider.ts
|
|
887
85
|
var defaultPaths = {
|
|
888
86
|
bootstrap: "/bootstrap",
|
|
@@ -894,44 +92,11 @@ var defaultPaths = {
|
|
|
894
92
|
};
|
|
895
93
|
function createHttpSessionProvider(options) {
|
|
896
94
|
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
897
|
-
const
|
|
898
|
-
if (typeof fetchImplementation !== "function") {
|
|
899
|
-
throw new ExpoSdkConfigurationError("A Fetch API implementation is required");
|
|
900
|
-
}
|
|
95
|
+
const postJson = createJsonPoster({ fetch: options.fetch, timeoutMs: options.timeoutMs });
|
|
901
96
|
const paths = { ...defaultPaths, ...options.paths };
|
|
902
97
|
async function post(path, body) {
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
() => controller.abort(),
|
|
906
|
-
options.timeoutMs ?? 1e4
|
|
907
|
-
);
|
|
908
|
-
try {
|
|
909
|
-
const extraHeaders = typeof options.headers === "function" ? await options.headers() : options.headers ?? {};
|
|
910
|
-
const response = await fetchImplementation(joinUrl(baseUrl, path), {
|
|
911
|
-
method: "POST",
|
|
912
|
-
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
913
|
-
body: JSON.stringify(body),
|
|
914
|
-
signal: controller.signal,
|
|
915
|
-
redirect: "error"
|
|
916
|
-
});
|
|
917
|
-
const envelope = await response.json().catch(() => {
|
|
918
|
-
throw new InvalidResponseError();
|
|
919
|
-
});
|
|
920
|
-
if (!response.ok) throw parseProxyError(envelope, response.status);
|
|
921
|
-
if (!isRecord(envelope) || !("data" in envelope)) {
|
|
922
|
-
throw new InvalidResponseError();
|
|
923
|
-
}
|
|
924
|
-
return envelope.data;
|
|
925
|
-
} catch (error) {
|
|
926
|
-
if (controller.signal.aborted) throw new ExpoSdkTimeoutError();
|
|
927
|
-
if (error instanceof PickleballExpoError) throw error;
|
|
928
|
-
if (error instanceof TypeError) {
|
|
929
|
-
throw new NetworkUnavailableError(void 0, { cause: error });
|
|
930
|
-
}
|
|
931
|
-
throw error;
|
|
932
|
-
} finally {
|
|
933
|
-
clearTimeout(timeout);
|
|
934
|
-
}
|
|
98
|
+
const extraHeaders = typeof options.headers === "function" ? await options.headers() : options.headers ?? {};
|
|
99
|
+
return postJson(joinUrl(baseUrl, path), body, extraHeaders);
|
|
935
100
|
}
|
|
936
101
|
return {
|
|
937
102
|
async bootstrap(input) {
|
|
@@ -963,76 +128,362 @@ function createHttpSessionProvider(options) {
|
|
|
963
128
|
}
|
|
964
129
|
};
|
|
965
130
|
}
|
|
966
|
-
|
|
967
|
-
|
|
131
|
+
|
|
132
|
+
// src/camera-grant-provider.ts
|
|
133
|
+
var CAMERA_GRANT_STORAGE_KEY = "@pickleball/expo-sdk/camera-grant";
|
|
134
|
+
var DEFAULT_LOCAL_CONFIG = {
|
|
135
|
+
enabled: true,
|
|
136
|
+
protocolVersion: 1,
|
|
137
|
+
minSdkVersion: "0.0.0",
|
|
138
|
+
recommendedSdkVersion: PICKLEBALL_EXPO_SDK_VERSION,
|
|
139
|
+
videoQuality: 720,
|
|
140
|
+
reconnectTimeoutMs: 3e4,
|
|
141
|
+
backgroundGraceMs: 9e4,
|
|
142
|
+
telemetryIntervalMs: 6e4,
|
|
143
|
+
maxSessionDurationMs: 6 * 60 * 6e4
|
|
144
|
+
};
|
|
145
|
+
function parseHandle(raw) {
|
|
146
|
+
if (!raw) return null;
|
|
968
147
|
try {
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const localDevelopment = url.protocol === "http:" && loopbackHosts.has(url.hostname);
|
|
973
|
-
const devPrivateLan = typeof __DEV__ !== "undefined" && __DEV__ === true && url.protocol === "http:" && PRIVATE_LAN_HOST.test(url.hostname);
|
|
974
|
-
if (!secure && !localDevelopment && !devPrivateLan || url.username || url.password) {
|
|
975
|
-
throw new Error();
|
|
148
|
+
const value = JSON.parse(raw);
|
|
149
|
+
if (!isRecord(value) || typeof value.sessionId !== "string" || typeof value.base !== "string" || typeof value.token !== "string" || typeof value.tokenExpiresAt !== "number") {
|
|
150
|
+
return null;
|
|
976
151
|
}
|
|
977
|
-
return
|
|
152
|
+
return {
|
|
153
|
+
sessionId: value.sessionId,
|
|
154
|
+
base: value.base,
|
|
155
|
+
token: value.token,
|
|
156
|
+
tokenExpiresAt: value.tokenExpiresAt,
|
|
157
|
+
...value.quality === 720 || value.quality === 1080 ? { quality: value.quality } : {},
|
|
158
|
+
savedAt: typeof value.savedAt === "number" ? value.savedAt : 0
|
|
159
|
+
};
|
|
978
160
|
} catch {
|
|
979
|
-
|
|
980
|
-
"baseUrl must use HTTPS (HTTP is allowed only for localhost loopback)"
|
|
981
|
-
);
|
|
161
|
+
return null;
|
|
982
162
|
}
|
|
983
163
|
}
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
envelope.error.code,
|
|
990
|
-
typeof envelope.error.message === "string" ? envelope.error.message : void 0
|
|
991
|
-
) : new InvalidResponseError();
|
|
992
|
-
return Object.assign(error, { httpStatus });
|
|
993
|
-
}
|
|
994
|
-
function isRecord(value) {
|
|
995
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
164
|
+
function withStatus(error) {
|
|
165
|
+
if (httpStatusOf(error) === 401) {
|
|
166
|
+
return Object.assign(new TokenExpiredError(), { httpStatus: 401 });
|
|
167
|
+
}
|
|
168
|
+
return error;
|
|
996
169
|
}
|
|
997
|
-
function
|
|
998
|
-
|
|
170
|
+
function createCameraGrantProvider(options) {
|
|
171
|
+
const storage = options.storage ?? createDefaultStorage();
|
|
172
|
+
const storageKey = options.storageKey ?? CAMERA_GRANT_STORAGE_KEY;
|
|
173
|
+
const post = createJsonPoster({ fetch: options.fetch, timeoutMs: options.timeoutMs });
|
|
174
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
175
|
+
let handle = null;
|
|
176
|
+
let handleLoaded = null;
|
|
177
|
+
let activeGrant = null;
|
|
178
|
+
let pendingResume = null;
|
|
179
|
+
const emit = () => {
|
|
180
|
+
for (const listener of listeners) listener();
|
|
181
|
+
};
|
|
182
|
+
async function loadHandle() {
|
|
183
|
+
if (!handleLoaded) {
|
|
184
|
+
handleLoaded = storage.getItem(storageKey).then((raw) => {
|
|
185
|
+
if (handle === null) handle = parseHandle(raw);
|
|
186
|
+
}).catch(() => void 0);
|
|
187
|
+
}
|
|
188
|
+
await handleLoaded;
|
|
189
|
+
}
|
|
190
|
+
async function saveHandle(next) {
|
|
191
|
+
handle = next;
|
|
192
|
+
try {
|
|
193
|
+
if (next) await storage.setItem(storageKey, JSON.stringify(next));
|
|
194
|
+
else await storage.removeItem(storageKey);
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function setActive(grant) {
|
|
199
|
+
activeGrant = grant;
|
|
200
|
+
emit();
|
|
201
|
+
}
|
|
202
|
+
async function adopt(grant, quality) {
|
|
203
|
+
const base = sessionApiBaseFromTelemetryEndpoint(grant.telemetry.endpoint);
|
|
204
|
+
await saveHandle({
|
|
205
|
+
sessionId: grant.sessionId,
|
|
206
|
+
base,
|
|
207
|
+
token: grant.telemetry.token,
|
|
208
|
+
tokenExpiresAt: grant.telemetry.expiresAt,
|
|
209
|
+
...quality !== void 0 ? { quality } : handle?.quality !== void 0 ? { quality: handle.quality } : {},
|
|
210
|
+
savedAt: Date.now()
|
|
211
|
+
});
|
|
212
|
+
setActive(grant);
|
|
213
|
+
}
|
|
214
|
+
function requireHandle(sessionId) {
|
|
215
|
+
if (!handle || handle.sessionId !== sessionId) {
|
|
216
|
+
throw Object.assign(
|
|
217
|
+
new SessionConflictError("No camera grant is held for this session"),
|
|
218
|
+
{ httpStatus: 404 }
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return handle;
|
|
222
|
+
}
|
|
223
|
+
function bearer(current) {
|
|
224
|
+
return { Authorization: `Bearer ${current.token}` };
|
|
225
|
+
}
|
|
226
|
+
async function call(current, action, body) {
|
|
227
|
+
try {
|
|
228
|
+
return await post(`${current.base}/${action}`, body, bearer(current));
|
|
229
|
+
} catch (error) {
|
|
230
|
+
throw withStatus(error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const provider = {
|
|
234
|
+
async bootstrap() {
|
|
235
|
+
await loadHandle();
|
|
236
|
+
return {
|
|
237
|
+
config: { ...DEFAULT_LOCAL_CONFIG, ...options.localConfig },
|
|
238
|
+
serverTime: Date.now(),
|
|
239
|
+
rolloutEnabled: true
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
async start(input) {
|
|
243
|
+
await loadHandle();
|
|
244
|
+
const resumeGrant = pendingResume;
|
|
245
|
+
if (resumeGrant && resumeGrantMatches(input.externalSessionId, resumeGrant.sessionId)) {
|
|
246
|
+
pendingResume = null;
|
|
247
|
+
return resumeGrant;
|
|
248
|
+
}
|
|
249
|
+
const raw = await options.requestGrant(input);
|
|
250
|
+
if (!isCameraGrant(raw)) {
|
|
251
|
+
throw new InvalidResponseError("The partner backend returned an invalid camera grant");
|
|
252
|
+
}
|
|
253
|
+
await adopt(raw, input.quality);
|
|
254
|
+
if (input.standby !== true && raw.status !== "live") {
|
|
255
|
+
const current = requireHandle(raw.sessionId);
|
|
256
|
+
try {
|
|
257
|
+
const data = await call(current, "publish", {
|
|
258
|
+
sessionId: raw.sessionId,
|
|
259
|
+
...input.quality !== void 0 ? { quality: input.quality } : {}
|
|
260
|
+
});
|
|
261
|
+
if (!isSessionStatus(data)) throw new InvalidResponseError();
|
|
262
|
+
} catch (error) {
|
|
263
|
+
await call(current, "end", { sessionId: raw.sessionId, reason: "error" }).catch(
|
|
264
|
+
() => void 0
|
|
265
|
+
);
|
|
266
|
+
await saveHandle(null);
|
|
267
|
+
setActive(null);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
setActive({ ...raw, status: "live" });
|
|
271
|
+
}
|
|
272
|
+
options.onGrant?.(activeGrant ?? raw, "start");
|
|
273
|
+
return raw;
|
|
274
|
+
},
|
|
275
|
+
async refresh(sessionId) {
|
|
276
|
+
await loadHandle();
|
|
277
|
+
const current = requireHandle(sessionId);
|
|
278
|
+
const data = await call(current, "refresh", {});
|
|
279
|
+
if (!isCameraGrant(data)) throw new InvalidResponseError();
|
|
280
|
+
await adopt(data, current.quality);
|
|
281
|
+
options.onGrant?.(data, "refresh");
|
|
282
|
+
return {
|
|
283
|
+
participantToken: data.participantToken,
|
|
284
|
+
tokenExpiresAt: data.tokenExpiresAt,
|
|
285
|
+
telemetry: data.telemetry,
|
|
286
|
+
config: data.config
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
async end(input) {
|
|
290
|
+
await loadHandle();
|
|
291
|
+
if (!handle || handle.sessionId !== input.sessionId) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const current = handle;
|
|
295
|
+
if (current.tokenExpiresAt <= Date.now()) {
|
|
296
|
+
await saveHandle(null);
|
|
297
|
+
setActive(null);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
await call(current, "end", { sessionId: input.sessionId, reason: input.reason });
|
|
302
|
+
} catch (error) {
|
|
303
|
+
const status = httpStatusOf(error);
|
|
304
|
+
if (status !== 404 && status !== 409 && status !== 410) throw error;
|
|
305
|
+
}
|
|
306
|
+
await saveHandle(null);
|
|
307
|
+
setActive(null);
|
|
308
|
+
},
|
|
309
|
+
async publish(sessionId) {
|
|
310
|
+
await loadHandle();
|
|
311
|
+
const current = requireHandle(sessionId);
|
|
312
|
+
const data = await call(current, "publish", {
|
|
313
|
+
sessionId,
|
|
314
|
+
...current.quality !== void 0 ? { quality: current.quality } : {}
|
|
315
|
+
});
|
|
316
|
+
if (!isSessionStatus(data)) throw new InvalidResponseError();
|
|
317
|
+
if (activeGrant) setActive({ ...activeGrant, status: "live" });
|
|
318
|
+
},
|
|
319
|
+
async unpublish(sessionId) {
|
|
320
|
+
await loadHandle();
|
|
321
|
+
const current = requireHandle(sessionId);
|
|
322
|
+
const data = await call(current, "unpublish", { sessionId });
|
|
323
|
+
if (!isSessionStatus(data)) throw new InvalidResponseError();
|
|
324
|
+
if (activeGrant) setActive({ ...activeGrant, status: "scheduled" });
|
|
325
|
+
},
|
|
326
|
+
adoptResumeGrant(grant) {
|
|
327
|
+
pendingResume = grant;
|
|
328
|
+
},
|
|
329
|
+
async resume(sessionId) {
|
|
330
|
+
await loadHandle();
|
|
331
|
+
if (!handle || handle.sessionId !== sessionId) return { kind: "gone" };
|
|
332
|
+
const current = handle;
|
|
333
|
+
if (current.tokenExpiresAt <= Date.now()) {
|
|
334
|
+
await saveHandle(null);
|
|
335
|
+
return { kind: "gone" };
|
|
336
|
+
}
|
|
337
|
+
let data;
|
|
338
|
+
try {
|
|
339
|
+
data = await post(`${current.base}/refresh`, {}, bearer(current));
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const status = httpStatusOf(error);
|
|
342
|
+
if (status === void 0) return { kind: "retry" };
|
|
343
|
+
const verdict = classifyResumeStatus(status);
|
|
344
|
+
return { kind: verdict === "ok" ? "retry" : verdict };
|
|
345
|
+
}
|
|
346
|
+
if (!isCameraGrant(data)) return { kind: "retry" };
|
|
347
|
+
await adopt(data, current.quality);
|
|
348
|
+
options.onGrant?.(data, "resume");
|
|
349
|
+
return { kind: "grant", grant: data, live: data.status === "live" };
|
|
350
|
+
},
|
|
351
|
+
getActiveGrant: () => activeGrant,
|
|
352
|
+
subscribe(listener) {
|
|
353
|
+
listeners.add(listener);
|
|
354
|
+
return () => {
|
|
355
|
+
listeners.delete(listener);
|
|
356
|
+
};
|
|
357
|
+
},
|
|
358
|
+
async hasResumableSession() {
|
|
359
|
+
await loadHandle();
|
|
360
|
+
return handle !== null;
|
|
361
|
+
},
|
|
362
|
+
async clearStoredSession() {
|
|
363
|
+
pendingResume = null;
|
|
364
|
+
await saveHandle(null);
|
|
365
|
+
setActive(null);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
return provider;
|
|
999
369
|
}
|
|
1000
|
-
function
|
|
1001
|
-
|
|
370
|
+
function useCameraGrant(provider) {
|
|
371
|
+
const React = __require("react");
|
|
372
|
+
return React.useSyncExternalStore(
|
|
373
|
+
provider.subscribe,
|
|
374
|
+
provider.getActiveGrant,
|
|
375
|
+
provider.getActiveGrant
|
|
376
|
+
);
|
|
1002
377
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
378
|
+
|
|
379
|
+
// src/resume.ts
|
|
380
|
+
function withResume(provider, resume) {
|
|
381
|
+
let pending = null;
|
|
382
|
+
return {
|
|
383
|
+
bootstrap: provider.bootstrap.bind(provider),
|
|
384
|
+
refresh: provider.refresh.bind(provider),
|
|
385
|
+
end: provider.end.bind(provider),
|
|
386
|
+
publish: provider.publish?.bind(provider),
|
|
387
|
+
unpublish: provider.unpublish?.bind(provider),
|
|
388
|
+
async start(input) {
|
|
389
|
+
const grant = pending;
|
|
390
|
+
if (grant && resumeGrantMatches(input.externalSessionId, grant.sessionId)) {
|
|
391
|
+
pending = null;
|
|
392
|
+
return grant;
|
|
393
|
+
}
|
|
394
|
+
return provider.start(input);
|
|
395
|
+
},
|
|
396
|
+
adoptResumeGrant(grant) {
|
|
397
|
+
pending = grant;
|
|
398
|
+
},
|
|
399
|
+
resume
|
|
400
|
+
};
|
|
1005
401
|
}
|
|
1006
|
-
function
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
402
|
+
function grantFromRefreshPayload(data) {
|
|
403
|
+
if (!isRecord(data) || typeof data.sessionId !== "string" || typeof data.serverUrl !== "string" || typeof data.participantToken !== "string" || !isFiniteNumber(data.tokenExpiresAt) || !isTelemetryCredentials(data.telemetry) || !isSdkRemoteConfig(data.config)) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
sessionId: data.sessionId,
|
|
408
|
+
serverUrl: data.serverUrl,
|
|
409
|
+
participantToken: data.participantToken,
|
|
410
|
+
playbackUrl: typeof data.playbackUrl === "string" ? data.playbackUrl : null,
|
|
411
|
+
tokenExpiresAt: data.tokenExpiresAt,
|
|
412
|
+
telemetry: data.telemetry,
|
|
413
|
+
config: data.config
|
|
414
|
+
};
|
|
1011
415
|
}
|
|
1012
|
-
function
|
|
1013
|
-
|
|
416
|
+
function createHttpResumeFetcher(options) {
|
|
417
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
418
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
419
|
+
return async (sessionId) => {
|
|
420
|
+
const controller = new AbortController();
|
|
421
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
422
|
+
try {
|
|
423
|
+
const headers = typeof options.headers === "function" ? await options.headers() : options.headers ?? {};
|
|
424
|
+
const response = await fetchImplementation(options.url(sessionId), {
|
|
425
|
+
method: "POST",
|
|
426
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
427
|
+
body: JSON.stringify({}),
|
|
428
|
+
signal: controller.signal
|
|
429
|
+
});
|
|
430
|
+
const verdict = classifyResumeStatus(response.status);
|
|
431
|
+
if (verdict !== "ok") return { kind: verdict };
|
|
432
|
+
const envelope = await response.json().catch(() => null);
|
|
433
|
+
const grant = grantFromRefreshPayload(envelope?.data);
|
|
434
|
+
if (!grant) return { kind: "retry" };
|
|
435
|
+
const status = (envelope?.data).status;
|
|
436
|
+
return { kind: "grant", grant, live: status === "live" };
|
|
437
|
+
} catch {
|
|
438
|
+
return { kind: "retry" };
|
|
439
|
+
} finally {
|
|
440
|
+
clearTimeout(timer);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
1014
443
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
444
|
+
|
|
445
|
+
// src/local-video-quality.ts
|
|
446
|
+
function withLocalVideoQuality(provider, resolution) {
|
|
447
|
+
return {
|
|
448
|
+
...provider,
|
|
449
|
+
async bootstrap(input) {
|
|
450
|
+
const result = await provider.bootstrap(input);
|
|
451
|
+
return { ...result, config: { ...result.config, videoQuality: resolution() } };
|
|
452
|
+
},
|
|
453
|
+
async start(input) {
|
|
454
|
+
const quality = resolution();
|
|
455
|
+
const result = await provider.start({ ...input, quality });
|
|
456
|
+
return { ...result, config: { ...result.config, videoQuality: quality } };
|
|
457
|
+
},
|
|
458
|
+
async refresh(sessionId) {
|
|
459
|
+
const result = await provider.refresh(sessionId);
|
|
460
|
+
return { ...result, config: { ...result.config, videoQuality: resolution() } };
|
|
461
|
+
},
|
|
462
|
+
end: (input) => provider.end(input),
|
|
463
|
+
// publish/unpublish là optional trong contract — forward nguyên trạng.
|
|
464
|
+
publish: provider.publish ? (sessionId) => provider.publish(sessionId) : void 0,
|
|
465
|
+
unpublish: provider.unpublish ? (sessionId) => provider.unpublish(sessionId) : void 0
|
|
466
|
+
};
|
|
1024
467
|
}
|
|
1025
468
|
export {
|
|
469
|
+
AUDIO_BITRATE_BPS,
|
|
470
|
+
CAMERA_GRANT_STORAGE_KEY,
|
|
471
|
+
CAMERA_SETTINGS_STORAGE_KEY,
|
|
1026
472
|
ConsentRequiredError,
|
|
473
|
+
DEFAULT_CAMERA_SETTINGS,
|
|
474
|
+
DEFAULT_LOCAL_CONFIG,
|
|
475
|
+
DEFAULT_UPLOAD_URL,
|
|
1027
476
|
ExpoSdkConfigurationError,
|
|
1028
477
|
ExpoSdkTimeoutError,
|
|
1029
478
|
InvalidResponseError,
|
|
1030
|
-
LivestreamScreen,
|
|
1031
479
|
NetworkUnavailableError,
|
|
1032
480
|
PICKLEBALL_EXPO_SDK_VERSION,
|
|
1033
481
|
PermissionDeniedError,
|
|
482
|
+
PickleballAutoPrepare,
|
|
483
|
+
PickleballAutoResume,
|
|
1034
484
|
PickleballExpoError,
|
|
1035
485
|
PickleballLiveProvider,
|
|
486
|
+
RESUME_RETRY_DELAYS_MS,
|
|
1036
487
|
RecordingFailureError,
|
|
1037
488
|
SDK_TELEMETRY_EVENT_NAMES,
|
|
1038
489
|
SdkDisabledError,
|
|
@@ -1041,14 +492,45 @@ export {
|
|
|
1041
492
|
UnknownSdkError,
|
|
1042
493
|
UnsupportedRuntimeError,
|
|
1043
494
|
UnsupportedSdkError,
|
|
495
|
+
VIDEO_BITRATE_BPS,
|
|
496
|
+
applyAntiFlicker,
|
|
497
|
+
cameraSettingsKey,
|
|
498
|
+
classifyResumeStatus,
|
|
1044
499
|
compareSemver,
|
|
500
|
+
createCameraGrantProvider,
|
|
501
|
+
createCameraSettingsStore,
|
|
1045
502
|
createDefaultStorage,
|
|
503
|
+
createHttpResumeFetcher,
|
|
1046
504
|
createHttpSessionProvider,
|
|
505
|
+
createRtmpRoomFactory,
|
|
506
|
+
createRtmpRuntime,
|
|
1047
507
|
defaultLivestreamTheme,
|
|
1048
508
|
errorFromCode,
|
|
509
|
+
getCameraCapabilities,
|
|
510
|
+
grantFromRefreshPayload,
|
|
1049
511
|
isSdkRemoteConfig,
|
|
1050
512
|
isSdkVersionSupported,
|
|
1051
513
|
isSessionGoneError,
|
|
1052
514
|
normalizeError,
|
|
1053
|
-
|
|
515
|
+
parseCameraSettings,
|
|
516
|
+
requestNativePermissions,
|
|
517
|
+
requiredMbpsFor,
|
|
518
|
+
resumeBlocksArm,
|
|
519
|
+
resumeExternalSessionId,
|
|
520
|
+
resumeGrantMatches,
|
|
521
|
+
resumeRetryDelayMs,
|
|
522
|
+
rtmpRoomFactoryFor,
|
|
523
|
+
runUplinkCheck,
|
|
524
|
+
setExposureBias,
|
|
525
|
+
setExposureLocked,
|
|
526
|
+
setFocusLocked,
|
|
527
|
+
setTorch,
|
|
528
|
+
setZoom,
|
|
529
|
+
useCameraGrant,
|
|
530
|
+
useCameraSettings,
|
|
531
|
+
useLivestream,
|
|
532
|
+
useStandbySession,
|
|
533
|
+
verdictFor,
|
|
534
|
+
withLocalVideoQuality,
|
|
535
|
+
withResume
|
|
1054
536
|
};
|