@midscene/android 1.12.0 → 1.12.1-beta-20260824081858.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/cli.mjs +262 -96
- package/dist/es/index.mjs +261 -95
- package/dist/lib/cli.js +261 -95
- package/dist/lib/index.js +260 -94
- package/dist/types/index.d.ts +61 -15
- package/package.json +6 -6
package/dist/lib/cli.js
CHANGED
|
@@ -17,7 +17,6 @@ var __webpack_modules__ = {
|
|
|
17
17
|
__webpack_require__.d(__webpack_exports__, {
|
|
18
18
|
GJ: ()=>isScrcpyFreshFrameUnavailableError,
|
|
19
19
|
ScrcpyScreenshotManager: ()=>ScrcpyScreenshotManager,
|
|
20
|
-
Xr: ()=>SCRCPY_VIDEO_BIT_RATE_NETWORK_HINT,
|
|
21
20
|
ov: ()=>DEFAULT_SCRCPY_CONFIG
|
|
22
21
|
});
|
|
23
22
|
var node_fs__rspack_import_0 = __webpack_require__("node:fs");
|
|
@@ -52,7 +51,6 @@ var __webpack_modules__ = {
|
|
|
52
51
|
const FRESH_FRAME_TIMEOUT_MS = 300;
|
|
53
52
|
const KEYFRAME_POLL_INTERVAL_MS = 200;
|
|
54
53
|
const MAX_SCAN_BYTES = 1000;
|
|
55
|
-
const CONNECTION_WAIT_MS = 1000;
|
|
56
54
|
const MAX_SERVER_OUTPUT_LINES = 100;
|
|
57
55
|
const SERVER_OUTPUT_DRAIN_TIMEOUT_MS = 500;
|
|
58
56
|
const MAX_FRAME_AGE_US = 500000n;
|
|
@@ -62,7 +60,12 @@ var __webpack_modules__ = {
|
|
|
62
60
|
'dumpsys',
|
|
63
61
|
'power'
|
|
64
62
|
];
|
|
65
|
-
const SCRCPY_VIDEO_BIT_RATE_NETWORK_HINT = 'The appropriate scrcpy video bitrate depends on network conditions. For constrained remote links,
|
|
63
|
+
const SCRCPY_VIDEO_BIT_RATE_NETWORK_HINT = 'The appropriate scrcpy video bitrate depends on network conditions. For constrained remote links, pass --scrcpy-video-bit-rate 4000000 in the Android CLI, or set scrcpyConfig.videoBitRate to 4_000_000 (4 Mbps) in SDK/YAML configuration. Lower it further if backlog persists.';
|
|
64
|
+
const CONSTRAINED_LINK_STARTING_VIDEO_BIT_RATE = 4000000;
|
|
65
|
+
function getScrcpyVideoBitRateNetworkHint(currentVideoBitRate) {
|
|
66
|
+
if (currentVideoBitRate <= CONSTRAINED_LINK_STARTING_VIDEO_BIT_RATE) return 'The current scrcpy video bitrate is already at or below 4 Mbps. Lowering it further is unlikely to help on a local USB connection; check whether the screen was static or the device encoder emitted no new frame.';
|
|
67
|
+
return SCRCPY_VIDEO_BIT_RATE_NETWORK_HINT;
|
|
68
|
+
}
|
|
66
69
|
const BUSY_LOOP_WINDOW_MS = 1000;
|
|
67
70
|
const BUSY_LOOP_MAX_READS = 500;
|
|
68
71
|
const BUSY_LOOP_COOLDOWN_MS = 50;
|
|
@@ -76,8 +79,9 @@ var __webpack_modules__ = {
|
|
|
76
79
|
const SCRCPY_FRESH_FRAME_UNAVAILABLE_ERROR_CODE = 'ERR_SCRCPY_FRESH_FRAME_UNAVAILABLE';
|
|
77
80
|
class ScrcpyFreshFrameUnavailableError extends Error {
|
|
78
81
|
constructor(message, options){
|
|
79
|
-
super(message, options), _define_property(this, "code", SCRCPY_FRESH_FRAME_UNAVAILABLE_ERROR_CODE);
|
|
82
|
+
super(message, options), _define_property(this, "code", SCRCPY_FRESH_FRAME_UNAVAILABLE_ERROR_CODE), _define_property(this, "diagnosticMessage", void 0);
|
|
80
83
|
this.name = 'ScrcpyFreshFrameUnavailableError';
|
|
84
|
+
this.diagnosticMessage = options?.diagnosticMessage;
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
function isScrcpyFreshFrameUnavailableError(error) {
|
|
@@ -110,26 +114,30 @@ var __webpack_modules__ = {
|
|
|
110
114
|
await this.ensureFfmpegAvailable();
|
|
111
115
|
}
|
|
112
116
|
async ensureConnected() {
|
|
117
|
+
if (this.disposed) throw new Error('Scrcpy manager has been disposed');
|
|
113
118
|
if (this.scrcpyClient && this.videoStream) {
|
|
114
119
|
debugScrcpy('Scrcpy already connected');
|
|
115
120
|
await this.ensureFrameClockCalibration();
|
|
116
121
|
this.resetIdleTimer();
|
|
117
122
|
return;
|
|
118
123
|
}
|
|
119
|
-
if (this.
|
|
120
|
-
debugScrcpy('Connection already in progress,
|
|
121
|
-
await
|
|
122
|
-
|
|
123
|
-
await this.ensureFrameClockCalibration();
|
|
124
|
-
this.resetIdleTimer();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
throw new Error('Scrcpy connection failed: another connection attempt did not complete in time');
|
|
124
|
+
if (this.connectionPromise) {
|
|
125
|
+
debugScrcpy('Connection already in progress, sharing the same attempt...');
|
|
126
|
+
await this.connectionPromise;
|
|
127
|
+
return;
|
|
128
128
|
}
|
|
129
|
+
const connectionPromise = this.connectScrcpy();
|
|
130
|
+
this.connectionPromise = connectionPromise;
|
|
131
|
+
try {
|
|
132
|
+
await connectionPromise;
|
|
133
|
+
} finally{
|
|
134
|
+
if (this.connectionPromise === connectionPromise) this.connectionPromise = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async connectScrcpy() {
|
|
129
138
|
const serverOutput = [];
|
|
130
139
|
let serverOutputTask = null;
|
|
131
140
|
try {
|
|
132
|
-
this.isConnecting = true;
|
|
133
141
|
debugScrcpy('Starting scrcpy connection...');
|
|
134
142
|
const { AdbScrcpyClient } = await import("@yume-chan/adb-scrcpy");
|
|
135
143
|
const { ReadableStream } = await import("@yume-chan/stream-extra");
|
|
@@ -149,6 +157,8 @@ var __webpack_modules__ = {
|
|
|
149
157
|
height
|
|
150
158
|
};
|
|
151
159
|
await this.ensureFrameClockCalibration();
|
|
160
|
+
this.streamBaselineFramePending = true;
|
|
161
|
+
this.streamBaselineFrameDeadlineAt = Date.now() + MAX_KEYFRAME_WAIT_MS;
|
|
152
162
|
this.startFrameConsumer();
|
|
153
163
|
this.resetIdleTimer();
|
|
154
164
|
this.isInitialized = true;
|
|
@@ -161,8 +171,6 @@ var __webpack_modules__ = {
|
|
|
161
171
|
new Promise((resolve)=>setTimeout(resolve, SERVER_OUTPUT_DRAIN_TIMEOUT_MS))
|
|
162
172
|
]);
|
|
163
173
|
throw this.createConnectionError(error, serverOutput);
|
|
164
|
-
} finally{
|
|
165
|
-
this.isConnecting = false;
|
|
166
174
|
}
|
|
167
175
|
}
|
|
168
176
|
async createScrcpyOptions() {
|
|
@@ -347,24 +355,30 @@ var __webpack_modules__ = {
|
|
|
347
355
|
if (this.deviceClockCalibrationPromise === calibrationPromise) this.deviceClockCalibrationPromise = null;
|
|
348
356
|
}
|
|
349
357
|
}
|
|
350
|
-
async setFreshnessBarrier(reason) {
|
|
358
|
+
async setFreshnessBarrier(reason, options = {}) {
|
|
359
|
+
const cachedFrame = this.getCachedKeyframeCandidate();
|
|
351
360
|
const generation = ++this.frameFreshnessBarrierGeneration;
|
|
352
361
|
this.frameFreshnessBarrierPending = true;
|
|
362
|
+
this.frameFreshnessBarrierAllowsOverAgeForNextCapture = false;
|
|
353
363
|
this.clearFrameCache();
|
|
354
364
|
try {
|
|
355
365
|
const calibration = this.deviceClockCalibration;
|
|
356
366
|
if (!calibration) throw new Error('Scrcpy frame clock is not calibrated for the current stream epoch');
|
|
357
|
-
const hostMonotonicUs = this.monotonicTimeUs();
|
|
367
|
+
const hostMonotonicUs = options.hostMonotonicUs ?? this.monotonicTimeUs();
|
|
358
368
|
const estimatedDeviceNowUs = this.estimateDeviceTimeUs(hostMonotonicUs, calibration);
|
|
359
369
|
const conservativeDeviceNowUs = estimatedDeviceNowUs + this.getCalibrationUncertaintyUs(calibration);
|
|
360
370
|
const barrierPtsUs = (conservativeDeviceNowUs / 1000n + 1n) * 1000n;
|
|
361
371
|
if (generation !== this.frameFreshnessBarrierGeneration) return this.frameFreshnessBarrierPtsUs ?? barrierPtsUs;
|
|
362
372
|
this.frameFreshnessBarrierPtsUs = null === this.frameFreshnessBarrierPtsUs || barrierPtsUs > this.frameFreshnessBarrierPtsUs ? barrierPtsUs : this.frameFreshnessBarrierPtsUs;
|
|
363
373
|
this.frameFreshnessBarrierReason = reason;
|
|
374
|
+
this.frameFreshnessBarrierAllowsOverAgeForNextCapture = options.allowOverAgeForNextCapture ?? false;
|
|
364
375
|
this.frameFreshnessBarrierPending = false;
|
|
365
376
|
this.frameFreshnessError = null;
|
|
366
377
|
this.lastFramePtsUs = null;
|
|
367
|
-
this.
|
|
378
|
+
if (cachedFrame?.ptsUs !== void 0 && cachedFrame.ptsUs >= this.frameFreshnessBarrierPtsUs) {
|
|
379
|
+
this.restoreFrameCache(cachedFrame);
|
|
380
|
+
debugScrcpy(`Preserved cached frame PTS ${cachedFrame.ptsUs}µs because it crosses the newly armed ${reason} barrier`);
|
|
381
|
+
} else this.clearFrameCache();
|
|
368
382
|
debugScrcpy(`Armed frame freshness barrier at PTS ${this.frameFreshnessBarrierPtsUs}µs (${reason}, projected from stream-epoch clock anchor, uncertainty<=${Number(this.getCalibrationUncertaintyUs(calibration)) / 1000}ms)`);
|
|
369
383
|
return this.frameFreshnessBarrierPtsUs;
|
|
370
384
|
} catch (error) {
|
|
@@ -385,6 +399,7 @@ var __webpack_modules__ = {
|
|
|
385
399
|
this.frameFreshnessBarrierPending = true;
|
|
386
400
|
this.frameFreshnessBarrierPtsUs = null;
|
|
387
401
|
this.frameFreshnessBarrierReason = null;
|
|
402
|
+
this.frameFreshnessBarrierAllowsOverAgeForNextCapture = false;
|
|
388
403
|
this.deviceClockCalibration = null;
|
|
389
404
|
this.clearFrameCache();
|
|
390
405
|
this.warnFrameFreshness();
|
|
@@ -406,7 +421,7 @@ var __webpack_modules__ = {
|
|
|
406
421
|
const behindBarrierUs = this.frameFreshnessBarrierPtsUs - packetPtsUs;
|
|
407
422
|
this.frameFreshnessError = new Error(`Scrcpy frame predates the ${this.frameFreshnessBarrierReason ?? 'active'} freshness barrier by ${Number(behindBarrierUs) / 1000}ms; refusing to use it`);
|
|
408
423
|
this.clearFrameCache();
|
|
409
|
-
this.
|
|
424
|
+
debugScrcpy(this.frameFreshnessError.message);
|
|
410
425
|
return false;
|
|
411
426
|
}
|
|
412
427
|
estimateFrameAgeUs(packetPtsUs, hostMonotonicUs = this.monotonicTimeUs()) {
|
|
@@ -456,13 +471,18 @@ var __webpack_modules__ = {
|
|
|
456
471
|
this.lastFrameFreshnessWarningAt = now;
|
|
457
472
|
}
|
|
458
473
|
}
|
|
459
|
-
|
|
460
|
-
const now = Date.now();
|
|
461
|
-
if (now - this.lastTransportBacklogWarningAt < TRANSPORT_BACKLOG_WARN_INTERVAL_MS) return;
|
|
474
|
+
transportBacklogMessage(error) {
|
|
462
475
|
const cause = this.frameFreshnessError ?? error;
|
|
463
476
|
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
477
|
+
if (this.streamBaselineFramePending && null === this.frameFreshnessBarrierPtsUs) return `The new scrcpy stream did not produce a usable baseline frame within its ${MAX_KEYFRAME_WAIT_MS}ms startup window; closing the stream epoch and falling back to ADB screenshot. This is a stream startup or encoder-readiness failure, not evidence that the configured video bitrate is too high.\nError: ${causeMessage}`;
|
|
464
478
|
const currentBitRateMbps = this.options.videoBitRate / 1000000;
|
|
465
|
-
|
|
479
|
+
const networkHint = getScrcpyVideoBitRateNetworkHint(this.options.videoBitRate);
|
|
480
|
+
return `No usable scrcpy frame crossed the active freshness target within ${FRESH_FRAME_TIMEOUT_MS}ms; closing the stale stream epoch and falling back to ADB screenshot. This may indicate transport backlog or a static screen that emitted no new frame. ${networkHint} Current videoBitRate: ${this.options.videoBitRate} bps (${currentBitRateMbps} Mbps).\nError: ${causeMessage}`;
|
|
481
|
+
}
|
|
482
|
+
warnTransportBacklog(error) {
|
|
483
|
+
const now = Date.now();
|
|
484
|
+
if (now - this.lastTransportBacklogWarningAt < TRANSPORT_BACKLOG_WARN_INTERVAL_MS) return;
|
|
485
|
+
warnScrcpy(this.transportBacklogMessage(error));
|
|
466
486
|
this.lastTransportBacklogWarningAt = now;
|
|
467
487
|
}
|
|
468
488
|
estimateFrameTiming(packetPtsUs, receivedAtUs) {
|
|
@@ -484,12 +504,21 @@ var __webpack_modules__ = {
|
|
|
484
504
|
this.lastRawKeyframePtsUs = void 0;
|
|
485
505
|
this.lastRawKeyframeEstimatedAgeMs = void 0;
|
|
486
506
|
}
|
|
507
|
+
restoreFrameCache(frame) {
|
|
508
|
+
this.lastRawKeyframe = frame.data;
|
|
509
|
+
this.lastRawKeyframeAt = frame.capturedAt;
|
|
510
|
+
this.lastRawKeyframePtsUs = frame.ptsUs;
|
|
511
|
+
this.lastRawKeyframeEstimatedAgeMs = frame.estimatedAgeMs;
|
|
512
|
+
}
|
|
487
513
|
monotonicTimeUs() {
|
|
488
514
|
return process.hrtime.bigint() / 1000n;
|
|
489
515
|
}
|
|
490
516
|
resetFrameFreshnessState() {
|
|
491
517
|
this.frameFreshnessBarrierPtsUs = null;
|
|
492
518
|
this.frameFreshnessBarrierReason = null;
|
|
519
|
+
this.frameFreshnessBarrierAllowsOverAgeForNextCapture = false;
|
|
520
|
+
this.streamBaselineFramePending = false;
|
|
521
|
+
this.streamBaselineFrameDeadlineAt = 0;
|
|
493
522
|
this.frameFreshnessBarrierPending = false;
|
|
494
523
|
this.frameFreshnessBarrierGeneration = 0;
|
|
495
524
|
this.deviceClockCalibration = null;
|
|
@@ -521,35 +550,52 @@ var __webpack_modules__ = {
|
|
|
521
550
|
capturedAt: this.lastRawKeyframeAt
|
|
522
551
|
};
|
|
523
552
|
}
|
|
553
|
+
canReuseFrameAcrossActionWait(frame) {
|
|
554
|
+
return this.frameFreshnessBarrierAllowsOverAgeForNextCapture && null !== this.frameFreshnessBarrierPtsUs && void 0 !== frame.ptsUs && frame.ptsUs >= this.frameFreshnessBarrierPtsUs;
|
|
555
|
+
}
|
|
556
|
+
canUseStreamBaseline(frame) {
|
|
557
|
+
return this.streamBaselineFramePending && !this.frameFreshnessBarrierPending && null === this.frameFreshnessBarrierPtsUs && Date.now() <= this.streamBaselineFrameDeadlineAt && void 0 !== frame.ptsUs;
|
|
558
|
+
}
|
|
559
|
+
consumeActionFreshnessBarrier(frame) {
|
|
560
|
+
if (!this.canReuseFrameAcrossActionWait(frame)) return;
|
|
561
|
+
this.frameFreshnessBarrierPtsUs = null;
|
|
562
|
+
this.frameFreshnessBarrierReason = null;
|
|
563
|
+
this.frameFreshnessBarrierAllowsOverAgeForNextCapture = false;
|
|
564
|
+
this.frameFreshnessError = null;
|
|
565
|
+
}
|
|
524
566
|
async decodeRawKeyframeToJpeg(frame) {
|
|
525
567
|
return this.decodeH264ToJpeg(Buffer.concat([
|
|
526
568
|
frame.header,
|
|
527
569
|
frame.data
|
|
528
570
|
]));
|
|
529
571
|
}
|
|
530
|
-
async waitForUsableKeyframe(timeoutMs) {
|
|
531
|
-
const deadline = Date.now() + timeoutMs;
|
|
532
|
-
let candidate = this.getCachedKeyframeCandidate();
|
|
533
|
-
while(true){
|
|
534
|
-
if (candidate && this.isFrameAgeAcceptable(candidate.ptsUs)) return candidate;
|
|
535
|
-
const remainingMs = deadline - Date.now();
|
|
536
|
-
if (remainingMs <= 0) throw new Error(`No fresh keyframe received within ${timeoutMs}ms`);
|
|
537
|
-
candidate = await this.waitForNextKeyframe(remainingMs);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
572
|
async prepareFreshFrame() {
|
|
541
573
|
await this.ensureConnected();
|
|
542
574
|
await this.ensureFrameClockCalibration();
|
|
543
575
|
await this.waitForKeyframe();
|
|
544
|
-
await this.
|
|
576
|
+
await this.waitForPlanningFrame();
|
|
545
577
|
}
|
|
546
578
|
async waitForPlanningFrame() {
|
|
547
579
|
let planningBarrierArmed = false;
|
|
548
|
-
|
|
580
|
+
const planningStartedAt = Date.now();
|
|
581
|
+
const startupWindowRemainingMs = this.streamBaselineFramePending ? this.streamBaselineFrameDeadlineAt - planningStartedAt : 0;
|
|
582
|
+
if (this.streamBaselineFramePending && startupWindowRemainingMs <= 0) {
|
|
583
|
+
this.streamBaselineFramePending = false;
|
|
584
|
+
this.streamBaselineFrameDeadlineAt = 0;
|
|
585
|
+
}
|
|
586
|
+
let deadline = planningStartedAt + (startupWindowRemainingMs > 0 ? startupWindowRemainingMs : FRESH_FRAME_TIMEOUT_MS);
|
|
549
587
|
let candidate = this.getCachedKeyframeCandidate();
|
|
550
588
|
while(true){
|
|
551
589
|
if (candidate) {
|
|
552
590
|
if (void 0 === candidate.ptsUs) throw new Error('Scrcpy frame has no PTS metadata; cannot prove planning freshness');
|
|
591
|
+
if (this.canReuseFrameAcrossActionWait(candidate)) {
|
|
592
|
+
debugScrcpy(`Using frame PTS ${candidate.ptsUs}µs that crossed the active input-action barrier; preserving the settled frame across wait-after-action`);
|
|
593
|
+
return candidate;
|
|
594
|
+
}
|
|
595
|
+
if (this.canUseStreamBaseline(candidate)) {
|
|
596
|
+
debugScrcpy(`Using frame PTS ${candidate.ptsUs}µs as the first planning baseline for the new scrcpy stream epoch`);
|
|
597
|
+
return candidate;
|
|
598
|
+
}
|
|
553
599
|
const age = this.estimateFrameAge(candidate.ptsUs);
|
|
554
600
|
if (null === age) throw new Error('Scrcpy frame clock is not calibrated; cannot prove planning freshness');
|
|
555
601
|
if (age.upperBoundUs <= MAX_FRAME_AGE_US) return candidate;
|
|
@@ -567,11 +613,12 @@ var __webpack_modules__ = {
|
|
|
567
613
|
}
|
|
568
614
|
}
|
|
569
615
|
async closeStaleStreamAndCreateFallbackError(error) {
|
|
570
|
-
this.
|
|
616
|
+
const diagnosticMessage = this.transportBacklogMessage(error);
|
|
571
617
|
const causeMessage = error instanceof Error ? error.message : String(error);
|
|
572
618
|
await this.disconnect();
|
|
573
|
-
return new ScrcpyFreshFrameUnavailableError(`Unable to obtain a fresh scrcpy frame; the stale stream epoch was closed so the caller can use ADB screenshot fallback. ${causeMessage}`, {
|
|
574
|
-
cause: error
|
|
619
|
+
return new ScrcpyFreshFrameUnavailableError(`Unable to obtain a fresh scrcpy frame; the stale stream epoch was closed so the caller can restart it or use ADB screenshot fallback. ${causeMessage}`, {
|
|
620
|
+
cause: error,
|
|
621
|
+
diagnosticMessage
|
|
575
622
|
});
|
|
576
623
|
}
|
|
577
624
|
async getScreenshotJpeg() {
|
|
@@ -598,6 +645,9 @@ var __webpack_modules__ = {
|
|
|
598
645
|
const t5 = Date.now();
|
|
599
646
|
const result = await this.decodeH264ToJpeg(keyframeBuffer);
|
|
600
647
|
const decodeTime = Date.now() - t5;
|
|
648
|
+
this.consumeActionFreshnessBarrier(frame);
|
|
649
|
+
this.streamBaselineFramePending = false;
|
|
650
|
+
this.streamBaselineFrameDeadlineAt = 0;
|
|
601
651
|
const totalTime = Date.now() - perfStart;
|
|
602
652
|
debugScrcpy(`Performance: total=${totalTime}ms (connect=${connectTime}ms, frameWait=${frameWaitTime}ms, decode=${decodeTime}ms)`);
|
|
603
653
|
return result;
|
|
@@ -754,6 +804,13 @@ var __webpack_modules__ = {
|
|
|
754
804
|
}
|
|
755
805
|
debugScrcpy('Scrcpy disconnected');
|
|
756
806
|
}
|
|
807
|
+
async dispose() {
|
|
808
|
+
if (this.disposed) return;
|
|
809
|
+
this.disposed = true;
|
|
810
|
+
if (this.connectionPromise) await this.connectionPromise.catch(()=>{});
|
|
811
|
+
await this.disconnect();
|
|
812
|
+
await this.adb.close();
|
|
813
|
+
}
|
|
757
814
|
isConnected() {
|
|
758
815
|
return this.isInitialized && null !== this.scrcpyClient;
|
|
759
816
|
}
|
|
@@ -763,7 +820,8 @@ var __webpack_modules__ = {
|
|
|
763
820
|
_define_property(this, "videoStream", null);
|
|
764
821
|
_define_property(this, "spsHeader", null);
|
|
765
822
|
_define_property(this, "idleTimer", null);
|
|
766
|
-
_define_property(this, "
|
|
823
|
+
_define_property(this, "connectionPromise", null);
|
|
824
|
+
_define_property(this, "disposed", false);
|
|
767
825
|
_define_property(this, "isInitialized", false);
|
|
768
826
|
_define_property(this, "options", void 0);
|
|
769
827
|
_define_property(this, "ffmpegAvailable", null);
|
|
@@ -777,6 +835,9 @@ var __webpack_modules__ = {
|
|
|
777
835
|
_define_property(this, "streamReader", null);
|
|
778
836
|
_define_property(this, "frameFreshnessBarrierPtsUs", null);
|
|
779
837
|
_define_property(this, "frameFreshnessBarrierReason", null);
|
|
838
|
+
_define_property(this, "frameFreshnessBarrierAllowsOverAgeForNextCapture", false);
|
|
839
|
+
_define_property(this, "streamBaselineFramePending", false);
|
|
840
|
+
_define_property(this, "streamBaselineFrameDeadlineAt", 0);
|
|
780
841
|
_define_property(this, "frameFreshnessBarrierPending", false);
|
|
781
842
|
_define_property(this, "frameFreshnessBarrierGeneration", 0);
|
|
782
843
|
_define_property(this, "deviceClockCalibration", null);
|
|
@@ -1039,6 +1100,9 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1039
1100
|
return obj;
|
|
1040
1101
|
}
|
|
1041
1102
|
const debugAdapter = (0, logger_.getDebug)('android:scrcpy-adapter');
|
|
1103
|
+
const warnAdapter = (0, logger_.getDebug)('android:scrcpy-adapter', {
|
|
1104
|
+
console: true
|
|
1105
|
+
});
|
|
1042
1106
|
const SCRCPY_RETRY_COOLDOWN_MS = 5000;
|
|
1043
1107
|
const DEFAULT_ADB_SERVER_ENDPOINT = {
|
|
1044
1108
|
host: '127.0.0.1',
|
|
@@ -1092,10 +1156,11 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1092
1156
|
if (null === this.retryAfter || Date.now() >= this.retryAfter) return;
|
|
1093
1157
|
throw new Error(`scrcpy retry is cooling down until ${new Date(this.retryAfter).toISOString()}. Last error: ${this.lastError}`);
|
|
1094
1158
|
}
|
|
1095
|
-
resolveConfig(
|
|
1159
|
+
resolveConfig(_deviceInfo) {
|
|
1096
1160
|
if (this.resolvedConfig) return this.resolvedConfig;
|
|
1097
1161
|
const config = this.scrcpyConfig;
|
|
1098
1162
|
const maxSize = config?.maxSize ?? scrcpy_manager.ov.maxSize;
|
|
1163
|
+
if (!Number.isInteger(maxSize) || maxSize < 0) throw new Error(`Invalid scrcpyConfig.maxSize: expected a non-negative integer, received ${maxSize}`);
|
|
1099
1164
|
const videoBitRate = config?.videoBitRate ?? scrcpy_manager.ov.videoBitRate;
|
|
1100
1165
|
this.resolvedConfig = {
|
|
1101
1166
|
enabled: this.isConfigured(),
|
|
@@ -1105,31 +1170,68 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1105
1170
|
};
|
|
1106
1171
|
return this.resolvedConfig;
|
|
1107
1172
|
}
|
|
1173
|
+
async prepareFallbackScreenshot(screenshotBase64) {
|
|
1174
|
+
if (!this.isConfigured()) return screenshotBase64;
|
|
1175
|
+
const { maxSize } = this.resolveConfig();
|
|
1176
|
+
if (0 === maxSize) return screenshotBase64;
|
|
1177
|
+
const sourceSize = await (0, img_namespaceObject.imageInfoOfBase64)(screenshotBase64);
|
|
1178
|
+
const largestDimension = Math.max(sourceSize.width, sourceSize.height);
|
|
1179
|
+
if (largestDimension <= maxSize) return screenshotBase64;
|
|
1180
|
+
const scale = maxSize / largestDimension;
|
|
1181
|
+
const targetSize = {
|
|
1182
|
+
width: Math.max(1, Math.round(sourceSize.width * scale)),
|
|
1183
|
+
height: Math.max(1, Math.round(sourceSize.height * scale))
|
|
1184
|
+
};
|
|
1185
|
+
debugAdapter(`Applying scrcpy maxSize to fallback screenshot: ${sourceSize.width}x${sourceSize.height} -> ${targetSize.width}x${targetSize.height}`);
|
|
1186
|
+
return (0, img_namespaceObject.resizeBase64ImageToJpeg)(screenshotBase64, {
|
|
1187
|
+
sourceSize,
|
|
1188
|
+
targetSize
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1108
1191
|
async ensureManager(deviceInfo) {
|
|
1109
1192
|
if (this.manager) return this.manager;
|
|
1193
|
+
if (this.managerPromise) return this.managerPromise;
|
|
1110
1194
|
debugAdapter('Initializing Scrcpy manager...');
|
|
1195
|
+
const generation = this.lifecycleGeneration;
|
|
1196
|
+
const managerPromise = (async ()=>{
|
|
1197
|
+
let adb = null;
|
|
1198
|
+
let manager = null;
|
|
1199
|
+
try {
|
|
1200
|
+
const { Adb, AdbServerClient } = await import("@yume-chan/adb");
|
|
1201
|
+
const { AdbServerNodeTcpConnector } = await import("@yume-chan/adb-server-node-tcp");
|
|
1202
|
+
const { ScrcpyScreenshotManager: ScrcpyManager } = await Promise.resolve().then(__webpack_require__.bind(__webpack_require__, "./src/scrcpy-manager.ts"));
|
|
1203
|
+
const adbServerEndpoint = await this.resolveAdbServerEndpoint();
|
|
1204
|
+
const adbClient = new AdbServerClient(new AdbServerNodeTcpConnector(adbServerEndpoint));
|
|
1205
|
+
adb = new Adb(await adbClient.createTransport({
|
|
1206
|
+
serial: this.deviceId
|
|
1207
|
+
}));
|
|
1208
|
+
const config = this.resolveConfig(deviceInfo);
|
|
1209
|
+
manager = new ScrcpyManager(adb, {
|
|
1210
|
+
maxSize: config.maxSize,
|
|
1211
|
+
videoBitRate: config.videoBitRate,
|
|
1212
|
+
idleTimeoutMs: config.idleTimeoutMs
|
|
1213
|
+
});
|
|
1214
|
+
await manager.validateEnvironment();
|
|
1215
|
+
if (generation !== this.lifecycleGeneration) throw new Error('Scrcpy manager initialization was superseded by device cleanup');
|
|
1216
|
+
this.manager = manager;
|
|
1217
|
+
debugAdapter('Scrcpy manager initialized');
|
|
1218
|
+
return manager;
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
try {
|
|
1221
|
+
if (manager) await manager.dispose();
|
|
1222
|
+
else await adb?.close();
|
|
1223
|
+
} catch (cleanupError) {
|
|
1224
|
+
debugAdapter(`Failed to clean up Scrcpy manager initialization: ${cleanupError}`);
|
|
1225
|
+
}
|
|
1226
|
+
debugAdapter(`Failed to initialize Scrcpy manager: ${error}`);
|
|
1227
|
+
throw new Error(`Failed to initialize Scrcpy for device ${this.deviceId}. Ensure ADB server is running and device is connected. Error: ${error}`);
|
|
1228
|
+
}
|
|
1229
|
+
})();
|
|
1230
|
+
this.managerPromise = managerPromise;
|
|
1111
1231
|
try {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const adbServerEndpoint = await this.resolveAdbServerEndpoint();
|
|
1116
|
-
const adbClient = new AdbServerClient(new AdbServerNodeTcpConnector(adbServerEndpoint));
|
|
1117
|
-
const adb = new Adb(await adbClient.createTransport({
|
|
1118
|
-
serial: this.deviceId
|
|
1119
|
-
}));
|
|
1120
|
-
const config = this.resolveConfig(deviceInfo);
|
|
1121
|
-
const manager = new ScrcpyManager(adb, {
|
|
1122
|
-
maxSize: config.maxSize,
|
|
1123
|
-
videoBitRate: config.videoBitRate,
|
|
1124
|
-
idleTimeoutMs: config.idleTimeoutMs
|
|
1125
|
-
});
|
|
1126
|
-
await manager.validateEnvironment();
|
|
1127
|
-
this.manager = manager;
|
|
1128
|
-
debugAdapter('Scrcpy manager initialized');
|
|
1129
|
-
return this.manager;
|
|
1130
|
-
} catch (error) {
|
|
1131
|
-
debugAdapter(`Failed to initialize Scrcpy manager: ${error}`);
|
|
1132
|
-
throw new Error(`Failed to initialize Scrcpy for device ${this.deviceId}. Ensure ADB server is running and device is connected. Error: ${error}`);
|
|
1232
|
+
return await managerPromise;
|
|
1233
|
+
} finally{
|
|
1234
|
+
if (this.managerPromise === managerPromise) this.managerPromise = null;
|
|
1133
1235
|
}
|
|
1134
1236
|
}
|
|
1135
1237
|
async screenshotBase64(deviceInfo) {
|
|
@@ -1141,16 +1243,54 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1141
1243
|
await this.applyPendingActionBarrier(manager);
|
|
1142
1244
|
const screenshotBuffer = await manager.getScreenshotJpeg();
|
|
1143
1245
|
this.clearFailure();
|
|
1144
|
-
return
|
|
1246
|
+
return this.jpegBufferToBase64(screenshotBuffer);
|
|
1145
1247
|
} catch (error) {
|
|
1146
|
-
if ((0, scrcpy_manager.GJ)(error)) {
|
|
1147
|
-
this.
|
|
1248
|
+
if (!(0, scrcpy_manager.GJ)(error)) {
|
|
1249
|
+
this.recordFailure(error);
|
|
1148
1250
|
throw error;
|
|
1149
1251
|
}
|
|
1150
|
-
this.
|
|
1151
|
-
|
|
1252
|
+
this.markFreshnessRecoveryPending(error);
|
|
1253
|
+
debugAdapter(`Scrcpy freshness target was unavailable; restarting the stream once before ADB fallback: ${error}`);
|
|
1254
|
+
try {
|
|
1255
|
+
const screenshotBuffer = await this.restartAndCaptureOnce(deviceInfo);
|
|
1256
|
+
manager = this.manager;
|
|
1257
|
+
if (!manager) throw new Error('Scrcpy manager disappeared after freshness recovery');
|
|
1258
|
+
this.attachKeyframeListeners(manager);
|
|
1259
|
+
this.clearFailure();
|
|
1260
|
+
debugAdapter('Scrcpy screenshot recovered on a new stream epoch');
|
|
1261
|
+
return this.jpegBufferToBase64(screenshotBuffer);
|
|
1262
|
+
} catch (retryError) {
|
|
1263
|
+
if ((0, scrcpy_manager.GJ)(retryError)) this.markFreshnessRecoveryPending(retryError);
|
|
1264
|
+
else this.recordFailure(retryError);
|
|
1265
|
+
this.warnFreshnessFallback(error, retryError);
|
|
1266
|
+
throw retryError;
|
|
1267
|
+
}
|
|
1152
1268
|
}
|
|
1153
1269
|
}
|
|
1270
|
+
async restartAndCaptureOnce(deviceInfo) {
|
|
1271
|
+
if (this.freshnessRestartPromise) return this.freshnessRestartPromise;
|
|
1272
|
+
const generation = this.lifecycleGeneration;
|
|
1273
|
+
const restartPromise = (async ()=>{
|
|
1274
|
+
const manager = await this.ensureManager(deviceInfo);
|
|
1275
|
+
await manager.ensureConnected();
|
|
1276
|
+
await this.applyPendingActionBarrier(manager);
|
|
1277
|
+
if (generation !== this.lifecycleGeneration) throw new Error('Scrcpy freshness restart was cancelled by cleanup');
|
|
1278
|
+
return manager.getScreenshotJpeg();
|
|
1279
|
+
})();
|
|
1280
|
+
this.freshnessRestartPromise = restartPromise;
|
|
1281
|
+
try {
|
|
1282
|
+
return await restartPromise;
|
|
1283
|
+
} finally{
|
|
1284
|
+
if (this.freshnessRestartPromise === restartPromise) this.freshnessRestartPromise = null;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
jpegBufferToBase64(screenshotBuffer) {
|
|
1288
|
+
return (0, img_namespaceObject.createImgBase64ByFormat)('jpeg', screenshotBuffer.toString('base64'));
|
|
1289
|
+
}
|
|
1290
|
+
warnFreshnessFallback(firstError, retryError) {
|
|
1291
|
+
const retryDiagnostic = (0, scrcpy_manager.GJ)(retryError) ? retryError.diagnosticMessage : void 0;
|
|
1292
|
+
warnAdapter(retryDiagnostic ?? (firstError.diagnosticMessage ? `${firstError.diagnosticMessage}\nScrcpy stream restart error: ${retryError}` : void 0) ?? `Scrcpy stream restart failed; falling back to ADB screenshot. Error: ${retryError}`);
|
|
1293
|
+
}
|
|
1154
1294
|
async subscribeKeyframes(deviceInfo, listener) {
|
|
1155
1295
|
this.ensureRetryReady();
|
|
1156
1296
|
this.keyframeListeners.add(listener);
|
|
@@ -1183,17 +1323,26 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1183
1323
|
this.keyframeUnsubscribers.clear();
|
|
1184
1324
|
for (const listener of this.keyframeListeners)this.attachKeyframeListener(manager, listener);
|
|
1185
1325
|
}
|
|
1186
|
-
markFreshnessRecoveryPending(
|
|
1326
|
+
markFreshnessRecoveryPending(error) {
|
|
1187
1327
|
this.lastError = error.message;
|
|
1188
1328
|
this.retryAfter = null;
|
|
1189
1329
|
this.freshnessRecoveryPending = true;
|
|
1190
1330
|
this.keyframeUnsubscribers.clear();
|
|
1191
|
-
if (manager && this.manager === manager) this.manager = null;
|
|
1192
1331
|
}
|
|
1193
1332
|
async applyPendingActionBarrier(manager) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1333
|
+
const actionCompletedAtHostUs = this.pendingActionBarrierAtHostUs;
|
|
1334
|
+
if (null === actionCompletedAtHostUs) return;
|
|
1335
|
+
await manager.setFreshnessBarrier('completed input action while scrcpy was unavailable', {
|
|
1336
|
+
allowOverAgeForNextCapture: true,
|
|
1337
|
+
hostMonotonicUs: actionCompletedAtHostUs
|
|
1338
|
+
});
|
|
1339
|
+
if (this.pendingActionBarrierAtHostUs === actionCompletedAtHostUs) this.pendingActionBarrierAtHostUs = null;
|
|
1340
|
+
}
|
|
1341
|
+
monotonicTimeUs() {
|
|
1342
|
+
return process.hrtime.bigint() / 1000n;
|
|
1343
|
+
}
|
|
1344
|
+
deferActionBarrier(actionCompletedAtHostUs) {
|
|
1345
|
+
if (null === this.pendingActionBarrierAtHostUs || actionCompletedAtHostUs > this.pendingActionBarrierAtHostUs) this.pendingActionBarrierAtHostUs = actionCompletedAtHostUs;
|
|
1197
1346
|
}
|
|
1198
1347
|
recoverAfterAdbScreenshot(deviceInfo) {
|
|
1199
1348
|
if (!this.freshnessRecoveryPending || this.recoveryPromise) return;
|
|
@@ -1206,7 +1355,7 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1206
1355
|
await this.applyPendingActionBarrier(manager);
|
|
1207
1356
|
await manager.prepareFreshFrame();
|
|
1208
1357
|
if (generation !== this.lifecycleGeneration) {
|
|
1209
|
-
await manager.
|
|
1358
|
+
await manager.dispose();
|
|
1210
1359
|
if (this.manager === manager) this.manager = null;
|
|
1211
1360
|
return;
|
|
1212
1361
|
}
|
|
@@ -1215,7 +1364,7 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1215
1364
|
debugAdapter('Scrcpy freshness recovery completed in background');
|
|
1216
1365
|
} catch (error) {
|
|
1217
1366
|
if (manager) {
|
|
1218
|
-
await manager.
|
|
1367
|
+
await manager.dispose();
|
|
1219
1368
|
if (this.manager === manager) this.manager = null;
|
|
1220
1369
|
}
|
|
1221
1370
|
this.freshnessRecoveryPending = false;
|
|
@@ -1230,21 +1379,22 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1230
1379
|
});
|
|
1231
1380
|
}
|
|
1232
1381
|
async markActionBarrier() {
|
|
1382
|
+
const actionCompletedAtHostUs = this.monotonicTimeUs();
|
|
1233
1383
|
const manager = this.manager;
|
|
1234
|
-
if (!manager?.isConnected())
|
|
1235
|
-
this.pendingActionBarrier = true;
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1384
|
+
if (!manager?.isConnected()) return void this.deferActionBarrier(actionCompletedAtHostUs);
|
|
1238
1385
|
try {
|
|
1239
|
-
await manager.setFreshnessBarrier('completed input action'
|
|
1240
|
-
|
|
1386
|
+
await manager.setFreshnessBarrier('completed input action', {
|
|
1387
|
+
allowOverAgeForNextCapture: true,
|
|
1388
|
+
hostMonotonicUs: actionCompletedAtHostUs
|
|
1389
|
+
});
|
|
1390
|
+
if (null !== this.pendingActionBarrierAtHostUs && this.pendingActionBarrierAtHostUs <= actionCompletedAtHostUs) this.pendingActionBarrierAtHostUs = null;
|
|
1241
1391
|
this.clearFailure();
|
|
1242
1392
|
} catch (error) {
|
|
1243
|
-
this.
|
|
1393
|
+
this.deferActionBarrier(actionCompletedAtHostUs);
|
|
1244
1394
|
this.recordFailure(error);
|
|
1245
1395
|
debugAdapter(`Unable to mark scrcpy action barrier; disabling this stream: ${error}`);
|
|
1246
1396
|
try {
|
|
1247
|
-
await manager.
|
|
1397
|
+
await manager.dispose();
|
|
1248
1398
|
} catch (disconnectError) {
|
|
1249
1399
|
debugAdapter(`Error disconnecting scrcpy after barrier failure: ${disconnectError}`);
|
|
1250
1400
|
} finally{
|
|
@@ -1277,14 +1427,16 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1277
1427
|
async disconnect() {
|
|
1278
1428
|
this.lifecycleGeneration += 1;
|
|
1279
1429
|
this.freshnessRecoveryPending = false;
|
|
1280
|
-
this.
|
|
1430
|
+
this.pendingActionBarrierAtHostUs = null;
|
|
1281
1431
|
for (const unsubscribe of this.keyframeUnsubscribers.values())unsubscribe();
|
|
1282
1432
|
this.keyframeUnsubscribers.clear();
|
|
1283
1433
|
this.keyframeListeners.clear();
|
|
1284
1434
|
if (this.recoveryPromise) await this.recoveryPromise.catch(()=>{});
|
|
1435
|
+
if (this.freshnessRestartPromise) await this.freshnessRestartPromise.catch(()=>{});
|
|
1436
|
+
if (this.managerPromise) await this.managerPromise.catch(()=>{});
|
|
1285
1437
|
if (this.manager) {
|
|
1286
1438
|
try {
|
|
1287
|
-
await this.manager.
|
|
1439
|
+
await this.manager.dispose();
|
|
1288
1440
|
} catch (error) {
|
|
1289
1441
|
debugAdapter(`Error disconnecting scrcpy: ${error}`);
|
|
1290
1442
|
}
|
|
@@ -1298,26 +1450,30 @@ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
|
1298
1450
|
_define_property(this, "scrcpyConfig", void 0);
|
|
1299
1451
|
_define_property(this, "resolveAdbServerEndpoint", void 0);
|
|
1300
1452
|
_define_property(this, "manager", void 0);
|
|
1453
|
+
_define_property(this, "managerPromise", void 0);
|
|
1301
1454
|
_define_property(this, "resolvedConfig", void 0);
|
|
1302
1455
|
_define_property(this, "lastError", void 0);
|
|
1303
1456
|
_define_property(this, "retryAfter", void 0);
|
|
1304
1457
|
_define_property(this, "freshnessRecoveryPending", void 0);
|
|
1458
|
+
_define_property(this, "freshnessRestartPromise", void 0);
|
|
1305
1459
|
_define_property(this, "recoveryPromise", void 0);
|
|
1306
1460
|
_define_property(this, "lifecycleGeneration", void 0);
|
|
1307
|
-
_define_property(this, "
|
|
1461
|
+
_define_property(this, "pendingActionBarrierAtHostUs", void 0);
|
|
1308
1462
|
_define_property(this, "keyframeListeners", void 0);
|
|
1309
1463
|
_define_property(this, "keyframeUnsubscribers", void 0);
|
|
1310
1464
|
this.deviceId = deviceId;
|
|
1311
1465
|
this.scrcpyConfig = scrcpyConfig;
|
|
1312
1466
|
this.resolveAdbServerEndpoint = resolveAdbServerEndpoint;
|
|
1313
1467
|
this.manager = null;
|
|
1468
|
+
this.managerPromise = null;
|
|
1314
1469
|
this.resolvedConfig = null;
|
|
1315
1470
|
this.lastError = null;
|
|
1316
1471
|
this.retryAfter = null;
|
|
1317
1472
|
this.freshnessRecoveryPending = false;
|
|
1473
|
+
this.freshnessRestartPromise = null;
|
|
1318
1474
|
this.recoveryPromise = null;
|
|
1319
1475
|
this.lifecycleGeneration = 0;
|
|
1320
|
-
this.
|
|
1476
|
+
this.pendingActionBarrierAtHostUs = null;
|
|
1321
1477
|
this.keyframeListeners = new Set();
|
|
1322
1478
|
this.keyframeUnsubscribers = new Map();
|
|
1323
1479
|
}
|
|
@@ -2168,9 +2324,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
2168
2324
|
}
|
|
2169
2325
|
async screenshotBase64() {
|
|
2170
2326
|
debugDevice('screenshotBase64 begin');
|
|
2171
|
-
const screenshotStrategy = this.options?.screenshotStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_SCREENSHOT_STRATEGY) || SCREENSHOT_STRATEGY_AUTO;
|
|
2172
|
-
if (screenshotStrategy === SCREENSHOT_STRATEGY_ALWAYS_YADB) return this.screenshotBase64ViaYadb();
|
|
2173
2327
|
const adapter = this.getScrcpyAdapter();
|
|
2328
|
+
const screenshotStrategy = this.options?.screenshotStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_SCREENSHOT_STRATEGY) || SCREENSHOT_STRATEGY_AUTO;
|
|
2329
|
+
if (screenshotStrategy === SCREENSHOT_STRATEGY_ALWAYS_YADB) return adapter.prepareFallbackScreenshot(await this.screenshotBase64ViaYadb());
|
|
2174
2330
|
let scrcpyDeviceInfo = null;
|
|
2175
2331
|
if (adapter.isEnabled()) try {
|
|
2176
2332
|
debugDevice('Attempting scrcpy screenshot...');
|
|
@@ -2179,7 +2335,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
2179
2335
|
debugDevice('screenshotBase64 end (scrcpy mode)');
|
|
2180
2336
|
return result;
|
|
2181
2337
|
} catch (error) {
|
|
2182
|
-
warnDevice(`Scrcpy screenshot failed, falling back to standard ADB method
|
|
2338
|
+
warnDevice(`Scrcpy screenshot failed, falling back to standard ADB method.\nError: ${error}`);
|
|
2183
2339
|
}
|
|
2184
2340
|
const adb = await this.getAdb();
|
|
2185
2341
|
let screenshotBuffer;
|
|
@@ -2221,14 +2377,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
2221
2377
|
});
|
|
2222
2378
|
debugDevice('screenshotBase64 end (fallback)');
|
|
2223
2379
|
if (scrcpyDeviceInfo) adapter.recoverAfterAdbScreenshot(scrcpyDeviceInfo);
|
|
2224
|
-
return result;
|
|
2380
|
+
return adapter.prepareFallbackScreenshot(result);
|
|
2225
2381
|
}
|
|
2226
2382
|
if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
|
|
2227
2383
|
debugDevice('Converting to base64');
|
|
2228
2384
|
const result = (0, img_namespaceObject.createImgBase64ByFormat)('png', screenshotBuffer.toString('base64'));
|
|
2229
2385
|
debugDevice('screenshotBase64 end');
|
|
2230
2386
|
if (scrcpyDeviceInfo) adapter.recoverAfterAdbScreenshot(scrcpyDeviceInfo);
|
|
2231
|
-
return result;
|
|
2387
|
+
return adapter.prepareFallbackScreenshot(result);
|
|
2232
2388
|
}
|
|
2233
2389
|
async captureScreenshotBase64FromDeviceFile(label, capture) {
|
|
2234
2390
|
const adb = await this.getAdb();
|
|
@@ -3082,12 +3238,18 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
3082
3238
|
const debug = (0, logger_.getDebug)('agent-tools:android');
|
|
3083
3239
|
function adaptAndroidInitArgs(extracted) {
|
|
3084
3240
|
if (!extracted) return;
|
|
3241
|
+
const scrcpyVideoBitRate = 'number' == typeof extracted.scrcpyVideoBitRate ? extracted.scrcpyVideoBitRate : void 0;
|
|
3242
|
+
const requestedUseScrcpy = 'boolean' == typeof extracted.useScrcpy ? extracted.useScrcpy : void 0;
|
|
3243
|
+
const useScrcpy = void 0 !== scrcpyVideoBitRate ? true : requestedUseScrcpy;
|
|
3085
3244
|
const initArgs = {
|
|
3086
3245
|
...'string' == typeof extracted.deviceId ? {
|
|
3087
3246
|
deviceId: extracted.deviceId
|
|
3088
3247
|
} : {},
|
|
3089
|
-
...
|
|
3090
|
-
useScrcpy
|
|
3248
|
+
...void 0 !== useScrcpy ? {
|
|
3249
|
+
useScrcpy
|
|
3250
|
+
} : {},
|
|
3251
|
+
...void 0 !== scrcpyVideoBitRate ? {
|
|
3252
|
+
scrcpyVideoBitRate
|
|
3091
3253
|
} : {},
|
|
3092
3254
|
...(0, agent_behavior_init_args_namespaceObject.extractAgentBehaviorInitArgs)(extracted) ?? {}
|
|
3093
3255
|
};
|
|
@@ -3119,7 +3281,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
3119
3281
|
...(0, agent_behavior_init_args_namespaceObject.extractAgentBehaviorInitArgs)(initArgs) ?? {},
|
|
3120
3282
|
...initArgs?.useScrcpy ? {
|
|
3121
3283
|
scrcpyConfig: {
|
|
3122
|
-
enabled: true
|
|
3284
|
+
enabled: true,
|
|
3285
|
+
...void 0 !== initArgs.scrcpyVideoBitRate ? {
|
|
3286
|
+
videoBitRate: initArgs.scrcpyVideoBitRate
|
|
3287
|
+
} : {}
|
|
3123
3288
|
}
|
|
3124
3289
|
} : {},
|
|
3125
3290
|
...reportOptions ?? {}
|
|
@@ -3177,6 +3342,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
3177
3342
|
shape: {
|
|
3178
3343
|
deviceId: core_namespaceObject.z.string().optional().describe('Android device ID (from adb devices)'),
|
|
3179
3344
|
useScrcpy: core_namespaceObject.z.boolean().optional().describe('Enable scrcpy accelerated screenshots'),
|
|
3345
|
+
scrcpyVideoBitRate: core_namespaceObject.z.number().int().positive().optional().describe('scrcpy H.264 video bitrate in bits per second; providing this option also enables scrcpy. Default with --use-scrcpy: 100000000 (100 Mbps). For constrained remote links, start with 4000000 (4 Mbps).'),
|
|
3180
3346
|
...agent_behavior_init_args_namespaceObject.agentBehaviorInitArgShape
|
|
3181
3347
|
},
|
|
3182
3348
|
cli: {
|
|
@@ -3189,7 +3355,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
3189
3355
|
const tools = new AndroidMidsceneTools();
|
|
3190
3356
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
|
|
3191
3357
|
stripPrefix: 'android_',
|
|
3192
|
-
version: "1.12.0",
|
|
3358
|
+
version: "1.12.1-beta-20260824081858.0",
|
|
3193
3359
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
3194
3360
|
}).catch((e)=>{
|
|
3195
3361
|
process.exit((0, cli_namespaceObject.reportCLIError)(e));
|