@midscene/android 1.3.10-beta-20260210033532.0 → 1.3.10

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/dist/es/index.mjs CHANGED
@@ -32,12 +32,10 @@ var __webpack_modules__ = {
32
32
  return obj;
33
33
  }
34
34
  const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
35
- const H265_NAL_TYPE_IDR_W_RADL = 19;
36
- const H265_NAL_TYPE_IDR_N_LP = 20;
37
- const H265_NAL_TYPE_CRA = 21;
38
- const H265_NAL_TYPE_VPS = 32;
39
- const H265_NAL_TYPE_SPS = 33;
40
- const H265_NAL_TYPE_PPS = 34;
35
+ const NAL_TYPE_IDR = 5;
36
+ const NAL_TYPE_SPS = 7;
37
+ const NAL_TYPE_PPS = 8;
38
+ const NAL_TYPE_MASK = 0x1f;
41
39
  const START_CODE_4_BYTE = Buffer.from([
42
40
  0x00,
43
41
  0x00,
@@ -45,7 +43,7 @@ var __webpack_modules__ = {
45
43
  0x01
46
44
  ]);
47
45
  const DEFAULT_MAX_SIZE = 0;
48
- const DEFAULT_VIDEO_BIT_RATE = 8000000;
46
+ const DEFAULT_VIDEO_BIT_RATE = 2000000;
49
47
  const DEFAULT_IDLE_TIMEOUT_MS = 30000;
50
48
  const MAX_KEYFRAME_WAIT_MS = 5000;
51
49
  const FRESH_FRAME_TIMEOUT_MS = 300;
@@ -58,17 +56,17 @@ var __webpack_modules__ = {
58
56
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
59
57
  videoBitRate: DEFAULT_VIDEO_BIT_RATE
60
58
  };
61
- function isH265KeyFrameNalType(nalUnitType) {
62
- return nalUnitType === H265_NAL_TYPE_IDR_W_RADL || nalUnitType === H265_NAL_TYPE_IDR_N_LP || nalUnitType === H265_NAL_TYPE_CRA || nalUnitType === H265_NAL_TYPE_VPS || nalUnitType === H265_NAL_TYPE_SPS || nalUnitType === H265_NAL_TYPE_PPS;
59
+ function isKeyFrameNalType(nalUnitType) {
60
+ return nalUnitType === NAL_TYPE_IDR || nalUnitType === NAL_TYPE_SPS || nalUnitType === NAL_TYPE_PPS;
63
61
  }
64
- function detectH265KeyFrame(buffer) {
65
- const scanLimit = Math.min(buffer.length - 5, MAX_SCAN_BYTES);
62
+ function detectH264KeyFrame(buffer) {
63
+ const scanLimit = Math.min(buffer.length - 4, MAX_SCAN_BYTES);
66
64
  for(let i = 0; i < scanLimit; i++)if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x00 === buffer[i + 2] && 0x01 === buffer[i + 3]) {
67
- const nalUnitType = buffer[i + 4] >> 1 & 0x3f;
68
- if (isH265KeyFrameNalType(nalUnitType)) return true;
65
+ const nalUnitType = buffer[i + 4] & NAL_TYPE_MASK;
66
+ if (isKeyFrameNalType(nalUnitType)) return true;
69
67
  } else if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x01 === buffer[i + 2]) {
70
- const nalUnitType = buffer[i + 3] >> 1 & 0x3f;
71
- if (isH265KeyFrameNalType(nalUnitType)) return true;
68
+ const nalUnitType = buffer[i + 3] & NAL_TYPE_MASK;
69
+ if (isKeyFrameNalType(nalUnitType)) return true;
72
70
  }
73
71
  return false;
74
72
  }
@@ -93,8 +91,8 @@ var __webpack_modules__ = {
93
91
  debugScrcpy('Starting scrcpy connection...');
94
92
  const { AdbScrcpyClient, AdbScrcpyOptions2_1 } = await import("@yume-chan/adb-scrcpy");
95
93
  const { ReadableStream } = await import("@yume-chan/stream-extra");
96
- const { ScrcpyOptions3_1, DefaultServerPath, h265SearchConfiguration } = await import("@yume-chan/scrcpy");
97
- this.h265SearchConfigFn = h265SearchConfiguration;
94
+ const { ScrcpyOptions3_1, DefaultServerPath, h264SearchConfiguration } = await import("@yume-chan/scrcpy");
95
+ this.h264SearchConfigFn = h264SearchConfiguration;
98
96
  const serverBinPath = this.resolveServerBinPath();
99
97
  await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
100
98
  const scrcpyOptions = new ScrcpyOptions3_1({
@@ -103,7 +101,6 @@ var __webpack_modules__ = {
103
101
  maxSize: this.options.maxSize,
104
102
  videoBitRate: this.options.videoBitRate,
105
103
  sendFrameMeta: false,
106
- videoCodec: 'h265',
107
104
  videoCodecOptions: 'i-frame-interval=0'
108
105
  });
109
106
  this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, new AdbScrcpyOptions2_1(scrcpyOptions));
@@ -163,7 +160,7 @@ var __webpack_modules__ = {
163
160
  }
164
161
  processFrame(packet) {
165
162
  const frameBuffer = Buffer.from(packet.data);
166
- const actualKeyFrame = detectH265KeyFrame(frameBuffer);
163
+ const actualKeyFrame = detectH264KeyFrame(frameBuffer);
167
164
  if (actualKeyFrame && !this.spsHeader) this.extractSpsHeader(frameBuffer);
168
165
  if (actualKeyFrame && this.spsHeader) {
169
166
  this.lastRawKeyframe = frameBuffer;
@@ -177,21 +174,19 @@ var __webpack_modules__ = {
177
174
  }
178
175
  }
179
176
  extractSpsHeader(frameBuffer) {
180
- if (!this.h265SearchConfigFn) return;
177
+ if (!this.h264SearchConfigFn) return;
181
178
  try {
182
- const config = this.h265SearchConfigFn(new Uint8Array(frameBuffer));
183
- if (!config.videoParameterSet || !config.sequenceParameterSet || !config.pictureParameterSet) return;
179
+ const config = this.h264SearchConfigFn(new Uint8Array(frameBuffer));
180
+ if (!config.sequenceParameterSet || !config.pictureParameterSet) return;
184
181
  this.spsHeader = Buffer.concat([
185
182
  START_CODE_4_BYTE,
186
- Buffer.from(config.videoParameterSet.data),
183
+ Buffer.from(config.sequenceParameterSet),
187
184
  START_CODE_4_BYTE,
188
- Buffer.from(config.sequenceParameterSet.data),
189
- START_CODE_4_BYTE,
190
- Buffer.from(config.pictureParameterSet.data)
185
+ Buffer.from(config.pictureParameterSet)
191
186
  ]);
192
- debugScrcpy(`Extracted VPS/SPS/PPS: VPS=${config.videoParameterSet.data.length}B, SPS=${config.sequenceParameterSet.data.length}B, PPS=${config.pictureParameterSet.data.length}B, total=${this.spsHeader.length}B`);
187
+ debugScrcpy(`Extracted SPS/PPS: SPS=${config.sequenceParameterSet.length}B, PPS=${config.pictureParameterSet.length}B, total=${this.spsHeader.length}B`);
193
188
  } catch (error) {
194
- debugScrcpy(`Failed to extract VPS/SPS/PPS from keyframe: ${error}`);
189
+ debugScrcpy(`Failed to extract SPS/PPS from keyframe: ${error}`);
195
190
  }
196
191
  }
197
192
  async getScreenshotPng() {
@@ -222,9 +217,9 @@ var __webpack_modules__ = {
222
217
  }
223
218
  const frameWaitTime = Date.now() - t3;
224
219
  this.resetIdleTimer();
225
- debugScrcpy(`Decoding H.265 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
220
+ debugScrcpy(`Decoding H.264 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
226
221
  const t4 = Date.now();
227
- const result = await this.decodeH265ToPng(keyframeBuffer);
222
+ const result = await this.decodeH264ToPng(keyframeBuffer);
228
223
  const decodeTime = Date.now() - t4;
229
224
  const totalTime = Date.now() - perfStart;
230
225
  debugScrcpy(`Performance: total=${totalTime}ms (connect=${connectTime}ms, spsWait=${spsWaitTime}ms, frameWait=${frameWaitTime}ms[${frameSource}], decode=${decodeTime}ms)`);
@@ -266,7 +261,7 @@ var __webpack_modules__ = {
266
261
  const startTime = Date.now();
267
262
  while(!this.spsHeader && Date.now() - startTime < MAX_KEYFRAME_WAIT_MS){
268
263
  const elapsed = Date.now() - startTime;
269
- debugScrcpy(`Waiting for first keyframe (VPS/SPS/PPS header)... ${elapsed}ms`);
264
+ debugScrcpy(`Waiting for first keyframe (SPS/PPS header)... ${elapsed}ms`);
270
265
  await new Promise((resolve)=>setTimeout(resolve, KEYFRAME_POLL_INTERVAL_MS));
271
266
  }
272
267
  if (!this.spsHeader) throw new Error(`No keyframe received within ${MAX_KEYFRAME_WAIT_MS}ms. Device may have a long GOP interval or video encoding issues. Please retry.`);
@@ -287,12 +282,12 @@ var __webpack_modules__ = {
287
282
  return false;
288
283
  }
289
284
  }
290
- async decodeH265ToPng(hevcBuffer) {
285
+ async decodeH264ToPng(h264Buffer) {
291
286
  const { spawn } = await import("node:child_process");
292
287
  return new Promise((resolve, reject)=>{
293
288
  const ffmpegArgs = [
294
289
  '-f',
295
- 'hevc',
290
+ 'h264',
296
291
  '-i',
297
292
  'pipe:0',
298
293
  '-vframes',
@@ -329,13 +324,13 @@ var __webpack_modules__ = {
329
324
  } else {
330
325
  const errorMsg = stderrOutput || `FFmpeg exited with code ${code}`;
331
326
  debugScrcpy(`FFmpeg decode failed: ${errorMsg}`);
332
- reject(new Error(`H.265 to PNG decode failed: ${errorMsg}`));
327
+ reject(new Error(`H.264 to PNG decode failed: ${errorMsg}`));
333
328
  }
334
329
  });
335
330
  ffmpeg.on('error', (error)=>{
336
331
  reject(new Error(`Failed to spawn ffmpeg process: ${error.message}`));
337
332
  });
338
- ffmpeg.stdin.write(hevcBuffer);
333
+ ffmpeg.stdin.write(h264Buffer);
339
334
  ffmpeg.stdin.end();
340
335
  });
341
336
  }
@@ -361,7 +356,7 @@ var __webpack_modules__ = {
361
356
  this.spsHeader = null;
362
357
  this.lastRawKeyframe = null;
363
358
  this.isInitialized = false;
364
- this.h265SearchConfigFn = null;
359
+ this.h264SearchConfigFn = null;
365
360
  this.keyframeResolvers = [];
366
361
  if (reader) try {
367
362
  reader.cancel();
@@ -389,7 +384,7 @@ var __webpack_modules__ = {
389
384
  _define_property(this, "keyframeResolvers", []);
390
385
  _define_property(this, "lastRawKeyframe", null);
391
386
  _define_property(this, "videoResolution", null);
392
- _define_property(this, "h265SearchConfigFn", null);
387
+ _define_property(this, "h264SearchConfigFn", null);
393
388
  _define_property(this, "streamReader", null);
394
389
  this.adb = adb;
395
390
  this.options = {
@@ -467,20 +462,18 @@ class ScrcpyDeviceAdapter {
467
462
  resolveConfig(deviceInfo) {
468
463
  if (this.resolvedConfig) return this.resolvedConfig;
469
464
  const config = this.scrcpyConfig;
470
- const maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
471
- let videoBitRate = config?.videoBitRate ?? scrcpy_manager.o.videoBitRate;
472
- if (config?.videoBitRate === void 0) {
473
- const physicalPixels = deviceInfo.physicalWidth * deviceInfo.physicalHeight;
474
- const BASE_PIXELS = 2073600;
475
- const ratio = physicalPixels / BASE_PIXELS;
476
- videoBitRate = Math.round(Math.max(scrcpy_manager.o.videoBitRate, scrcpy_manager.o.videoBitRate * ratio));
477
- debugAdapter(`Auto-scaled videoBitRate: ${(videoBitRate / 1000000).toFixed(1)}Mbps (pixels=${physicalPixels}, ratio=${ratio.toFixed(2)})`);
465
+ let maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
466
+ if (config?.maxSize === void 0) {
467
+ const physicalMax = Math.max(deviceInfo.physicalWidth, deviceInfo.physicalHeight);
468
+ const scale = this.screenshotResizeScale ?? 1 / deviceInfo.dpr;
469
+ maxSize = Math.round(physicalMax * scale);
470
+ debugAdapter(`Auto-calculated maxSize: ${maxSize} (physical=${physicalMax}, scale=${scale.toFixed(3)}, ${void 0 !== this.screenshotResizeScale ? 'from screenshotResizeScale' : 'from 1/dpr'})`);
478
471
  }
479
472
  this.resolvedConfig = {
480
473
  enabled: this.isEnabled(),
481
474
  maxSize,
482
475
  idleTimeoutMs: config?.idleTimeoutMs ?? scrcpy_manager.o.idleTimeoutMs,
483
- videoBitRate
476
+ videoBitRate: config?.videoBitRate ?? scrcpy_manager.o.videoBitRate
484
477
  };
485
478
  return this.resolvedConfig;
486
479
  }
@@ -1011,6 +1004,17 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1011
1004
  }
1012
1005
  async size() {
1013
1006
  const deviceInfo = await this.getDevicePhysicalInfo();
1007
+ const adapter = this.getScrcpyAdapter();
1008
+ if (adapter.isEnabled()) {
1009
+ const scrcpySize = adapter.getSize(deviceInfo);
1010
+ if (scrcpySize) {
1011
+ const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1012
+ const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1013
+ const physicalWidth = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
1014
+ this.scalingRatio = adapter.getScalingRatio(physicalWidth) ?? this.scalingRatio;
1015
+ return scrcpySize;
1016
+ }
1017
+ }
1014
1018
  const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1015
1019
  const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1016
1020
  const width = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
@@ -33,12 +33,10 @@ var __webpack_modules__ = {
33
33
  return obj;
34
34
  }
35
35
  const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
36
- const H265_NAL_TYPE_IDR_W_RADL = 19;
37
- const H265_NAL_TYPE_IDR_N_LP = 20;
38
- const H265_NAL_TYPE_CRA = 21;
39
- const H265_NAL_TYPE_VPS = 32;
40
- const H265_NAL_TYPE_SPS = 33;
41
- const H265_NAL_TYPE_PPS = 34;
36
+ const NAL_TYPE_IDR = 5;
37
+ const NAL_TYPE_SPS = 7;
38
+ const NAL_TYPE_PPS = 8;
39
+ const NAL_TYPE_MASK = 0x1f;
42
40
  const START_CODE_4_BYTE = Buffer.from([
43
41
  0x00,
44
42
  0x00,
@@ -46,7 +44,7 @@ var __webpack_modules__ = {
46
44
  0x01
47
45
  ]);
48
46
  const DEFAULT_MAX_SIZE = 0;
49
- const DEFAULT_VIDEO_BIT_RATE = 8000000;
47
+ const DEFAULT_VIDEO_BIT_RATE = 2000000;
50
48
  const DEFAULT_IDLE_TIMEOUT_MS = 30000;
51
49
  const MAX_KEYFRAME_WAIT_MS = 5000;
52
50
  const FRESH_FRAME_TIMEOUT_MS = 300;
@@ -59,17 +57,17 @@ var __webpack_modules__ = {
59
57
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
60
58
  videoBitRate: DEFAULT_VIDEO_BIT_RATE
61
59
  };
62
- function isH265KeyFrameNalType(nalUnitType) {
63
- return nalUnitType === H265_NAL_TYPE_IDR_W_RADL || nalUnitType === H265_NAL_TYPE_IDR_N_LP || nalUnitType === H265_NAL_TYPE_CRA || nalUnitType === H265_NAL_TYPE_VPS || nalUnitType === H265_NAL_TYPE_SPS || nalUnitType === H265_NAL_TYPE_PPS;
60
+ function isKeyFrameNalType(nalUnitType) {
61
+ return nalUnitType === NAL_TYPE_IDR || nalUnitType === NAL_TYPE_SPS || nalUnitType === NAL_TYPE_PPS;
64
62
  }
65
- function detectH265KeyFrame(buffer) {
66
- const scanLimit = Math.min(buffer.length - 5, MAX_SCAN_BYTES);
63
+ function detectH264KeyFrame(buffer) {
64
+ const scanLimit = Math.min(buffer.length - 4, MAX_SCAN_BYTES);
67
65
  for(let i = 0; i < scanLimit; i++)if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x00 === buffer[i + 2] && 0x01 === buffer[i + 3]) {
68
- const nalUnitType = buffer[i + 4] >> 1 & 0x3f;
69
- if (isH265KeyFrameNalType(nalUnitType)) return true;
66
+ const nalUnitType = buffer[i + 4] & NAL_TYPE_MASK;
67
+ if (isKeyFrameNalType(nalUnitType)) return true;
70
68
  } else if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x01 === buffer[i + 2]) {
71
- const nalUnitType = buffer[i + 3] >> 1 & 0x3f;
72
- if (isH265KeyFrameNalType(nalUnitType)) return true;
69
+ const nalUnitType = buffer[i + 3] & NAL_TYPE_MASK;
70
+ if (isKeyFrameNalType(nalUnitType)) return true;
73
71
  }
74
72
  return false;
75
73
  }
@@ -94,8 +92,8 @@ var __webpack_modules__ = {
94
92
  debugScrcpy('Starting scrcpy connection...');
95
93
  const { AdbScrcpyClient, AdbScrcpyOptions2_1 } = await import("@yume-chan/adb-scrcpy");
96
94
  const { ReadableStream } = await import("@yume-chan/stream-extra");
97
- const { ScrcpyOptions3_1, DefaultServerPath, h265SearchConfiguration } = await import("@yume-chan/scrcpy");
98
- this.h265SearchConfigFn = h265SearchConfiguration;
95
+ const { ScrcpyOptions3_1, DefaultServerPath, h264SearchConfiguration } = await import("@yume-chan/scrcpy");
96
+ this.h264SearchConfigFn = h264SearchConfiguration;
99
97
  const serverBinPath = this.resolveServerBinPath();
100
98
  await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
101
99
  const scrcpyOptions = new ScrcpyOptions3_1({
@@ -104,7 +102,6 @@ var __webpack_modules__ = {
104
102
  maxSize: this.options.maxSize,
105
103
  videoBitRate: this.options.videoBitRate,
106
104
  sendFrameMeta: false,
107
- videoCodec: 'h265',
108
105
  videoCodecOptions: 'i-frame-interval=0'
109
106
  });
110
107
  this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, new AdbScrcpyOptions2_1(scrcpyOptions));
@@ -164,7 +161,7 @@ var __webpack_modules__ = {
164
161
  }
165
162
  processFrame(packet) {
166
163
  const frameBuffer = Buffer.from(packet.data);
167
- const actualKeyFrame = detectH265KeyFrame(frameBuffer);
164
+ const actualKeyFrame = detectH264KeyFrame(frameBuffer);
168
165
  if (actualKeyFrame && !this.spsHeader) this.extractSpsHeader(frameBuffer);
169
166
  if (actualKeyFrame && this.spsHeader) {
170
167
  this.lastRawKeyframe = frameBuffer;
@@ -178,21 +175,19 @@ var __webpack_modules__ = {
178
175
  }
179
176
  }
180
177
  extractSpsHeader(frameBuffer) {
181
- if (!this.h265SearchConfigFn) return;
178
+ if (!this.h264SearchConfigFn) return;
182
179
  try {
183
- const config = this.h265SearchConfigFn(new Uint8Array(frameBuffer));
184
- if (!config.videoParameterSet || !config.sequenceParameterSet || !config.pictureParameterSet) return;
180
+ const config = this.h264SearchConfigFn(new Uint8Array(frameBuffer));
181
+ if (!config.sequenceParameterSet || !config.pictureParameterSet) return;
185
182
  this.spsHeader = Buffer.concat([
186
183
  START_CODE_4_BYTE,
187
- Buffer.from(config.videoParameterSet.data),
184
+ Buffer.from(config.sequenceParameterSet),
188
185
  START_CODE_4_BYTE,
189
- Buffer.from(config.sequenceParameterSet.data),
190
- START_CODE_4_BYTE,
191
- Buffer.from(config.pictureParameterSet.data)
186
+ Buffer.from(config.pictureParameterSet)
192
187
  ]);
193
- debugScrcpy(`Extracted VPS/SPS/PPS: VPS=${config.videoParameterSet.data.length}B, SPS=${config.sequenceParameterSet.data.length}B, PPS=${config.pictureParameterSet.data.length}B, total=${this.spsHeader.length}B`);
188
+ debugScrcpy(`Extracted SPS/PPS: SPS=${config.sequenceParameterSet.length}B, PPS=${config.pictureParameterSet.length}B, total=${this.spsHeader.length}B`);
194
189
  } catch (error) {
195
- debugScrcpy(`Failed to extract VPS/SPS/PPS from keyframe: ${error}`);
190
+ debugScrcpy(`Failed to extract SPS/PPS from keyframe: ${error}`);
196
191
  }
197
192
  }
198
193
  async getScreenshotPng() {
@@ -223,9 +218,9 @@ var __webpack_modules__ = {
223
218
  }
224
219
  const frameWaitTime = Date.now() - t3;
225
220
  this.resetIdleTimer();
226
- debugScrcpy(`Decoding H.265 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
221
+ debugScrcpy(`Decoding H.264 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
227
222
  const t4 = Date.now();
228
- const result = await this.decodeH265ToPng(keyframeBuffer);
223
+ const result = await this.decodeH264ToPng(keyframeBuffer);
229
224
  const decodeTime = Date.now() - t4;
230
225
  const totalTime = Date.now() - perfStart;
231
226
  debugScrcpy(`Performance: total=${totalTime}ms (connect=${connectTime}ms, spsWait=${spsWaitTime}ms, frameWait=${frameWaitTime}ms[${frameSource}], decode=${decodeTime}ms)`);
@@ -267,7 +262,7 @@ var __webpack_modules__ = {
267
262
  const startTime = Date.now();
268
263
  while(!this.spsHeader && Date.now() - startTime < MAX_KEYFRAME_WAIT_MS){
269
264
  const elapsed = Date.now() - startTime;
270
- debugScrcpy(`Waiting for first keyframe (VPS/SPS/PPS header)... ${elapsed}ms`);
265
+ debugScrcpy(`Waiting for first keyframe (SPS/PPS header)... ${elapsed}ms`);
271
266
  await new Promise((resolve)=>setTimeout(resolve, KEYFRAME_POLL_INTERVAL_MS));
272
267
  }
273
268
  if (!this.spsHeader) throw new Error(`No keyframe received within ${MAX_KEYFRAME_WAIT_MS}ms. Device may have a long GOP interval or video encoding issues. Please retry.`);
@@ -288,12 +283,12 @@ var __webpack_modules__ = {
288
283
  return false;
289
284
  }
290
285
  }
291
- async decodeH265ToPng(hevcBuffer) {
286
+ async decodeH264ToPng(h264Buffer) {
292
287
  const { spawn } = await import("node:child_process");
293
288
  return new Promise((resolve, reject)=>{
294
289
  const ffmpegArgs = [
295
290
  '-f',
296
- 'hevc',
291
+ 'h264',
297
292
  '-i',
298
293
  'pipe:0',
299
294
  '-vframes',
@@ -330,13 +325,13 @@ var __webpack_modules__ = {
330
325
  } else {
331
326
  const errorMsg = stderrOutput || `FFmpeg exited with code ${code}`;
332
327
  debugScrcpy(`FFmpeg decode failed: ${errorMsg}`);
333
- reject(new Error(`H.265 to PNG decode failed: ${errorMsg}`));
328
+ reject(new Error(`H.264 to PNG decode failed: ${errorMsg}`));
334
329
  }
335
330
  });
336
331
  ffmpeg.on('error', (error)=>{
337
332
  reject(new Error(`Failed to spawn ffmpeg process: ${error.message}`));
338
333
  });
339
- ffmpeg.stdin.write(hevcBuffer);
334
+ ffmpeg.stdin.write(h264Buffer);
340
335
  ffmpeg.stdin.end();
341
336
  });
342
337
  }
@@ -362,7 +357,7 @@ var __webpack_modules__ = {
362
357
  this.spsHeader = null;
363
358
  this.lastRawKeyframe = null;
364
359
  this.isInitialized = false;
365
- this.h265SearchConfigFn = null;
360
+ this.h264SearchConfigFn = null;
366
361
  this.keyframeResolvers = [];
367
362
  if (reader) try {
368
363
  reader.cancel();
@@ -390,7 +385,7 @@ var __webpack_modules__ = {
390
385
  _define_property(this, "keyframeResolvers", []);
391
386
  _define_property(this, "lastRawKeyframe", null);
392
387
  _define_property(this, "videoResolution", null);
393
- _define_property(this, "h265SearchConfigFn", null);
388
+ _define_property(this, "h264SearchConfigFn", null);
394
389
  _define_property(this, "streamReader", null);
395
390
  this.adb = adb;
396
391
  this.options = {
@@ -564,20 +559,18 @@ class ScrcpyDeviceAdapter {
564
559
  resolveConfig(deviceInfo) {
565
560
  if (this.resolvedConfig) return this.resolvedConfig;
566
561
  const config = this.scrcpyConfig;
567
- const maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
568
- let videoBitRate = config?.videoBitRate ?? scrcpy_manager.o.videoBitRate;
569
- if (config?.videoBitRate === void 0) {
570
- const physicalPixels = deviceInfo.physicalWidth * deviceInfo.physicalHeight;
571
- const BASE_PIXELS = 2073600;
572
- const ratio = physicalPixels / BASE_PIXELS;
573
- videoBitRate = Math.round(Math.max(scrcpy_manager.o.videoBitRate, scrcpy_manager.o.videoBitRate * ratio));
574
- debugAdapter(`Auto-scaled videoBitRate: ${(videoBitRate / 1000000).toFixed(1)}Mbps (pixels=${physicalPixels}, ratio=${ratio.toFixed(2)})`);
562
+ let maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
563
+ if (config?.maxSize === void 0) {
564
+ const physicalMax = Math.max(deviceInfo.physicalWidth, deviceInfo.physicalHeight);
565
+ const scale = this.screenshotResizeScale ?? 1 / deviceInfo.dpr;
566
+ maxSize = Math.round(physicalMax * scale);
567
+ debugAdapter(`Auto-calculated maxSize: ${maxSize} (physical=${physicalMax}, scale=${scale.toFixed(3)}, ${void 0 !== this.screenshotResizeScale ? 'from screenshotResizeScale' : 'from 1/dpr'})`);
575
568
  }
576
569
  this.resolvedConfig = {
577
570
  enabled: this.isEnabled(),
578
571
  maxSize,
579
572
  idleTimeoutMs: config?.idleTimeoutMs ?? scrcpy_manager.o.idleTimeoutMs,
580
- videoBitRate
573
+ videoBitRate: config?.videoBitRate ?? scrcpy_manager.o.videoBitRate
581
574
  };
582
575
  return this.resolvedConfig;
583
576
  }
@@ -1108,6 +1101,17 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1108
1101
  }
1109
1102
  async size() {
1110
1103
  const deviceInfo = await this.getDevicePhysicalInfo();
1104
+ const adapter = this.getScrcpyAdapter();
1105
+ if (adapter.isEnabled()) {
1106
+ const scrcpySize = adapter.getSize(deviceInfo);
1107
+ if (scrcpySize) {
1108
+ const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1109
+ const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1110
+ const physicalWidth = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
1111
+ this.scalingRatio = adapter.getScalingRatio(physicalWidth) ?? this.scalingRatio;
1112
+ return scrcpySize;
1113
+ }
1114
+ }
1111
1115
  const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1112
1116
  const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1113
1117
  const width = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
package/dist/lib/index.js CHANGED
@@ -24,12 +24,10 @@ var __webpack_modules__ = {
24
24
  return obj;
25
25
  }
26
26
  const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
27
- const H265_NAL_TYPE_IDR_W_RADL = 19;
28
- const H265_NAL_TYPE_IDR_N_LP = 20;
29
- const H265_NAL_TYPE_CRA = 21;
30
- const H265_NAL_TYPE_VPS = 32;
31
- const H265_NAL_TYPE_SPS = 33;
32
- const H265_NAL_TYPE_PPS = 34;
27
+ const NAL_TYPE_IDR = 5;
28
+ const NAL_TYPE_SPS = 7;
29
+ const NAL_TYPE_PPS = 8;
30
+ const NAL_TYPE_MASK = 0x1f;
33
31
  const START_CODE_4_BYTE = Buffer.from([
34
32
  0x00,
35
33
  0x00,
@@ -37,7 +35,7 @@ var __webpack_modules__ = {
37
35
  0x01
38
36
  ]);
39
37
  const DEFAULT_MAX_SIZE = 0;
40
- const DEFAULT_VIDEO_BIT_RATE = 8000000;
38
+ const DEFAULT_VIDEO_BIT_RATE = 2000000;
41
39
  const DEFAULT_IDLE_TIMEOUT_MS = 30000;
42
40
  const MAX_KEYFRAME_WAIT_MS = 5000;
43
41
  const FRESH_FRAME_TIMEOUT_MS = 300;
@@ -50,17 +48,17 @@ var __webpack_modules__ = {
50
48
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
51
49
  videoBitRate: DEFAULT_VIDEO_BIT_RATE
52
50
  };
53
- function isH265KeyFrameNalType(nalUnitType) {
54
- return nalUnitType === H265_NAL_TYPE_IDR_W_RADL || nalUnitType === H265_NAL_TYPE_IDR_N_LP || nalUnitType === H265_NAL_TYPE_CRA || nalUnitType === H265_NAL_TYPE_VPS || nalUnitType === H265_NAL_TYPE_SPS || nalUnitType === H265_NAL_TYPE_PPS;
51
+ function isKeyFrameNalType(nalUnitType) {
52
+ return nalUnitType === NAL_TYPE_IDR || nalUnitType === NAL_TYPE_SPS || nalUnitType === NAL_TYPE_PPS;
55
53
  }
56
- function detectH265KeyFrame(buffer) {
57
- const scanLimit = Math.min(buffer.length - 5, MAX_SCAN_BYTES);
54
+ function detectH264KeyFrame(buffer) {
55
+ const scanLimit = Math.min(buffer.length - 4, MAX_SCAN_BYTES);
58
56
  for(let i = 0; i < scanLimit; i++)if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x00 === buffer[i + 2] && 0x01 === buffer[i + 3]) {
59
- const nalUnitType = buffer[i + 4] >> 1 & 0x3f;
60
- if (isH265KeyFrameNalType(nalUnitType)) return true;
57
+ const nalUnitType = buffer[i + 4] & NAL_TYPE_MASK;
58
+ if (isKeyFrameNalType(nalUnitType)) return true;
61
59
  } else if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x01 === buffer[i + 2]) {
62
- const nalUnitType = buffer[i + 3] >> 1 & 0x3f;
63
- if (isH265KeyFrameNalType(nalUnitType)) return true;
60
+ const nalUnitType = buffer[i + 3] & NAL_TYPE_MASK;
61
+ if (isKeyFrameNalType(nalUnitType)) return true;
64
62
  }
65
63
  return false;
66
64
  }
@@ -85,8 +83,8 @@ var __webpack_modules__ = {
85
83
  debugScrcpy('Starting scrcpy connection...');
86
84
  const { AdbScrcpyClient, AdbScrcpyOptions2_1 } = await import("@yume-chan/adb-scrcpy");
87
85
  const { ReadableStream } = await import("@yume-chan/stream-extra");
88
- const { ScrcpyOptions3_1, DefaultServerPath, h265SearchConfiguration } = await import("@yume-chan/scrcpy");
89
- this.h265SearchConfigFn = h265SearchConfiguration;
86
+ const { ScrcpyOptions3_1, DefaultServerPath, h264SearchConfiguration } = await import("@yume-chan/scrcpy");
87
+ this.h264SearchConfigFn = h264SearchConfiguration;
90
88
  const serverBinPath = this.resolveServerBinPath();
91
89
  await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
92
90
  const scrcpyOptions = new ScrcpyOptions3_1({
@@ -95,7 +93,6 @@ var __webpack_modules__ = {
95
93
  maxSize: this.options.maxSize,
96
94
  videoBitRate: this.options.videoBitRate,
97
95
  sendFrameMeta: false,
98
- videoCodec: 'h265',
99
96
  videoCodecOptions: 'i-frame-interval=0'
100
97
  });
101
98
  this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, new AdbScrcpyOptions2_1(scrcpyOptions));
@@ -155,7 +152,7 @@ var __webpack_modules__ = {
155
152
  }
156
153
  processFrame(packet) {
157
154
  const frameBuffer = Buffer.from(packet.data);
158
- const actualKeyFrame = detectH265KeyFrame(frameBuffer);
155
+ const actualKeyFrame = detectH264KeyFrame(frameBuffer);
159
156
  if (actualKeyFrame && !this.spsHeader) this.extractSpsHeader(frameBuffer);
160
157
  if (actualKeyFrame && this.spsHeader) {
161
158
  this.lastRawKeyframe = frameBuffer;
@@ -169,21 +166,19 @@ var __webpack_modules__ = {
169
166
  }
170
167
  }
171
168
  extractSpsHeader(frameBuffer) {
172
- if (!this.h265SearchConfigFn) return;
169
+ if (!this.h264SearchConfigFn) return;
173
170
  try {
174
- const config = this.h265SearchConfigFn(new Uint8Array(frameBuffer));
175
- if (!config.videoParameterSet || !config.sequenceParameterSet || !config.pictureParameterSet) return;
171
+ const config = this.h264SearchConfigFn(new Uint8Array(frameBuffer));
172
+ if (!config.sequenceParameterSet || !config.pictureParameterSet) return;
176
173
  this.spsHeader = Buffer.concat([
177
174
  START_CODE_4_BYTE,
178
- Buffer.from(config.videoParameterSet.data),
175
+ Buffer.from(config.sequenceParameterSet),
179
176
  START_CODE_4_BYTE,
180
- Buffer.from(config.sequenceParameterSet.data),
181
- START_CODE_4_BYTE,
182
- Buffer.from(config.pictureParameterSet.data)
177
+ Buffer.from(config.pictureParameterSet)
183
178
  ]);
184
- debugScrcpy(`Extracted VPS/SPS/PPS: VPS=${config.videoParameterSet.data.length}B, SPS=${config.sequenceParameterSet.data.length}B, PPS=${config.pictureParameterSet.data.length}B, total=${this.spsHeader.length}B`);
179
+ debugScrcpy(`Extracted SPS/PPS: SPS=${config.sequenceParameterSet.length}B, PPS=${config.pictureParameterSet.length}B, total=${this.spsHeader.length}B`);
185
180
  } catch (error) {
186
- debugScrcpy(`Failed to extract VPS/SPS/PPS from keyframe: ${error}`);
181
+ debugScrcpy(`Failed to extract SPS/PPS from keyframe: ${error}`);
187
182
  }
188
183
  }
189
184
  async getScreenshotPng() {
@@ -214,9 +209,9 @@ var __webpack_modules__ = {
214
209
  }
215
210
  const frameWaitTime = Date.now() - t3;
216
211
  this.resetIdleTimer();
217
- debugScrcpy(`Decoding H.265 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
212
+ debugScrcpy(`Decoding H.264 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
218
213
  const t4 = Date.now();
219
- const result = await this.decodeH265ToPng(keyframeBuffer);
214
+ const result = await this.decodeH264ToPng(keyframeBuffer);
220
215
  const decodeTime = Date.now() - t4;
221
216
  const totalTime = Date.now() - perfStart;
222
217
  debugScrcpy(`Performance: total=${totalTime}ms (connect=${connectTime}ms, spsWait=${spsWaitTime}ms, frameWait=${frameWaitTime}ms[${frameSource}], decode=${decodeTime}ms)`);
@@ -258,7 +253,7 @@ var __webpack_modules__ = {
258
253
  const startTime = Date.now();
259
254
  while(!this.spsHeader && Date.now() - startTime < MAX_KEYFRAME_WAIT_MS){
260
255
  const elapsed = Date.now() - startTime;
261
- debugScrcpy(`Waiting for first keyframe (VPS/SPS/PPS header)... ${elapsed}ms`);
256
+ debugScrcpy(`Waiting for first keyframe (SPS/PPS header)... ${elapsed}ms`);
262
257
  await new Promise((resolve)=>setTimeout(resolve, KEYFRAME_POLL_INTERVAL_MS));
263
258
  }
264
259
  if (!this.spsHeader) throw new Error(`No keyframe received within ${MAX_KEYFRAME_WAIT_MS}ms. Device may have a long GOP interval or video encoding issues. Please retry.`);
@@ -279,12 +274,12 @@ var __webpack_modules__ = {
279
274
  return false;
280
275
  }
281
276
  }
282
- async decodeH265ToPng(hevcBuffer) {
277
+ async decodeH264ToPng(h264Buffer) {
283
278
  const { spawn } = await import("node:child_process");
284
279
  return new Promise((resolve, reject)=>{
285
280
  const ffmpegArgs = [
286
281
  '-f',
287
- 'hevc',
282
+ 'h264',
288
283
  '-i',
289
284
  'pipe:0',
290
285
  '-vframes',
@@ -321,13 +316,13 @@ var __webpack_modules__ = {
321
316
  } else {
322
317
  const errorMsg = stderrOutput || `FFmpeg exited with code ${code}`;
323
318
  debugScrcpy(`FFmpeg decode failed: ${errorMsg}`);
324
- reject(new Error(`H.265 to PNG decode failed: ${errorMsg}`));
319
+ reject(new Error(`H.264 to PNG decode failed: ${errorMsg}`));
325
320
  }
326
321
  });
327
322
  ffmpeg.on('error', (error)=>{
328
323
  reject(new Error(`Failed to spawn ffmpeg process: ${error.message}`));
329
324
  });
330
- ffmpeg.stdin.write(hevcBuffer);
325
+ ffmpeg.stdin.write(h264Buffer);
331
326
  ffmpeg.stdin.end();
332
327
  });
333
328
  }
@@ -353,7 +348,7 @@ var __webpack_modules__ = {
353
348
  this.spsHeader = null;
354
349
  this.lastRawKeyframe = null;
355
350
  this.isInitialized = false;
356
- this.h265SearchConfigFn = null;
351
+ this.h264SearchConfigFn = null;
357
352
  this.keyframeResolvers = [];
358
353
  if (reader) try {
359
354
  reader.cancel();
@@ -381,7 +376,7 @@ var __webpack_modules__ = {
381
376
  _define_property(this, "keyframeResolvers", []);
382
377
  _define_property(this, "lastRawKeyframe", null);
383
378
  _define_property(this, "videoResolution", null);
384
- _define_property(this, "h265SearchConfigFn", null);
379
+ _define_property(this, "h264SearchConfigFn", null);
385
380
  _define_property(this, "streamReader", null);
386
381
  this.adb = adb;
387
382
  this.options = {
@@ -499,20 +494,18 @@ var __webpack_exports__ = {};
499
494
  resolveConfig(deviceInfo) {
500
495
  if (this.resolvedConfig) return this.resolvedConfig;
501
496
  const config = this.scrcpyConfig;
502
- const maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
503
- let videoBitRate = config?.videoBitRate ?? scrcpy_manager.o.videoBitRate;
504
- if (config?.videoBitRate === void 0) {
505
- const physicalPixels = deviceInfo.physicalWidth * deviceInfo.physicalHeight;
506
- const BASE_PIXELS = 2073600;
507
- const ratio = physicalPixels / BASE_PIXELS;
508
- videoBitRate = Math.round(Math.max(scrcpy_manager.o.videoBitRate, scrcpy_manager.o.videoBitRate * ratio));
509
- debugAdapter(`Auto-scaled videoBitRate: ${(videoBitRate / 1000000).toFixed(1)}Mbps (pixels=${physicalPixels}, ratio=${ratio.toFixed(2)})`);
497
+ let maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
498
+ if (config?.maxSize === void 0) {
499
+ const physicalMax = Math.max(deviceInfo.physicalWidth, deviceInfo.physicalHeight);
500
+ const scale = this.screenshotResizeScale ?? 1 / deviceInfo.dpr;
501
+ maxSize = Math.round(physicalMax * scale);
502
+ debugAdapter(`Auto-calculated maxSize: ${maxSize} (physical=${physicalMax}, scale=${scale.toFixed(3)}, ${void 0 !== this.screenshotResizeScale ? 'from screenshotResizeScale' : 'from 1/dpr'})`);
510
503
  }
511
504
  this.resolvedConfig = {
512
505
  enabled: this.isEnabled(),
513
506
  maxSize,
514
507
  idleTimeoutMs: config?.idleTimeoutMs ?? scrcpy_manager.o.idleTimeoutMs,
515
- videoBitRate
508
+ videoBitRate: config?.videoBitRate ?? scrcpy_manager.o.videoBitRate
516
509
  };
517
510
  return this.resolvedConfig;
518
511
  }
@@ -1043,6 +1036,17 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1043
1036
  }
1044
1037
  async size() {
1045
1038
  const deviceInfo = await this.getDevicePhysicalInfo();
1039
+ const adapter = this.getScrcpyAdapter();
1040
+ if (adapter.isEnabled()) {
1041
+ const scrcpySize = adapter.getSize(deviceInfo);
1042
+ if (scrcpySize) {
1043
+ const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1044
+ const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1045
+ const physicalWidth = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
1046
+ this.scalingRatio = adapter.getScalingRatio(physicalWidth) ?? this.scalingRatio;
1047
+ return scrcpySize;
1048
+ }
1049
+ }
1046
1050
  const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1047
1051
  const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1048
1052
  const width = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
@@ -24,12 +24,10 @@ var __webpack_modules__ = {
24
24
  return obj;
25
25
  }
26
26
  const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
27
- const H265_NAL_TYPE_IDR_W_RADL = 19;
28
- const H265_NAL_TYPE_IDR_N_LP = 20;
29
- const H265_NAL_TYPE_CRA = 21;
30
- const H265_NAL_TYPE_VPS = 32;
31
- const H265_NAL_TYPE_SPS = 33;
32
- const H265_NAL_TYPE_PPS = 34;
27
+ const NAL_TYPE_IDR = 5;
28
+ const NAL_TYPE_SPS = 7;
29
+ const NAL_TYPE_PPS = 8;
30
+ const NAL_TYPE_MASK = 0x1f;
33
31
  const START_CODE_4_BYTE = Buffer.from([
34
32
  0x00,
35
33
  0x00,
@@ -37,7 +35,7 @@ var __webpack_modules__ = {
37
35
  0x01
38
36
  ]);
39
37
  const DEFAULT_MAX_SIZE = 0;
40
- const DEFAULT_VIDEO_BIT_RATE = 8000000;
38
+ const DEFAULT_VIDEO_BIT_RATE = 2000000;
41
39
  const DEFAULT_IDLE_TIMEOUT_MS = 30000;
42
40
  const MAX_KEYFRAME_WAIT_MS = 5000;
43
41
  const FRESH_FRAME_TIMEOUT_MS = 300;
@@ -50,17 +48,17 @@ var __webpack_modules__ = {
50
48
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
51
49
  videoBitRate: DEFAULT_VIDEO_BIT_RATE
52
50
  };
53
- function isH265KeyFrameNalType(nalUnitType) {
54
- return nalUnitType === H265_NAL_TYPE_IDR_W_RADL || nalUnitType === H265_NAL_TYPE_IDR_N_LP || nalUnitType === H265_NAL_TYPE_CRA || nalUnitType === H265_NAL_TYPE_VPS || nalUnitType === H265_NAL_TYPE_SPS || nalUnitType === H265_NAL_TYPE_PPS;
51
+ function isKeyFrameNalType(nalUnitType) {
52
+ return nalUnitType === NAL_TYPE_IDR || nalUnitType === NAL_TYPE_SPS || nalUnitType === NAL_TYPE_PPS;
55
53
  }
56
- function detectH265KeyFrame(buffer) {
57
- const scanLimit = Math.min(buffer.length - 5, MAX_SCAN_BYTES);
54
+ function detectH264KeyFrame(buffer) {
55
+ const scanLimit = Math.min(buffer.length - 4, MAX_SCAN_BYTES);
58
56
  for(let i = 0; i < scanLimit; i++)if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x00 === buffer[i + 2] && 0x01 === buffer[i + 3]) {
59
- const nalUnitType = buffer[i + 4] >> 1 & 0x3f;
60
- if (isH265KeyFrameNalType(nalUnitType)) return true;
57
+ const nalUnitType = buffer[i + 4] & NAL_TYPE_MASK;
58
+ if (isKeyFrameNalType(nalUnitType)) return true;
61
59
  } else if (0x00 === buffer[i] && 0x00 === buffer[i + 1] && 0x01 === buffer[i + 2]) {
62
- const nalUnitType = buffer[i + 3] >> 1 & 0x3f;
63
- if (isH265KeyFrameNalType(nalUnitType)) return true;
60
+ const nalUnitType = buffer[i + 3] & NAL_TYPE_MASK;
61
+ if (isKeyFrameNalType(nalUnitType)) return true;
64
62
  }
65
63
  return false;
66
64
  }
@@ -85,8 +83,8 @@ var __webpack_modules__ = {
85
83
  debugScrcpy('Starting scrcpy connection...');
86
84
  const { AdbScrcpyClient, AdbScrcpyOptions2_1 } = await import("@yume-chan/adb-scrcpy");
87
85
  const { ReadableStream } = await import("@yume-chan/stream-extra");
88
- const { ScrcpyOptions3_1, DefaultServerPath, h265SearchConfiguration } = await import("@yume-chan/scrcpy");
89
- this.h265SearchConfigFn = h265SearchConfiguration;
86
+ const { ScrcpyOptions3_1, DefaultServerPath, h264SearchConfiguration } = await import("@yume-chan/scrcpy");
87
+ this.h264SearchConfigFn = h264SearchConfiguration;
90
88
  const serverBinPath = this.resolveServerBinPath();
91
89
  await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
92
90
  const scrcpyOptions = new ScrcpyOptions3_1({
@@ -95,7 +93,6 @@ var __webpack_modules__ = {
95
93
  maxSize: this.options.maxSize,
96
94
  videoBitRate: this.options.videoBitRate,
97
95
  sendFrameMeta: false,
98
- videoCodec: 'h265',
99
96
  videoCodecOptions: 'i-frame-interval=0'
100
97
  });
101
98
  this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, new AdbScrcpyOptions2_1(scrcpyOptions));
@@ -155,7 +152,7 @@ var __webpack_modules__ = {
155
152
  }
156
153
  processFrame(packet) {
157
154
  const frameBuffer = Buffer.from(packet.data);
158
- const actualKeyFrame = detectH265KeyFrame(frameBuffer);
155
+ const actualKeyFrame = detectH264KeyFrame(frameBuffer);
159
156
  if (actualKeyFrame && !this.spsHeader) this.extractSpsHeader(frameBuffer);
160
157
  if (actualKeyFrame && this.spsHeader) {
161
158
  this.lastRawKeyframe = frameBuffer;
@@ -169,21 +166,19 @@ var __webpack_modules__ = {
169
166
  }
170
167
  }
171
168
  extractSpsHeader(frameBuffer) {
172
- if (!this.h265SearchConfigFn) return;
169
+ if (!this.h264SearchConfigFn) return;
173
170
  try {
174
- const config = this.h265SearchConfigFn(new Uint8Array(frameBuffer));
175
- if (!config.videoParameterSet || !config.sequenceParameterSet || !config.pictureParameterSet) return;
171
+ const config = this.h264SearchConfigFn(new Uint8Array(frameBuffer));
172
+ if (!config.sequenceParameterSet || !config.pictureParameterSet) return;
176
173
  this.spsHeader = Buffer.concat([
177
174
  START_CODE_4_BYTE,
178
- Buffer.from(config.videoParameterSet.data),
175
+ Buffer.from(config.sequenceParameterSet),
179
176
  START_CODE_4_BYTE,
180
- Buffer.from(config.sequenceParameterSet.data),
181
- START_CODE_4_BYTE,
182
- Buffer.from(config.pictureParameterSet.data)
177
+ Buffer.from(config.pictureParameterSet)
183
178
  ]);
184
- debugScrcpy(`Extracted VPS/SPS/PPS: VPS=${config.videoParameterSet.data.length}B, SPS=${config.sequenceParameterSet.data.length}B, PPS=${config.pictureParameterSet.data.length}B, total=${this.spsHeader.length}B`);
179
+ debugScrcpy(`Extracted SPS/PPS: SPS=${config.sequenceParameterSet.length}B, PPS=${config.pictureParameterSet.length}B, total=${this.spsHeader.length}B`);
185
180
  } catch (error) {
186
- debugScrcpy(`Failed to extract VPS/SPS/PPS from keyframe: ${error}`);
181
+ debugScrcpy(`Failed to extract SPS/PPS from keyframe: ${error}`);
187
182
  }
188
183
  }
189
184
  async getScreenshotPng() {
@@ -214,9 +209,9 @@ var __webpack_modules__ = {
214
209
  }
215
210
  const frameWaitTime = Date.now() - t3;
216
211
  this.resetIdleTimer();
217
- debugScrcpy(`Decoding H.265 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
212
+ debugScrcpy(`Decoding H.264 stream: ${keyframeBuffer.length} bytes (${frameSource})`);
218
213
  const t4 = Date.now();
219
- const result = await this.decodeH265ToPng(keyframeBuffer);
214
+ const result = await this.decodeH264ToPng(keyframeBuffer);
220
215
  const decodeTime = Date.now() - t4;
221
216
  const totalTime = Date.now() - perfStart;
222
217
  debugScrcpy(`Performance: total=${totalTime}ms (connect=${connectTime}ms, spsWait=${spsWaitTime}ms, frameWait=${frameWaitTime}ms[${frameSource}], decode=${decodeTime}ms)`);
@@ -258,7 +253,7 @@ var __webpack_modules__ = {
258
253
  const startTime = Date.now();
259
254
  while(!this.spsHeader && Date.now() - startTime < MAX_KEYFRAME_WAIT_MS){
260
255
  const elapsed = Date.now() - startTime;
261
- debugScrcpy(`Waiting for first keyframe (VPS/SPS/PPS header)... ${elapsed}ms`);
256
+ debugScrcpy(`Waiting for first keyframe (SPS/PPS header)... ${elapsed}ms`);
262
257
  await new Promise((resolve)=>setTimeout(resolve, KEYFRAME_POLL_INTERVAL_MS));
263
258
  }
264
259
  if (!this.spsHeader) throw new Error(`No keyframe received within ${MAX_KEYFRAME_WAIT_MS}ms. Device may have a long GOP interval or video encoding issues. Please retry.`);
@@ -279,12 +274,12 @@ var __webpack_modules__ = {
279
274
  return false;
280
275
  }
281
276
  }
282
- async decodeH265ToPng(hevcBuffer) {
277
+ async decodeH264ToPng(h264Buffer) {
283
278
  const { spawn } = await import("node:child_process");
284
279
  return new Promise((resolve, reject)=>{
285
280
  const ffmpegArgs = [
286
281
  '-f',
287
- 'hevc',
282
+ 'h264',
288
283
  '-i',
289
284
  'pipe:0',
290
285
  '-vframes',
@@ -321,13 +316,13 @@ var __webpack_modules__ = {
321
316
  } else {
322
317
  const errorMsg = stderrOutput || `FFmpeg exited with code ${code}`;
323
318
  debugScrcpy(`FFmpeg decode failed: ${errorMsg}`);
324
- reject(new Error(`H.265 to PNG decode failed: ${errorMsg}`));
319
+ reject(new Error(`H.264 to PNG decode failed: ${errorMsg}`));
325
320
  }
326
321
  });
327
322
  ffmpeg.on('error', (error)=>{
328
323
  reject(new Error(`Failed to spawn ffmpeg process: ${error.message}`));
329
324
  });
330
- ffmpeg.stdin.write(hevcBuffer);
325
+ ffmpeg.stdin.write(h264Buffer);
331
326
  ffmpeg.stdin.end();
332
327
  });
333
328
  }
@@ -353,7 +348,7 @@ var __webpack_modules__ = {
353
348
  this.spsHeader = null;
354
349
  this.lastRawKeyframe = null;
355
350
  this.isInitialized = false;
356
- this.h265SearchConfigFn = null;
351
+ this.h264SearchConfigFn = null;
357
352
  this.keyframeResolvers = [];
358
353
  if (reader) try {
359
354
  reader.cancel();
@@ -381,7 +376,7 @@ var __webpack_modules__ = {
381
376
  _define_property(this, "keyframeResolvers", []);
382
377
  _define_property(this, "lastRawKeyframe", null);
383
378
  _define_property(this, "videoResolution", null);
384
- _define_property(this, "h265SearchConfigFn", null);
379
+ _define_property(this, "h264SearchConfigFn", null);
385
380
  _define_property(this, "streamReader", null);
386
381
  this.adb = adb;
387
382
  this.options = {
@@ -595,20 +590,18 @@ var __webpack_exports__ = {};
595
590
  resolveConfig(deviceInfo) {
596
591
  if (this.resolvedConfig) return this.resolvedConfig;
597
592
  const config = this.scrcpyConfig;
598
- const maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
599
- let videoBitRate = config?.videoBitRate ?? scrcpy_manager.o.videoBitRate;
600
- if (config?.videoBitRate === void 0) {
601
- const physicalPixels = deviceInfo.physicalWidth * deviceInfo.physicalHeight;
602
- const BASE_PIXELS = 2073600;
603
- const ratio = physicalPixels / BASE_PIXELS;
604
- videoBitRate = Math.round(Math.max(scrcpy_manager.o.videoBitRate, scrcpy_manager.o.videoBitRate * ratio));
605
- debugAdapter(`Auto-scaled videoBitRate: ${(videoBitRate / 1000000).toFixed(1)}Mbps (pixels=${physicalPixels}, ratio=${ratio.toFixed(2)})`);
593
+ let maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
594
+ if (config?.maxSize === void 0) {
595
+ const physicalMax = Math.max(deviceInfo.physicalWidth, deviceInfo.physicalHeight);
596
+ const scale = this.screenshotResizeScale ?? 1 / deviceInfo.dpr;
597
+ maxSize = Math.round(physicalMax * scale);
598
+ debugAdapter(`Auto-calculated maxSize: ${maxSize} (physical=${physicalMax}, scale=${scale.toFixed(3)}, ${void 0 !== this.screenshotResizeScale ? 'from screenshotResizeScale' : 'from 1/dpr'})`);
606
599
  }
607
600
  this.resolvedConfig = {
608
601
  enabled: this.isEnabled(),
609
602
  maxSize,
610
603
  idleTimeoutMs: config?.idleTimeoutMs ?? scrcpy_manager.o.idleTimeoutMs,
611
- videoBitRate
604
+ videoBitRate: config?.videoBitRate ?? scrcpy_manager.o.videoBitRate
612
605
  };
613
606
  return this.resolvedConfig;
614
607
  }
@@ -1139,6 +1132,17 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1139
1132
  }
1140
1133
  async size() {
1141
1134
  const deviceInfo = await this.getDevicePhysicalInfo();
1135
+ const adapter = this.getScrcpyAdapter();
1136
+ if (adapter.isEnabled()) {
1137
+ const scrcpySize = adapter.getSize(deviceInfo);
1138
+ if (scrcpySize) {
1139
+ const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1140
+ const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1141
+ const physicalWidth = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
1142
+ this.scalingRatio = adapter.getScalingRatio(physicalWidth) ?? this.scalingRatio;
1143
+ return scrcpySize;
1144
+ }
1145
+ }
1142
1146
  const isLandscape = 1 === deviceInfo.orientation || 3 === deviceInfo.orientation;
1143
1147
  const shouldSwap = true !== deviceInfo.isCurrentOrientation && isLandscape;
1144
1148
  const width = shouldSwap ? deviceInfo.physicalHeight : deviceInfo.physicalWidth;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/android",
3
- "version": "1.3.10-beta-20260210033532.0",
3
+ "version": "1.3.10",
4
4
  "description": "Android automation library for Midscene",
5
5
  "keywords": [
6
6
  "Android UI automation",
@@ -38,8 +38,8 @@
38
38
  "@yume-chan/stream-extra": "^1.0.0",
39
39
  "appium-adb": "12.12.1",
40
40
  "sharp": "^0.34.3",
41
- "@midscene/core": "1.3.10-beta-20260210033532.0",
42
- "@midscene/shared": "1.3.10-beta-20260210033532.0"
41
+ "@midscene/core": "1.3.10",
42
+ "@midscene/shared": "1.3.10"
43
43
  },
44
44
  "optionalDependencies": {
45
45
  "@ffmpeg-installer/ffmpeg": "^1.1.0"
@@ -53,7 +53,7 @@
53
53
  "tsx": "^4.19.2",
54
54
  "vitest": "3.0.5",
55
55
  "zod": "3.24.3",
56
- "@midscene/playground": "1.3.10-beta-20260210033532.0"
56
+ "@midscene/playground": "1.3.10"
57
57
  },
58
58
  "license": "MIT",
59
59
  "scripts": {