@midscene/playground-app 1.6.3-beta-20260403070857.0 → 1.6.4

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.
@@ -8,6 +8,7 @@ import { PreviewRenderer } from "./PreviewRenderer.mjs";
8
8
  import { SessionSetupPanel } from "./SessionSetupPanel.mjs";
9
9
  import server_offline_background from "./icons/server-offline-background.mjs";
10
10
  import server_offline_foreground from "./icons/server-offline-foreground.mjs";
11
+ import { resolveAutoCreateSessionInput } from "./session-setup.mjs";
11
12
  import { buildSessionInitialValues, resolveSessionViewState } from "./session-state.mjs";
12
13
  import { useServerStatus } from "./useServerStatus.mjs";
13
14
  import "./PlaygroundApp.css";
@@ -106,7 +107,7 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
106
107
  }), [
107
108
  serverUrl
108
109
  ]);
109
- const { serverOnline, isUserOperating, deviceType, runtimeInfo, executionUxHints } = useServerStatus(playgroundSDK, defaultDeviceType, pollIntervalMs);
110
+ const { serverOnline, isUserOperating, deviceType, runtimeInfo, executionUxHints, refreshServerState } = useServerStatus(playgroundSDK, defaultDeviceType, pollIntervalMs);
110
111
  const sessionViewState = useMemo(()=>resolveSessionViewState(runtimeInfo), [
111
112
  runtimeInfo
112
113
  ]);
@@ -114,6 +115,8 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
114
115
  const countdownResolveRef = useRef(null);
115
116
  const mountedRef = useRef(true);
116
117
  const lastSetupPlatformIdRef = useRef(void 0);
