@aiscene/android 1.3.5 → 1.6.0-cache

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.
@@ -1,4 +1,3 @@
1
- import * as __rspack_external__ffmpeg_installer_ffmpeg_acfac5f1 from "@ffmpeg-installer/ffmpeg";
2
1
  import * as __rspack_external__midscene_shared_logger_b1dc2426 from "@midscene/shared/logger";
3
2
  import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
4
3
  import * as __rspack_external_node_module_ab9f2194 from "node:module";
@@ -34,24 +33,26 @@ var __webpack_modules__ = {
34
33
  return obj;
35
34
  }
36
35
  const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
36
+ const warnScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy', {
37
+ console: true
38
+ });
37
39
  const NAL_TYPE_IDR = 5;
38
40
  const NAL_TYPE_SPS = 7;
39
41
  const NAL_TYPE_PPS = 8;
40
42
  const NAL_TYPE_MASK = 0x1f;
41
- const START_CODE_4_BYTE = Buffer.from([
42
- 0x00,
43
- 0x00,
44
- 0x00,
45
- 0x01
46
- ]);
47
43
  const DEFAULT_MAX_SIZE = 0;
48
- const DEFAULT_VIDEO_BIT_RATE = 2000000;
44
+ const DEFAULT_VIDEO_BIT_RATE = 100000000;
45
+ const MAX_VIDEO_BIT_RATE = 100000000;
49
46
  const DEFAULT_IDLE_TIMEOUT_MS = 30000;
50
47
  const MAX_KEYFRAME_WAIT_MS = 5000;
51
48
  const FRESH_FRAME_TIMEOUT_MS = 300;
52
49
  const KEYFRAME_POLL_INTERVAL_MS = 200;
53
50
  const MAX_SCAN_BYTES = 1000;
54
51
  const CONNECTION_WAIT_MS = 1000;
52
+ const BUSY_LOOP_WINDOW_MS = 1000;
53
+ const BUSY_LOOP_MAX_READS = 500;
54
+ const BUSY_LOOP_COOLDOWN_MS = 50;
55
+ const BUSY_LOOP_WARN_INTERVAL_MS = 5000;
55
56
  const DEFAULT_SCRCPY_CONFIG = {
56
57
  enabled: false,
57
58
  maxSize: DEFAULT_MAX_SIZE,
@@ -91,21 +92,21 @@ var __webpack_modules__ = {
91
92
  try {
92
93
  this.isConnecting = true;
93
94
  debugScrcpy('Starting scrcpy connection...');
94
- const { AdbScrcpyClient, AdbScrcpyOptions2_1 } = await import("@yume-chan/adb-scrcpy");
95
+ const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
95
96
  const { ReadableStream } = await import("@yume-chan/stream-extra");
96
- const { ScrcpyOptions3_1, DefaultServerPath, h264SearchConfiguration } = await import("@yume-chan/scrcpy");
97
- this.h264SearchConfigFn = h264SearchConfiguration;
97
+ const { DefaultServerPath } = await import("@yume-chan/scrcpy");
98
98
  const serverBinPath = this.resolveServerBinPath();
99
99
  await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
100
- const scrcpyOptions = new ScrcpyOptions3_1({
100
+ const scrcpyOptions = new AdbScrcpyOptions3_3_3({
101
101
  audio: false,
102
102
  control: false,
103
103
  maxSize: this.options.maxSize,
104
104
  videoBitRate: this.options.videoBitRate,
105
- sendFrameMeta: false,
106
- videoCodecOptions: 'i-frame-interval=0'
105
+ maxFps: 10,
106
+ sendFrameMeta: true,
107
+ videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
107
108
  });
108
- this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, new AdbScrcpyOptions2_1(scrcpyOptions));
109
+ this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
109
110
  const videoStreamPromise = this.scrcpyClient.videoStream;
110
111
  if (!videoStreamPromise) throw new Error('Scrcpy client did not provide video stream');
111
112
  this.videoStream = await videoStreamPromise;
@@ -128,12 +129,13 @@ var __webpack_modules__ = {
128
129
  }
129
130
  }
130
131
  resolveServerBinPath() {
131
- const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(import.meta.url).resolve('@midscene/android/package.json');
132
+ const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(import.meta.url).resolve('@aiscene/android/package.json');
132
133
  return node_path__rspack_import_2["default"].join(node_path__rspack_import_2["default"].dirname(androidPkgJson), 'bin', 'scrcpy-server');
133
134
  }
134
135
  getFfmpegPath() {
135
136
  try {
136
- const ffmpegInstaller = __webpack_require__("@ffmpeg-installer/ffmpeg");
137
+ const dynamicRequire = (0, node_module__rspack_import_1.createRequire)(import.meta.url);
138
+ const ffmpegInstaller = dynamicRequire('@ffmpeg-installer/ffmpeg');
137
139
  debugScrcpy(`Using ffmpeg from npm package: ${ffmpegInstaller.path}`);
138
140
  return ffmpegInstaller.path;
139
141
  } catch (error) {
@@ -148,22 +150,47 @@ var __webpack_modules__ = {
148
150
  this.consumeFramesLoop(reader);
149
151
  }
150
152
  async consumeFramesLoop(reader) {
153
+ let readCount = 0;
154
+ let windowStart = Date.now();
155
+ let lastBusyWarn = 0;
156
+ let totalReads = 0;
151
157
  try {
152
158
  while(true){
153
159
  const { done, value } = await reader.read();
154
160
  if (done) break;
161
+ totalReads++;
162
+ readCount++;
163
+ const now = Date.now();
164
+ const elapsed = now - windowStart;
165
+ if (elapsed >= BUSY_LOOP_WINDOW_MS) {
166
+ const readsPerSec = readCount / elapsed * 1000;
167
+ if (readCount > BUSY_LOOP_MAX_READS) {
168
+ if (now - lastBusyWarn >= BUSY_LOOP_WARN_INTERVAL_MS) {
169
+ warnScrcpy(`[CPU-DIAG] Possible busy loop detected! ${readCount} reads in ${elapsed}ms (${readsPerSec.toFixed(0)} reads/sec). Total reads: ${totalReads}. Throttling with ${BUSY_LOOP_COOLDOWN_MS}ms delay.`);
170
+ lastBusyWarn = now;
171
+ }
172
+ await new Promise((resolve)=>setTimeout(resolve, BUSY_LOOP_COOLDOWN_MS));
173
+ } else debugScrcpy(`[CPU-DIAG] Frame loop stats: ${readCount} reads in ${elapsed}ms (${readsPerSec.toFixed(1)} reads/sec), total: ${totalReads}`);
174
+ readCount = 0;
175
+ windowStart = Date.now();
176
+ }
155
177
  this.processFrame(value);
156
178
  }
157
179
  } catch (error) {
158
- debugScrcpy(`Frame consumer error: ${error}`);
180
+ debugScrcpy(`Frame consumer error (total reads: ${totalReads}): ${error}`);
159
181
  await this.disconnect();
160
182
  }
183
+ debugScrcpy(`Frame consumer loop ended normally (total reads: ${totalReads})`);
161
184
  }
162
185
  processFrame(packet) {
186
+ if ('configuration' === packet.type) {
187
+ this.spsHeader = Buffer.from(packet.data);
188
+ debugScrcpy(`Received SPS/PPS configuration: ${this.spsHeader.length}B`);
189
+ return;
190
+ }
163
191
  const frameBuffer = Buffer.from(packet.data);
164
- const actualKeyFrame = detectH264KeyFrame(frameBuffer);
165
- if (actualKeyFrame && !this.spsHeader) this.extractSpsHeader(frameBuffer);
166
- if (actualKeyFrame && this.spsHeader) {
192
+ const isKeyFrame = detectH264KeyFrame(frameBuffer);
193
+ if (isKeyFrame && this.spsHeader) {
167
194
  this.lastRawKeyframe = frameBuffer;
168
195
  if (this.keyframeResolvers.length > 0) {
169
196
  const combined = Buffer.concat([
@@ -174,23 +201,7 @@ var __webpack_modules__ = {
174
201
  }
175
202
  }
176
203
  }
177
- extractSpsHeader(frameBuffer) {
178
- if (!this.h264SearchConfigFn) return;
179
- try {
180
- const config = this.h264SearchConfigFn(new Uint8Array(frameBuffer));
181
- if (!config.sequenceParameterSet || !config.pictureParameterSet) return;
182
- this.spsHeader = Buffer.concat([
183
- START_CODE_4_BYTE,
184
- Buffer.from(config.sequenceParameterSet),
185
- START_CODE_4_BYTE,
186
- Buffer.from(config.pictureParameterSet)
187
- ]);
188
- debugScrcpy(`Extracted SPS/PPS: SPS=${config.sequenceParameterSet.length}B, PPS=${config.pictureParameterSet.length}B, total=${this.spsHeader.length}B`);
189
- } catch (error) {
190
- debugScrcpy(`Failed to extract SPS/PPS from keyframe: ${error}`);
191
- }
192
- }
193
- async getScreenshotPng() {
204
+ async getScreenshotJpeg() {
194
205
  const perfStart = Date.now();
195
206
  const t1 = Date.now();
196
207
  await this.ensureConnected();
@@ -220,7 +231,7 @@ var __webpack_modules__ = {
220
231
  this.resetIdleTimer();
221
232
  debugScrcpy(`Decoding H.264 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
222
233
  const t4 = Date.now();
223
- const result = await this.decodeH264ToPng(keyframeBuffer);
234
+ const result = await this.decodeH264ToJpeg(keyframeBuffer);
224
235
  const decodeTime = Date.now() - t4;
225
236
  const totalTime = Date.now() - perfStart;
226
237
  debugScrcpy(`Performance: total=${totalTime}ms (connect=${connectTime}ms, spsWait=${spsWaitTime}ms, frameWait=${frameWaitTime}ms[${frameSource}], decode=${decodeTime}ms)`);
@@ -283,7 +294,7 @@ var __webpack_modules__ = {
283
294
  return false;
284
295
  }
285
296
  }
286
- async decodeH264ToPng(h264Buffer) {
297
+ async decodeH264ToJpeg(h264Buffer) {
287
298
  const { spawn } = await import("node:child_process");
288
299
  return new Promise((resolve, reject)=>{
289
300
  const ffmpegArgs = [
@@ -296,7 +307,9 @@ var __webpack_modules__ = {
296
307
  '-f',
297
308
  'image2pipe',
298
309
  '-vcodec',
299
- 'png',
310
+ 'mjpeg',
311
+ '-q:v',
312
+ '5',
300
313
  '-loglevel',
301
314
  'error',
302
315
  'pipe:1'
@@ -319,13 +332,13 @@ var __webpack_modules__ = {
319
332
  });
320
333
  ffmpeg.on('close', (code)=>{
321
334
  if (0 === code && chunks.length > 0) {
322
- const pngBuffer = Buffer.concat(chunks);
323
- debugScrcpy(`FFmpeg decode successful, PNG size: ${pngBuffer.length} bytes`);
324
- resolve(pngBuffer);
335
+ const jpegBuffer = Buffer.concat(chunks);
336
+ debugScrcpy(`FFmpeg decode successful, JPEG size: ${jpegBuffer.length} bytes`);
337
+ resolve(jpegBuffer);
325
338
  } else {
326
339
  const errorMsg = stderrOutput || `FFmpeg exited with code ${code}`;
327
340
  debugScrcpy(`FFmpeg decode failed: ${errorMsg}`);
328
- reject(new Error(`H.264 to PNG decode failed: ${errorMsg}`));
341
+ reject(new Error(`H.264 to JPEG decode failed: ${errorMsg}`));
329
342
  }
330
343
  });
331
344
  ffmpeg.on('error', (error)=>{
@@ -357,7 +370,6 @@ var __webpack_modules__ = {
357
370
  this.spsHeader = null;
358
371
  this.lastRawKeyframe = null;
359
372
  this.isInitialized = false;
360
- this.h264SearchConfigFn = null;
361
373
  this.keyframeResolvers = [];
362
374
  if (reader) try {
363
375
  reader.cancel();
@@ -385,20 +397,19 @@ var __webpack_modules__ = {
385
397
  _define_property(this, "keyframeResolvers", []);
386
398
  _define_property(this, "lastRawKeyframe", null);
387
399
  _define_property(this, "videoResolution", null);
388
- _define_property(this, "h264SearchConfigFn", null);
389
400
  _define_property(this, "streamReader", null);
390
401
  this.adb = adb;
402
+ const requestedBitRate = options.videoBitRate ?? DEFAULT_VIDEO_BIT_RATE;
403
+ const clampedBitRate = Math.min(requestedBitRate, MAX_VIDEO_BIT_RATE);
404
+ if (requestedBitRate > MAX_VIDEO_BIT_RATE) warnScrcpy(`videoBitRate ${requestedBitRate} exceeds maximum ${MAX_VIDEO_BIT_RATE}, clamped to ${clampedBitRate}`);
391
405
  this.options = {
392
406
  maxSize: options.maxSize ?? DEFAULT_MAX_SIZE,
393
- videoBitRate: options.videoBitRate ?? DEFAULT_VIDEO_BIT_RATE,
407
+ videoBitRate: clampedBitRate,
394
408
  idleTimeoutMs: options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS
395
409
  };
396
410
  }
397
411
  }
398
412
  },
399
- "@ffmpeg-installer/ffmpeg" (module) {
400
- module.exports = __rspack_external__ffmpeg_installer_ffmpeg_acfac5f1;
401
- },
402
413
  "@midscene/shared/logger" (module) {
403
414
  module.exports = __rspack_external__midscene_shared_logger_b1dc2426;
404
415
  },
@@ -562,18 +573,13 @@ class ScrcpyDeviceAdapter {
562
573
  resolveConfig(deviceInfo) {
563
574
  if (this.resolvedConfig) return this.resolvedConfig;
564
575
  const config = this.scrcpyConfig;
565
- let maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
566
- if (config?.maxSize === void 0) {
567
- const physicalMax = Math.max(deviceInfo.physicalWidth, deviceInfo.physicalHeight);
568
- const scale = this.screenshotResizeScale ?? 1 / deviceInfo.dpr;
569
- maxSize = Math.round(physicalMax * scale);
570
- debugAdapter(`Auto-calculated maxSize: ${maxSize} (physical=${physicalMax}, scale=${scale.toFixed(3)}, ${void 0 !== this.screenshotResizeScale ? 'from screenshotResizeScale' : 'from 1/dpr'})`);
571
- }
576
+ const maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
577
+ const videoBitRate = config?.videoBitRate ?? scrcpy_manager.o.videoBitRate;
572
578
  this.resolvedConfig = {
573
579
  enabled: this.isEnabled(),
574
580
  maxSize,
575
581
  idleTimeoutMs: config?.idleTimeoutMs ?? scrcpy_manager.o.idleTimeoutMs,
576
- videoBitRate: config?.videoBitRate ?? scrcpy_manager.o.videoBitRate
582
+ videoBitRate
577
583
  };
578
584
  return this.resolvedConfig;
579
585
  }
@@ -608,8 +614,8 @@ class ScrcpyDeviceAdapter {
608
614
  }
609
615
  async screenshotBase64(deviceInfo) {
610
616
  const manager = await this.ensureManager(deviceInfo);
611
- const screenshotBuffer = await manager.getScreenshotPng();
612
- return createImgBase64ByFormat('png', screenshotBuffer.toString('base64'));
617
+ const screenshotBuffer = await manager.getScreenshotJpeg();
618
+ return createImgBase64ByFormat('jpeg', screenshotBuffer.toString('base64'));
613
619
  }
614
620
  getResolution() {
615
621
  return this.manager?.getResolution() ?? null;
@@ -620,8 +626,7 @@ class ScrcpyDeviceAdapter {
620
626
  debugAdapter(`Using scrcpy resolution: ${resolution.width}x${resolution.height}`);
621
627
  return {
622
628
  width: resolution.width,
623
- height: resolution.height,
624
- dpr: deviceInfo.dpr
629
+ height: resolution.height
625
630
  };
626
631
  }
627
632
  getScalingRatio(physicalWidth) {
@@ -640,16 +645,14 @@ class ScrcpyDeviceAdapter {
640
645
  }
641
646
  this.resolvedConfig = null;
642
647
  }
643
- constructor(deviceId, scrcpyConfig, screenshotResizeScale){
648
+ constructor(deviceId, scrcpyConfig){
644
649
  _define_property(this, "deviceId", void 0);
645
650
  _define_property(this, "scrcpyConfig", void 0);
646
- _define_property(this, "screenshotResizeScale", void 0);
647
651
  _define_property(this, "manager", void 0);
648
652
  _define_property(this, "resolvedConfig", void 0);
649
653
  _define_property(this, "initFailed", void 0);
650
654
  this.deviceId = deviceId;
651
655
  this.scrcpyConfig = scrcpyConfig;
652
- this.screenshotResizeScale = screenshotResizeScale;
653
656
  this.manager = null;
654
657
  this.resolvedConfig = null;
655
658
  this.initFailed = false;
@@ -876,7 +879,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
876
879
  });
877
880
  }
878
881
  getScrcpyAdapter() {
879
- if (!this.scrcpyAdapter) this.scrcpyAdapter = new ScrcpyDeviceAdapter(this.deviceId, this.options?.scrcpyConfig, this.options?.screenshotResizeScale);
882
+ if (!this.scrcpyAdapter) this.scrcpyAdapter = new ScrcpyDeviceAdapter(this.deviceId, this.options?.scrcpyConfig);
880
883
  return this.scrcpyAdapter;
881
884
  }
882
885
  async getDevicePhysicalInfo() {
@@ -1122,8 +1125,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1122
1125
  const logicalHeight = Math.round(height * scale);
1123
1126
  return {
1124
1127
  width: logicalWidth,
1125
- height: logicalHeight,
1126
- dpr: this.devicePixelRatio
1128
+ height: logicalHeight
1127
1129
  };
1128
1130
  }
1129
1131
  async cacheFeatureForPoint(center, options) {
@@ -1409,7 +1411,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1409
1411
  async ensureYadb() {
1410
1412
  if (!this.yadbPushed) {
1411
1413
  const adb = await this.getAdb();
1412
- const androidPkgJson = (0, external_node_module_.createRequire)(import.meta.url).resolve('@midscene/android/package.json');
1414
+ const androidPkgJson = (0, external_node_module_.createRequire)(import.meta.url).resolve('@aiscene/android/package.json');
1413
1415
  const yadbBin = external_node_path_["default"].join(external_node_path_["default"].dirname(androidPkgJson), 'bin', 'yadb');
1414
1416
  await adb.push(yadbBin, '/data/local/tmp');
1415
1417
  this.yadbPushed = true;
@@ -1866,7 +1868,7 @@ class AndroidMCPServer extends BaseMCPServer {
1866
1868
  constructor(toolsManager){
1867
1869
  super({
1868
1870
  name: '@midscene/android-mcp',
1869
- version: __VERSION__,
1871
+ version: "1.6.0-cache",
1870
1872
  description: 'Control the Android device using natural language commands'
1871
1873
  }, toolsManager);
1872
1874
  }