118
+ const autoCreateSignatureRef = useRef(null);
119
+ const autoCreateDisabledRef = useRef(false);
117
120
  const finishCountdown = useCallback(()=>{
118
121
  if (null !== countdownTimerRef.current) {
119
122
  window.clearInterval(countdownTimerRef.current);
@@ -242,31 +245,41 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
242
245
  version: appVersion,
243
246
  targetName: null != (_ref1 = null != (_ref = null != (_runtimeInfo_platformId = null == runtimeInfo ? void 0 : runtimeInfo.platformId) ? _runtimeInfo_platformId : null == branding ? void 0 : branding.targetName) ? _ref : deviceType) ? _ref1 : 'screen'
244
247
  });
245
- const handleCreateSession = useCallback(()=>_async_to_generator(function*() {
248
+ const createSession = useCallback((input, options)=>_async_to_generator(function*() {
246
249
  try {
247
- const values = yield setupForm.validateFields();
250
+ const values = null != input ? input : yield setupForm.validateFields();
248
251
  setSessionMutating(true);
249
252
  yield playgroundSDK.createSession(values);
250
- messageApi.success('Agent created');
251
- yield refreshSessionSetup();
253
+ if (!(null == options ? void 0 : options.silent)) messageApi.success('Agent created');
254
+ yield refreshServerState();
255
+ return true;
252
256
  } catch (error) {
253
- if (error.errorFields) return;
257
+ if (error.errorFields) return false;
254
258
  const errorMessage = error instanceof Error ? error.message : 'Failed to create Agent';
255
259
  messageApi.error(errorMessage);
260
+ return false;
256
261
  } finally{
257
262
  setSessionMutating(false);
258
263
  }
259
264
  })(), [
260
265
  messageApi,
261
266
  playgroundSDK,
262
- refreshSessionSetup,
267
+ refreshServerState,
263
268
  setupForm
264
269
  ]);
270
+ const handleCreateSession = useCallback(()=>_async_to_generator(function*() {
271
+ autoCreateDisabledRef.current = true;
272
+ yield createSession();
273
+ })(), [
274
+ createSession
275
+ ]);
265
276
  const handleDestroySession = useCallback(()=>_async_to_generator(function*() {
266
277
  try {
278
+ autoCreateDisabledRef.current = true;
267
279
  setSessionMutating(true);
268
280
  yield playgroundSDK.destroySession();
269
281
  messageApi.success('Session disconnected');
282
+ yield refreshServerState();
270
283
  yield refreshSessionSetup();
271
284
  } catch (error) {
272
285
  const errorMessage = error instanceof Error ? error.message : 'Failed to disconnect session';
@@ -277,8 +290,39 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
277
290
  })(), [
278
291
  messageApi,
279
292
  playgroundSDK,
293
+ refreshServerState,
280
294
  refreshSessionSetup
281
295
  ]);
296
+ useEffect(()=>{
297
+ if (sessionViewState.connected) {
298
+ autoCreateSignatureRef.current = null;
299
+ return;
300
+ }
301
+ if (!serverOnline || sessionLoading || sessionMutating || sessionSetupError || autoCreateDisabledRef.current) return;
302
+ const autoCreateInput = resolveAutoCreateSessionInput(sessionSetup, setupForm.getFieldsValue(true));
303
+ if (!autoCreateInput) {
304
+ autoCreateSignatureRef.current = null;
305
+ return;
306
+ }
307
+ const signature = JSON.stringify(autoCreateInput);
308
+ if (autoCreateSignatureRef.current === signature) return;
309
+ autoCreateSignatureRef.current = signature;
310
+ (()=>_async_to_generator(function*() {
311
+ const created = yield createSession(autoCreateInput, {
312
+ silent: true
313
+ });
314
+ if (!created) autoCreateSignatureRef.current = null;
315
+ })())();
316
+ }, [
317
+ createSession,
318
+ serverOnline,
319
+ sessionLoading,
320
+ sessionMutating,
321
+ sessionSetup,
322
+ sessionSetupError,
323
+ sessionViewState.connected,
324
+ setupForm
325
+ ]);
282
326
  if (!serverOnline) return /*#__PURE__*/ jsx(ConfigProvider, {
283
327
  theme: globalThemeConfig(),
284
328
  children: /*#__PURE__*/ jsx("div", {
@@ -400,7 +444,8 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
400
444
  /*#__PURE__*/ jsx(Logo, {}),
401
445
  /*#__PURE__*/ jsx(NavActions, {
402
446
  showTooltipWhenEmpty: false,
403
- showModelName: false
447
+ showModelName: false,
448
+ playgroundSDK: playgroundSDK
404
449
  })
405
450
  ]
406
451
  })
@@ -4,6 +4,7 @@ import { BitmapVideoFrameRenderer, WebCodecsVideoDecoder, WebGLVideoFrameRendere
4
4
  import { Alert, Button, Card, Space, Spin, Typography } from "antd";
5
5
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
6
  import { io } from "socket.io-client";
7
+ import { createScrcpyVideoStream } from "./scrcpy-stream.mjs";
7
8
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
8
9
  try {
9
10
  var info = gen[key](arg);
@@ -131,55 +132,6 @@ function ScrcpyPanel({ serverUrl, reconnectInterval = 3000 }) {
131
132
  });
132
133
  return decoder;
133
134
  })();
134
- const setupVideoStream = ()=>{
135
- const socket = socketRef.current;
136
- let configurationPacketSent = false;
137
- let pendingDataPackets = [];
138
- const transformStream = new TransformStream({
139
- transform (chunk, controller) {
140
- const packet = {
141
- type: chunk.type,
142
- data: new Uint8Array(chunk.data),
143
- timestamp: chunk.timestamp
144
- };
145
- if ('configuration' === packet.type) {
146
- configurationPacketSent = true;
147
- controller.enqueue(packet);
148
- pendingDataPackets.forEach((queuedPacket)=>controller.enqueue(queuedPacket));
149
- pendingDataPackets = [];
150
- return;
151
- }
152
- if ('data' === packet.type && !configurationPacketSent) return void pendingDataPackets.push(packet);
153
- controller.enqueue(packet);
154
- }
155
- });
156
- let cleanupListeners;
157
- const readable = new ReadableStream({
158
- start (controller) {
159
- const handleVideoData = (data)=>{
160
- try {
161
- controller.enqueue(data);
162
- } catch (error) {
163
- controller.error(error);
164
- }
165
- };
166
- const handleDisconnect = ()=>controller.close();
167
- const handleError = (error)=>controller.error(error);
168
- cleanupListeners = ()=>{
169
- null == socket || socket.off('video-data', handleVideoData);
170
- null == socket || socket.off('disconnect', handleDisconnect);
171
- null == socket || socket.off('error', handleError);
172
- };
173
- null == socket || socket.on('video-data', handleVideoData);
174
- null == socket || socket.on('disconnect', handleDisconnect);
175
- null == socket || socket.on('error', handleError);
176
- },
177
- cancel () {
178
- null == cleanupListeners || cleanupListeners();
179
- }
180
- });
181
- return readable.pipeThrough(transformStream);
182
- };
183
135
  const connect = ()=>{
184
136
  if (disposed) return;
185
137
  manuallyDisconnectedRef.current = false;
@@ -192,6 +144,7 @@ function ScrcpyPanel({ serverUrl, reconnectInterval = 3000 }) {
192
144
  timeout: 10000
193
145
  });
194
146
  socketRef.current = socket;
147
+ const videoStream = createScrcpyVideoStream(socket);
195
148
  socket.on('connect', ()=>{
196
149
  socket.emit('connect-device', {
197
150
  maxSize: 1024
@@ -207,7 +160,7 @@ function ScrcpyPanel({ serverUrl, reconnectInterval = 3000 }) {
207
160
  width: metadata.width,
208
161
  height: metadata.height
209
162
  });
210
- setupVideoStream().pipeTo(decoder.writable).catch((error)=>{
163
+ videoStream.pipeTo(decoder.writable).catch((error)=>{
211
164
  if (disposed) return;
212
165
  setStatus('error');
213
166
  setErrorMessage(error.message);
@@ -0,0 +1,53 @@
1
+ function createScrcpyVideoStream(socket) {
2
+ let configurationPacketSent = false;
3
+ let pendingDataPackets = [];
4
+ const transformStream = new TransformStream({
5
+ transform (chunk, controller) {
6
+ if ('configuration' === chunk.type) {
7
+ configurationPacketSent = true;
8
+ controller.enqueue(chunk);
9
+ pendingDataPackets.forEach((queuedPacket)=>controller.enqueue(queuedPacket));
10
+ pendingDataPackets = [];
11
+ return;
12
+ }
13
+ if ('data' === chunk.type && !configurationPacketSent) return void pendingDataPackets.push(chunk);
14
+ controller.enqueue(chunk);
15
+ }
16
+ });
17
+ let cleanupListeners;
18
+ const readable = new ReadableStream({
19
+ start (controller) {
20
+ const handleVideoData = (data)=>{
21
+ try {
22
+ const payload = new Uint8Array(data.data);
23
+ if ('configuration' === data.type) return void controller.enqueue({
24
+ type: 'configuration',
25
+ data: payload
26
+ });
27
+ controller.enqueue({
28
+ type: 'data',
29
+ data: payload,
30
+ keyframe: data.keyFrame
31
+ });
32
+ } catch (error) {
33
+ controller.error(error);
34
+ }
35
+ };
36
+ const handleDisconnect = ()=>controller.close();
37
+ const handleError = (error)=>controller.error(error);
38
+ cleanupListeners = ()=>{
39
+ socket.off('video-data', handleVideoData);
40
+ socket.off('disconnect', handleDisconnect);
41
+ socket.off('error', handleError);
42
+ };
43
+ socket.on('video-data', handleVideoData);
44
+ socket.on('disconnect', handleDisconnect);
45
+ socket.on('error', handleError);
46
+ },
47
+ cancel () {
48
+ null == cleanupListeners || cleanupListeners();
49
+ }
50
+ });
51
+ return readable.pipeThrough(transformStream);
52
+ }
53
+ export { createScrcpyVideoStream };
@@ -0,0 +1,13 @@
1
+ import { buildSessionInitialValues } from "./session-state.mjs";
2
+ function hasSessionFieldValue(value) {
3
+ return null != value && '' !== value;
4
+ }
5
+ function resolveAutoCreateSessionInput(setup, existingValues = {}) {
6
+ if (!(null == setup ? void 0 : setup.autoSubmitWhenReady)) return null;
7
+ const values = buildSessionInitialValues(setup, existingValues);
8
+ for (const field of setup.fields)if (field.required) {
9
+ if (!hasSessionFieldValue(values[field.key])) return null;
10
+ }
11
+ return values;
12
+ }
13
+ export { resolveAutoCreateSessionInput };
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState } from "react";
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { buildFallbackRuntimeInfo, filterValidExecutionUxHints, normalizeRuntimeDeviceType } from "./runtime-info.mjs";
3
3
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
4
4
  try {
@@ -33,6 +33,13 @@ function useServerStatus(playgroundSDK, defaultDeviceType, pollIntervalMs) {
33
33
  const [runtimeInfo, setRuntimeInfo] = useState(null);
34
34
  const [executionUxHints, setExecutionUxHints] = useState([]);
35
35
  const runtimeInfoRef = useRef(null);
36
+ const mountedRef = useRef(true);
37
+ useEffect(()=>{
38
+ mountedRef.current = true;
39
+ return ()=>{
40
+ mountedRef.current = false;
41
+ };
42
+ }, []);
36
43
  useEffect(()=>{
37
44
  runtimeInfoRef.current = runtimeInfo;
38
45
  }, [
@@ -45,65 +52,69 @@ function useServerStatus(playgroundSDK, defaultDeviceType, pollIntervalMs) {
45
52
  }, [
46
53
  playgroundSDK
47
54
  ]);
48
- useEffect(()=>{
49
- let active = true;
50
- const checkServer = ()=>_async_to_generator(function*() {
55
+ const refreshServerState = useCallback(()=>_async_to_generator(function*() {
56
+ try {
57
+ const online = yield playgroundSDK.checkStatus();
58
+ if (!mountedRef.current) return;
59
+ setServerOnline(online);
60
+ if (!online) {
61
+ runtimeInfoRef.current = null;
62
+ setRuntimeInfo(null);
63
+ setExecutionUxHints([]);
64
+ return;
65
+ }
51
66
  try {
52
- const online = yield playgroundSDK.checkStatus();
53
- if (!active) return;
54
- setServerOnline(online);
55
- if (!online) {
56
- runtimeInfoRef.current = null;
57
- setRuntimeInfo(null);
58
- setExecutionUxHints([]);
67
+ const nextRuntimeInfo = yield playgroundSDK.getRuntimeInfo();
68
+ if (!mountedRef.current) return;
69
+ if (nextRuntimeInfo) {
70
+ runtimeInfoRef.current = nextRuntimeInfo;
71
+ setRuntimeInfo(nextRuntimeInfo);
72
+ setDeviceType(normalizeRuntimeDeviceType(nextRuntimeInfo, defaultDeviceType));
73
+ setExecutionUxHints(filterValidExecutionUxHints(nextRuntimeInfo));
59
74
  return;
60
75
  }
61
- try {
62
- const nextRuntimeInfo = yield playgroundSDK.getRuntimeInfo();
63
- if (!active) return;
64
- if (nextRuntimeInfo) {
65
- setRuntimeInfo(nextRuntimeInfo);
66
- setDeviceType(normalizeRuntimeDeviceType(nextRuntimeInfo, defaultDeviceType));
67
- setExecutionUxHints(filterValidExecutionUxHints(nextRuntimeInfo));
68
- return;
69
- }
70
- } catch (error) {
71
- console.warn('Failed to get runtime info:', error);
72
- }
73
- try {
74
- const interfaceInfo = yield playgroundSDK.getInterfaceInfo();
75
- if (!active || !(null == interfaceInfo ? void 0 : interfaceInfo.type)) return;
76
- const fallbackRuntimeInfo = buildFallbackRuntimeInfo(runtimeInfoRef.current, interfaceInfo);
77
- runtimeInfoRef.current = fallbackRuntimeInfo;
78
- setRuntimeInfo(fallbackRuntimeInfo);
79
- setDeviceType(normalizeRuntimeDeviceType(fallbackRuntimeInfo, defaultDeviceType));
80
- setExecutionUxHints(filterValidExecutionUxHints(fallbackRuntimeInfo));
81
- } catch (error) {
82
- console.warn('Failed to get interface info:', error);
83
- }
84
76
  } catch (error) {
85
- if (!active) return;
86
- console.error('Failed to check server status:', error);
87
- setServerOnline(false);
77
+ console.warn('Failed to get runtime info:', error);
88
78
  }
89
- })();
90
- checkServer();
91
- const interval = window.setInterval(checkServer, pollIntervalMs);
79
+ try {
80
+ const interfaceInfo = yield playgroundSDK.getInterfaceInfo();
81
+ if (!mountedRef.current || !(null == interfaceInfo ? void 0 : interfaceInfo.type)) return;
82
+ const fallbackRuntimeInfo = buildFallbackRuntimeInfo(runtimeInfoRef.current, interfaceInfo);
83
+ runtimeInfoRef.current = fallbackRuntimeInfo;
84
+ setRuntimeInfo(fallbackRuntimeInfo);
85
+ setDeviceType(normalizeRuntimeDeviceType(fallbackRuntimeInfo, defaultDeviceType));
86
+ setExecutionUxHints(filterValidExecutionUxHints(fallbackRuntimeInfo));
87
+ } catch (error) {
88
+ console.warn('Failed to get interface info:', error);
89
+ }
90
+ } catch (error) {
91
+ if (!mountedRef.current) return;
92
+ console.error('Failed to check server status:', error);
93
+ setServerOnline(false);
94
+ }
95
+ })(), [
96
+ defaultDeviceType,
97
+ playgroundSDK
98
+ ]);
99
+ useEffect(()=>{
100
+ refreshServerState();
101
+ const interval = window.setInterval(()=>{
102
+ refreshServerState();
103
+ }, pollIntervalMs);
92
104
  return ()=>{
93
- active = false;
94
105
  window.clearInterval(interval);
95
106
  };
96
107
  }, [
97
- playgroundSDK,
98
108
  pollIntervalMs,
99
- defaultDeviceType
109
+ refreshServerState
100
110
  ]);
101
111
  return {
102
112
  serverOnline,
103
113
  isUserOperating,
104
114
  deviceType,
105
115
  runtimeInfo,
106
- executionUxHints
116
+ executionUxHints,
117
+ refreshServerState
107
118
  };
108
119
  }
109
120
  export { useServerStatus };
@@ -47,6 +47,7 @@ const server_offline_background_js_namespaceObject = require("./icons/server-off
47
47
  var server_offline_background_js_default = /*#__PURE__*/ __webpack_require__.n(server_offline_background_js_namespaceObject);
48
48
  const server_offline_foreground_js_namespaceObject = require("./icons/server-offline-foreground.js");
49
49
  var server_offline_foreground_js_default = /*#__PURE__*/ __webpack_require__.n(server_offline_foreground_js_namespaceObject);
50
+ const external_session_setup_js_namespaceObject = require("./session-setup.js");
50
51
  const external_session_state_js_namespaceObject = require("./session-state.js");
51
52
  const external_useServerStatus_js_namespaceObject = require("./useServerStatus.js");
52
53
  require("./PlaygroundApp.css");
@@ -145,7 +146,7 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
145
146
  }), [
146
147
  serverUrl
147
148
  ]);
148
- const { serverOnline, isUserOperating, deviceType, runtimeInfo, executionUxHints } = (0, external_useServerStatus_js_namespaceObject.useServerStatus)(playgroundSDK, defaultDeviceType, pollIntervalMs);
149
+ const { serverOnline, isUserOperating, deviceType, runtimeInfo, executionUxHints, refreshServerState } = (0, external_useServerStatus_js_namespaceObject.useServerStatus)(playgroundSDK, defaultDeviceType, pollIntervalMs);
149
150
  const sessionViewState = (0, external_react_namespaceObject.useMemo)(()=>(0, external_session_state_js_namespaceObject.resolveSessionViewState)(runtimeInfo), [
150
151
  runtimeInfo
151
152
  ]);
@@ -153,6 +154,8 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
153
154
  const countdownResolveRef = (0, external_react_namespaceObject.useRef)(null);
154
155
  const mountedRef = (0, external_react_namespaceObject.useRef)(true);
155
156
  const lastSetupPlatformIdRef = (0, external_react_namespaceObject.useRef)(void 0);
157
+ const autoCreateSignatureRef = (0, external_react_namespaceObject.useRef)(null);
158
+ const autoCreateDisabledRef = (0, external_react_namespaceObject.useRef)(false);
156
159
  const finishCountdown = (0, external_react_namespaceObject.useCallback)(()=>{
157
160
  if (null !== countdownTimerRef.current) {
158
161
  window.clearInterval(countdownTimerRef.current);
@@ -281,31 +284,41 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
281
284
  version: appVersion,
282
285
  targetName: null != (_ref1 = null != (_ref = null != (_runtimeInfo_platformId = null == runtimeInfo ? void 0 : runtimeInfo.platformId) ? _runtimeInfo_platformId : null == branding ? void 0 : branding.targetName) ? _ref : deviceType) ? _ref1 : 'screen'
283
286
  });
284
- const handleCreateSession = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
287
+ const createSession = (0, external_react_namespaceObject.useCallback)((input, options)=>_async_to_generator(function*() {
285
288
  try {
286
- const values = yield setupForm.validateFields();
289
+ const values = null != input ? input : yield setupForm.validateFields();
287
290
  setSessionMutating(true);
288
291
  yield playgroundSDK.createSession(values);
289
- messageApi.success('Agent created');
290
- yield refreshSessionSetup();
292
+ if (!(null == options ? void 0 : options.silent)) messageApi.success('Agent created');
293
+ yield refreshServerState();
294
+ return true;
291
295
  } catch (error) {
292
- if (error.errorFields) return;
296
+ if (error.errorFields) return false;
293
297
  const errorMessage = error instanceof Error ? error.message : 'Failed to create Agent';
294
298
  messageApi.error(errorMessage);
299
+ return false;
295
300
  } finally{
296
301
  setSessionMutating(false);
297
302
  }
298
303
  })(), [
299
304
  messageApi,
300
305
  playgroundSDK,
301
- refreshSessionSetup,
306
+ refreshServerState,
302
307
  setupForm
303
308
  ]);
309
+ const handleCreateSession = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
310
+ autoCreateDisabledRef.current = true;
311
+ yield createSession();
312
+ })(), [
313
+ createSession
314
+ ]);
304
315
  const handleDestroySession = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
305
316
  try {
317
+ autoCreateDisabledRef.current = true;
306
318
  setSessionMutating(true);
307
319
  yield playgroundSDK.destroySession();
308
320
  messageApi.success('Session disconnected');
321
+ yield refreshServerState();
309
322
  yield refreshSessionSetup();
310
323
  } catch (error) {
311
324
  const errorMessage = error instanceof Error ? error.message : 'Failed to disconnect session';
@@ -316,8 +329,39 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
316
329
  })(), [
317
330
  messageApi,
318
331
  playgroundSDK,
332
+ refreshServerState,
319
333
  refreshSessionSetup
320
334
  ]);
335
+ (0, external_react_namespaceObject.useEffect)(()=>{
336
+ if (sessionViewState.connected) {
337
+ autoCreateSignatureRef.current = null;
338
+ return;
339
+ }
340
+ if (!serverOnline || sessionLoading || sessionMutating || sessionSetupError || autoCreateDisabledRef.current) return;
341
+ const autoCreateInput = (0, external_session_setup_js_namespaceObject.resolveAutoCreateSessionInput)(sessionSetup, setupForm.getFieldsValue(true));
342
+ if (!autoCreateInput) {
343
+ autoCreateSignatureRef.current = null;
344
+ return;
345
+ }
346
+ const signature = JSON.stringify(autoCreateInput);
347
+ if (autoCreateSignatureRef.current === signature) return;
348
+ autoCreateSignatureRef.current = signature;
349
+ (()=>_async_to_generator(function*() {
350
+ const created = yield createSession(autoCreateInput, {
351
+ silent: true
352
+ });
353
+ if (!created) autoCreateSignatureRef.current = null;
354
+ })())();
355
+ }, [
356
+ createSession,
357
+ serverOnline,
358
+ sessionLoading,
359
+ sessionMutating,
360
+ sessionSetup,
361
+ sessionSetupError,
362
+ sessionViewState.connected,
363
+ setupForm
364
+ ]);
321
365
  if (!serverOnline) return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_antd_namespaceObject.ConfigProvider, {
322
366
  theme: (0, visualizer_namespaceObject.globalThemeConfig)(),
323
367
  children: /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("div", {
@@ -439,7 +483,8 @@ function PlaygroundApp({ serverUrl, appVersion, title = 'Playground', defaultDev
439
483
  /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(visualizer_namespaceObject.Logo, {}),
440
484
  /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(visualizer_namespaceObject.NavActions, {
441
485
  showTooltipWhenEmpty: false,
442
- showModelName: false
486
+ showModelName: false,
487
+ playgroundSDK: playgroundSDK
443
488
  })
444
489
  ]
445
490
  })
@@ -32,6 +32,7 @@ const scrcpy_decoder_webcodecs_namespaceObject = require("@yume-chan/scrcpy-deco
32
32
  const external_antd_namespaceObject = require("antd");
33
33
  const external_react_namespaceObject = require("react");
34
34
  const external_socket_io_client_namespaceObject = require("socket.io-client");
35
+ const external_scrcpy_stream_js_namespaceObject = require("./scrcpy-stream.js");
35
36
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
36
37
  try {
37
38
  var info = gen[key](arg);
@@ -159,55 +160,6 @@ function ScrcpyPanel({ serverUrl, reconnectInterval = 3000 }) {
159
160
  });
160
161
  return decoder;
161
162
  })();
162
- const setupVideoStream = ()=>{
163
- const socket = socketRef.current;
164
- let configurationPacketSent = false;
165
- let pendingDataPackets = [];
166
- const transformStream = new TransformStream({
167
- transform (chunk, controller) {
168
- const packet = {
169
- type: chunk.type,
170
- data: new Uint8Array(chunk.data),
171
- timestamp: chunk.timestamp
172
- };
173
- if ('configuration' === packet.type) {
174
- configurationPacketSent = true;
175
- controller.enqueue(packet);
176
- pendingDataPackets.forEach((queuedPacket)=>controller.enqueue(queuedPacket));
177
- pendingDataPackets = [];
178
- return;
179
- }
180
- if ('data' === packet.type && !configurationPacketSent) return void pendingDataPackets.push(packet);
181
- controller.enqueue(packet);
182
- }
183
- });
184
- let cleanupListeners;
185
- const readable = new ReadableStream({
186
- start (controller) {
187
- const handleVideoData = (data)=>{
188
- try {
189
- controller.enqueue(data);
190
- } catch (error) {
191
- controller.error(error);
192
- }
193
- };
194
- const handleDisconnect = ()=>controller.close();
195
- const handleError = (error)=>controller.error(error);
196
- cleanupListeners = ()=>{
197
- null == socket || socket.off('video-data', handleVideoData);
198
- null == socket || socket.off('disconnect', handleDisconnect);
199
- null == socket || socket.off('error', handleError);
200
- };
201
- null == socket || socket.on('video-data', handleVideoData);
202
- null == socket || socket.on('disconnect', handleDisconnect);
203
- null == socket || socket.on('error', handleError);
204
- },
205
- cancel () {
206
- null == cleanupListeners || cleanupListeners();
207
- }
208
- });
209
- return readable.pipeThrough(transformStream);
210
- };
211
163
  const connect = ()=>{
212
164
  if (disposed) return;
213
165
  manuallyDisconnectedRef.current = false;
@@ -220,6 +172,7 @@ function ScrcpyPanel({ serverUrl, reconnectInterval = 3000 }) {
220
172
  timeout: 10000
221
173
  });
222
174
  socketRef.current = socket;
175
+ const videoStream = (0, external_scrcpy_stream_js_namespaceObject.createScrcpyVideoStream)(socket);
223
176
  socket.on('connect', ()=>{
224
177
  socket.emit('connect-device', {
225
178
  maxSize: 1024
@@ -235,7 +188,7 @@ function ScrcpyPanel({ serverUrl, reconnectInterval = 3000 }) {
235
188
  width: metadata.width,
236
189
  height: metadata.height
237
190
  });
238
- setupVideoStream().pipeTo(decoder.writable).catch((error)=>{
191
+ videoStream.pipeTo(decoder.writable).catch((error)=>{
239
192
  if (disposed) return;
240
193
  setStatus('error');
241
194
  setErrorMessage(error.message);
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ createScrcpyVideoStream: ()=>createScrcpyVideoStream
28
+ });
29
+ function createScrcpyVideoStream(socket) {
30
+ let configurationPacketSent = false;
31
+ let pendingDataPackets = [];
32
+ const transformStream = new TransformStream({
33
+ transform (chunk, controller) {
34
+ if ('configuration' === chunk.type) {
35
+ configurationPacketSent = true;
36
+ controller.enqueue(chunk);
37
+ pendingDataPackets.forEach((queuedPacket)=>controller.enqueue(queuedPacket));
38
+ pendingDataPackets = [];
39
+ return;
40
+ }
41
+ if ('data' === chunk.type && !configurationPacketSent) return void pendingDataPackets.push(chunk);
42
+ controller.enqueue(chunk);
43
+ }
44
+ });
45
+ let cleanupListeners;
46
+ const readable = new ReadableStream({
47
+ start (controller) {
48
+ const handleVideoData = (data)=>{
49
+ try {
50
+ const payload = new Uint8Array(data.data);
51
+ if ('configuration' === data.type) return void controller.enqueue({
52
+ type: 'configuration',
53
+ data: payload
54
+ });
55
+ controller.enqueue({
56
+ type: 'data',
57
+ data: payload,
58
+ keyframe: data.keyFrame
59
+ });
60
+ } catch (error) {
61
+ controller.error(error);
62
+ }
63
+ };
64
+ const handleDisconnect = ()=>controller.close();
65
+ const handleError = (error)=>controller.error(error);
66
+ cleanupListeners = ()=>{
67
+ socket.off('video-data', handleVideoData);
68
+ socket.off('disconnect', handleDisconnect);
69
+ socket.off('error', handleError);
70
+ };
71
+ socket.on('video-data', handleVideoData);
72
+ socket.on('disconnect', handleDisconnect);
73
+ socket.on('error', handleError);
74
+ },
75
+ cancel () {
76
+ null == cleanupListeners || cleanupListeners();
77
+ }
78
+ });
79
+ return readable.pipeThrough(transformStream);
80
+ }
81
+ exports.createScrcpyVideoStream = __webpack_exports__.createScrcpyVideoStream;
82
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
83
+ "createScrcpyVideoStream"
84
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
85
+ Object.defineProperty(exports, '__esModule', {
86
+ value: true
87
+ });
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ resolveAutoCreateSessionInput: ()=>resolveAutoCreateSessionInput
28
+ });
29
+ const external_session_state_js_namespaceObject = require("./session-state.js");
30
+ function hasSessionFieldValue(value) {
31
+ return null != value && '' !== value;
32
+ }
33
+ function resolveAutoCreateSessionInput(setup, existingValues = {}) {
34
+ if (!(null == setup ? void 0 : setup.autoSubmitWhenReady)) return null;
35
+ const values = (0, external_session_state_js_namespaceObject.buildSessionInitialValues)(setup, existingValues);
36
+ for (const field of setup.fields)if (field.required) {
37
+ if (!hasSessionFieldValue(values[field.key])) return null;
38
+ }
39
+ return values;
40
+ }
41
+ exports.resolveAutoCreateSessionInput = __webpack_exports__.resolveAutoCreateSessionInput;
42
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
43
+ "resolveAutoCreateSessionInput"
44
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
45
+ Object.defineProperty(exports, '__esModule', {
46
+ value: true
47
+ });
@@ -61,6 +61,13 @@ function useServerStatus(playgroundSDK, defaultDeviceType, pollIntervalMs) {
61
61
  const [runtimeInfo, setRuntimeInfo] = (0, external_react_namespaceObject.useState)(null);
62
62
  const [executionUxHints, setExecutionUxHints] = (0, external_react_namespaceObject.useState)([]);
63
63
  const runtimeInfoRef = (0, external_react_namespaceObject.useRef)(null);
64
+ const mountedRef = (0, external_react_namespaceObject.useRef)(true);
65
+ (0, external_react_namespaceObject.useEffect)(()=>{
66
+ mountedRef.current = true;
67
+ return ()=>{
68
+ mountedRef.current = false;
69
+ };
70
+ }, []);
64
71
  (0, external_react_namespaceObject.useEffect)(()=>{
65
72
  runtimeInfoRef.current = runtimeInfo;
66
73
  }, [
@@ -73,65 +80,69 @@ function useServerStatus(playgroundSDK, defaultDeviceType, pollIntervalMs) {
73
80
  }, [
74
81
  playgroundSDK
75
82
  ]);
76
- (0, external_react_namespaceObject.useEffect)(()=>{
77
- let active = true;
78
- const checkServer = ()=>_async_to_generator(function*() {
83
+ const refreshServerState = (0, external_react_namespaceObject.useCallback)(()=>_async_to_generator(function*() {
84
+ try {
85
+ const online = yield playgroundSDK.checkStatus();
86
+ if (!mountedRef.current) return;
87
+ setServerOnline(online);
88
+ if (!online) {
89
+ runtimeInfoRef.current = null;
90
+ setRuntimeInfo(null);
91
+ setExecutionUxHints([]);
92
+ return;
93
+ }
79
94
  try {
80
- const online = yield playgroundSDK.checkStatus();
81
- if (!active) return;
82
- setServerOnline(online);
83
- if (!online) {
84
- runtimeInfoRef.current = null;
85
- setRuntimeInfo(null);
86
- setExecutionUxHints([]);
95
+ const nextRuntimeInfo = yield playgroundSDK.getRuntimeInfo();
96
+ if (!mountedRef.current) return;
97
+ if (nextRuntimeInfo) {
98
+ runtimeInfoRef.current = nextRuntimeInfo;
99
+ setRuntimeInfo(nextRuntimeInfo);
100
+ setDeviceType((0, external_runtime_info_js_namespaceObject.normalizeRuntimeDeviceType)(nextRuntimeInfo, defaultDeviceType));
101
+ setExecutionUxHints((0, external_runtime_info_js_namespaceObject.filterValidExecutionUxHints)(nextRuntimeInfo));
87
102
  return;
88
103
  }
89
- try {
90
- const nextRuntimeInfo = yield playgroundSDK.getRuntimeInfo();
91
- if (!active) return;
92
- if (nextRuntimeInfo) {
93
- setRuntimeInfo(nextRuntimeInfo);
94
- setDeviceType((0, external_runtime_info_js_namespaceObject.normalizeRuntimeDeviceType)(nextRuntimeInfo, defaultDeviceType));
95
- setExecutionUxHints((0, external_runtime_info_js_namespaceObject.filterValidExecutionUxHints)(nextRuntimeInfo));
96
- return;
97
- }
98
- } catch (error) {
99
- console.warn('Failed to get runtime info:', error);
100
- }
101
- try {
102
- const interfaceInfo = yield playgroundSDK.getInterfaceInfo();
103
- if (!active || !(null == interfaceInfo ? void 0 : interfaceInfo.type)) return;
104
- const fallbackRuntimeInfo = (0, external_runtime_info_js_namespaceObject.buildFallbackRuntimeInfo)(runtimeInfoRef.current, interfaceInfo);
105
- runtimeInfoRef.current = fallbackRuntimeInfo;
106
- setRuntimeInfo(fallbackRuntimeInfo);
107
- setDeviceType((0, external_runtime_info_js_namespaceObject.normalizeRuntimeDeviceType)(fallbackRuntimeInfo, defaultDeviceType));
108
- setExecutionUxHints((0, external_runtime_info_js_namespaceObject.filterValidExecutionUxHints)(fallbackRuntimeInfo));
109
- } catch (error) {
110
- console.warn('Failed to get interface info:', error);
111
- }
112
104
  } catch (error) {
113
- if (!active) return;
114
- console.error('Failed to check server status:', error);
115
- setServerOnline(false);
105
+ console.warn('Failed to get runtime info:', error);
116
106
  }
117
- })();
118
- checkServer();
119
- const interval = window.setInterval(checkServer, pollIntervalMs);
107
+ try {
108
+ const interfaceInfo = yield playgroundSDK.getInterfaceInfo();
109
+ if (!mountedRef.current || !(null == interfaceInfo ? void 0 : interfaceInfo.type)) return;
110
+ const fallbackRuntimeInfo = (0, external_runtime_info_js_namespaceObject.buildFallbackRuntimeInfo)(runtimeInfoRef.current, interfaceInfo);
111
+ runtimeInfoRef.current = fallbackRuntimeInfo;
112
+ setRuntimeInfo(fallbackRuntimeInfo);
113
+ setDeviceType((0, external_runtime_info_js_namespaceObject.normalizeRuntimeDeviceType)(fallbackRuntimeInfo, defaultDeviceType));
114
+ setExecutionUxHints((0, external_runtime_info_js_namespaceObject.filterValidExecutionUxHints)(fallbackRuntimeInfo));
115
+ } catch (error) {
116
+ console.warn('Failed to get interface info:', error);
117
+ }
118
+ } catch (error) {
119
+ if (!mountedRef.current) return;
120
+ console.error('Failed to check server status:', error);
121
+ setServerOnline(false);
122
+ }
123
+ })(), [
124
+ defaultDeviceType,
125
+ playgroundSDK
126
+ ]);
127
+ (0, external_react_namespaceObject.useEffect)(()=>{
128
+ refreshServerState();
129
+ const interval = window.setInterval(()=>{
130
+ refreshServerState();
131
+ }, pollIntervalMs);
120
132
  return ()=>{
121
- active = false;
122
133
  window.clearInterval(interval);
123
134
  };
124
135
  }, [
125
- playgroundSDK,
126
136
  pollIntervalMs,
127
- defaultDeviceType
137
+ refreshServerState
128
138
  ]);
129
139
  return {
130
140
  serverOnline,
131
141
  isUserOperating,
132
142
  deviceType,
133
143
  runtimeInfo,
134
- executionUxHints
144
+ executionUxHints,
145
+ refreshServerState
135
146
  };
136
147
  }
137
148
  exports.useServerStatus = __webpack_exports__.useServerStatus;
@@ -0,0 +1,16 @@
1
+ import type { ScrcpyMediaStreamPacket } from '@yume-chan/scrcpy';
2
+ interface RawScrcpyVideoPacket {
3
+ type?: string;
4
+ data: ArrayLike<number>;
5
+ keyFrame?: boolean;
6
+ }
7
+ interface ScrcpyVideoSocketLike {
8
+ on(event: 'video-data', handler: (data: RawScrcpyVideoPacket) => void): void;
9
+ on(event: 'disconnect', handler: () => void): void;
10
+ on(event: 'error', handler: (error: Error) => void): void;
11
+ off(event: 'video-data', handler: (data: RawScrcpyVideoPacket) => void): void;
12
+ off(event: 'disconnect', handler: () => void): void;
13
+ off(event: 'error', handler: (error: Error) => void): void;
14
+ }
15
+ export declare function createScrcpyVideoStream(socket: ScrcpyVideoSocketLike): ReadableStream<ScrcpyMediaStreamPacket>;
16
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { PlaygroundSessionSetup } from '@midscene/playground';
2
+ export declare function resolveAutoCreateSessionInput(setup: PlaygroundSessionSetup | null, existingValues?: Record<string, unknown>): Record<string, unknown> | null;
@@ -6,6 +6,7 @@ interface ServerStatusResult {
6
6
  deviceType: DeviceType;
7
7
  runtimeInfo: PlaygroundRuntimeInfo | null;
8
8
  executionUxHints: ExecutionUxHint[];
9
+ refreshServerState: () => Promise<void>;
9
10
  }
10
11
  export declare function useServerStatus(playgroundSDK: PlaygroundSDK, defaultDeviceType: DeviceType, pollIntervalMs: number): ServerStatusResult;
11
12
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/playground-app",
3
- "version": "1.6.3-beta-20260403070857.0",
3
+ "version": "1.6.4",
4
4
  "description": "Reusable React shell for Midscene playground applications",
5
5
  "repository": "https://github.com/web-infra-dev/midscene",
6
6
  "homepage": "https://midscenejs.com/",
@@ -28,8 +28,8 @@
28
28
  "antd": "^5.21.6",
29
29
  "react-resizable-panels": "2.0.22",
30
30
  "socket.io-client": "4.8.1",
31
- "@midscene/playground": "1.6.3-beta-20260403070857.0",
32
- "@midscene/visualizer": "1.6.3-beta-20260403070857.0"
31
+ "@midscene/playground": "1.6.4",
32
+ "@midscene/visualizer": "1.6.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@rsbuild/plugin-less": "^1.5.0",
@@ -43,7 +43,8 @@
43
43
  "less": "^4.2.0",
44
44
  "react": "18.3.1",
45
45
  "react-dom": "18.3.1",
46
- "typescript": "^5.8.3"
46
+ "typescript": "^5.8.3",
47
+ "vitest": "3.0.5"
47
48
  },
48
49
  "sideEffects": [
49
50
  "**/*.css",
@@ -58,6 +59,8 @@
58
59
  "scripts": {
59
60
  "dev": "npm run build:watch",
60
61
  "build": "rslib build",
61
- "build:watch": "rslib build --watch --no-clean"
62
+ "build:watch": "rslib build --watch --no-clean",
63
+ "test": "vitest --run",
64
+ "test:watch": "vitest"
62
65
  }
63
66
  }