@@ -16,6 +16,8 @@ import { parse_sequence_parameter_set } from './sps.js';
1616import { Point , ScrcpyOptions , H264Configuration , VideoStreamFramePacket } from './ScrcpyModels.js' ;
1717import prebuilds from "@u4/minicap-prebuilt" ;
1818
19+ const SC_SOCKET_NAME_PREFIX = "scrcpy_" ;
20+
1921const debug = Utils . debug ( 'adb:scrcpy' ) ;
2022
2123// const KEYFRAME_PTS = BigInt(1) << BigInt(62);
@@ -24,6 +26,11 @@ const debug = Utils.debug('adb:scrcpy');
2426const PACKET_FLAG_CONFIG = BigInt ( 1 ) << BigInt ( 63 ) ;
2527const PACKET_FLAG_KEY_FRAME = BigInt ( 1 ) << BigInt ( 62 ) ;
2628
29+ /**
30+ * usage reference fron app/src/server.c in scrcpy
31+ * https://github.com/Genymobile/scrcpy/blob/master/app/src/server.c
32+ */
33+
2734/**
2835 * by hand start:
2936 *
@@ -71,6 +78,7 @@ interface IEmissions {
7178export default class Scrcpy extends EventEmitter {
7279 private config : ScrcpyOptions ;
7380 private videoSocket : PromiseDuplex < Duplex > | undefined ;
81+ private audioSocket : PromiseDuplex < Duplex > | undefined ;
7482 private controlSocket : PromiseDuplex < Duplex > | undefined ;
7583 /**
7684 * used to recive Process Error
@@ -103,7 +111,9 @@ export default class Scrcpy extends EventEmitter {
103111 constructor ( private client : DeviceClient , config = { } as Partial < ScrcpyOptions > ) {
104112 super ( ) ;
105113 this . config = {
106- version : 24 ,
114+ scid : '0' + Math . random ( ) . toString ( 16 ) . substring ( 2 , 9 ) ,
115+ noAudio : true , // disable audio TMP
116+ version : "2.7" ,
107117 // port: 8099,
108118 maxSize : 600 ,
109119 maxFps : 0 ,
@@ -161,7 +171,7 @@ export default class Scrcpy extends EventEmitter {
161171 try {
162172 const errors = [ ] ;
163173 for ( ; ; ) {
164- await Utils . waitforReadable ( duplex , 0 , 'wait for error' ) ;
174+ await Utils . waitforReadable ( duplex , 0 , 'wait for error from ScrcpyServer ' ) ;
165175 const data = await duplex . read ( ) ;
166176 if ( data ) {
167177 const msg = data . toString ( ) . trim ( ) ;
@@ -173,7 +183,9 @@ export default class Scrcpy extends EventEmitter {
173183 // emit Error but to not want to Quit Yet
174184 }
175185 } else {
176- this . _setFatalError ( errors . join ( '\n' ) ) ;
186+ if ( errors . length > 0 )
187+ this . _setFatalError ( errors . join ( '\n' ) ) ;
188+ // else no error
177189 break ;
178190 }
179191 }
@@ -227,6 +239,13 @@ export default class Scrcpy extends EventEmitter {
227239 throw Error ( `Unsupported message type:${ type } ` ) ;
228240 }
229241 }
242+ get strVersion ( ) : string {
243+ let versionSplit = this . config . version . split ( "." ) . map ( Number ) ;
244+ if ( versionSplit . length === 2 ) {
245+ versionSplit = [ ...versionSplit , 0 ] ;
246+ }
247+ return `${ versionSplit [ 0 ] . toString ( ) . padStart ( 2 , '0' ) } .${ versionSplit [ 1 ] . toString ( ) . padStart ( 2 , '0' ) } .${ versionSplit [ 2 ] . toString ( ) . padStart ( 2 , '0' ) } ` ;
248+ }
230249
231250 private _getStartupLine ( jarDest : string ) : string {
232251 const args : Array < string | number | boolean > = [ ] ;
@@ -239,10 +258,19 @@ export default class Scrcpy extends EventEmitter {
239258 args . push ( 'app_process' ) ;
240259 args . push ( '/' ) ;
241260 args . push ( 'com.genymobile.scrcpy.Server' ) ;
261+ const versionStr = this . strVersion ;
242262
243- if ( this . config . version <= 20 ) {
244- // Version 11 => 20
245- args . push ( `1.${ this . config . version } ` ) ; // arg 0 Scrcpy server version
263+ // first args is the expected version number
264+ if ( versionStr === "02.02.00" ) {
265+ // V2.2 is the only version that expect a v prefix
266+ args . push ( "v" + this . config . version ) ;
267+ } else {
268+ args . push ( this . config . version ) ;
269+ }
270+ // args.push(this.config.version); // arg 0 Scrcpy server version
271+ //if (this.config.version <= 20) {
272+ if ( versionStr <= "02.00.00" ) {
273+ // Version 11 => 20
246274 args . push ( "info" ) ; // Log level: info, verbose...
247275 args . push ( maxSize ) ; // Max screen width (long side)
248276 args . push ( bitrate ) ; // Bitrate of video
@@ -259,10 +287,26 @@ export default class Scrcpy extends EventEmitter {
259287 args . push ( encoderName || '-' ) ; // Encoder name
260288 args . push ( powerOffScreenOnClose ) ; // Power off screen after server closed
261289 } else {
262- args . push ( `1.${ this . config . version } ` ) ; // arg 0 Scrcpy server version
290+ if ( versionStr >= "02.00.00" ) {
291+ args . push ( `scid=${ this . config . scid } ` ) ;
292+ if ( this . config . noAudio )
293+ args . push ( `audio=false` ) ;
294+ // if (this.config.noControl)
295+ // args.push(`no_control=${this.config.noControl}`);
296+ }
297+
298+ if ( versionStr >= "02.04.00" ) {
299+ args . push ( `video_source=display` ) ;
300+ }
301+
263302 args . push ( "log_level=info" ) ;
264303 args . push ( `max_size=${ maxSize } ` ) ;
265- args . push ( `bit_rate=${ bitrate } ` ) ;
304+ args . push ( "clipboard_autosync=false" ) ; // cause crash on some newer phone and we do not use that feature.
305+ if ( versionStr >= "02.00.00" ) {
306+ args . push ( `video_bit_rate=${ bitrate } ` ) ;
307+ } else {
308+ args . push ( `bit_rate=${ bitrate } ` ) ;
309+ }
266310 args . push ( `max_fps=${ maxFps } ` ) ;
267311 args . push ( `lock_video_orientation=${ lockedVideoOrientation } ` ) ;
268312 args . push ( `tunnel_forward=${ tunnelForward } ` ) ; // Tunnel forward
@@ -281,8 +325,9 @@ export default class Scrcpy extends EventEmitter {
281325 // args.push(`clipboard_autosync=${clipboardAutosync}`); // default is True
282326 if ( clipboardAutosync !== undefined )
283327 args . push ( `clipboard_autosync=${ clipboardAutosync } ` ) ; // default is True
284- if ( this . config . version >= 22 ) {
285- const {
328+ //if (this.config.version >= 22) {
329+ if ( versionStr >= "01.22.00" ) {
330+ const {
286331 downsizeOnError, sendDeviceMeta, sendDummyByte, rawVideoStream
287332 } = this . config ;
288333 if ( downsizeOnError !== undefined )
@@ -294,7 +339,7 @@ export default class Scrcpy extends EventEmitter {
294339 if ( rawVideoStream !== undefined )
295340 args . push ( `raw_video_stream=${ rawVideoStream } ` ) ;
296341 }
297- if ( this . config . version >= 22 ) {
342+ if ( versionStr >= "01.22.00" ) {
298343 const { cleanup } = this . config ;
299344 if ( cleanup !== undefined )
300345 args . push ( `raw_video_stream=${ cleanup } ` ) ;
@@ -311,10 +356,19 @@ export default class Scrcpy extends EventEmitter {
311356 async start ( ) : Promise < this> {
312357 if ( this . closed ) // can not start once stop called
313358 return this ;
314- const jarDest = '/data/local/tmp/scrcpy-server.jar' ;
315- // Transfer server...
316- const jar = prebuilds . getScrcpyJar ( `1.${ this . config . version } ` ) ;
317- // ThirdUtils.getResourcePath(`scrcpy-server-v1.${this.config.version}.jar`);
359+
360+ let dstFolder = '/data/local/tmp' ;
361+ let dstFolderStat = await this . client . stat ( dstFolder ) . catch ( ( e ) => { console . log ( e ) ; return null ; } ) ;
362+ if ( ! dstFolderStat ) {
363+ dstFolder = '/tmp' ;
364+ dstFolderStat = await this . client . stat ( dstFolder ) . catch ( ( ) => null ) ;
365+ }
366+ if ( ! dstFolderStat ) {
367+ throw Error ( "can not find a writable tmp dest folder in device" ) ;
368+ }
369+ const jarDest = `${ dstFolder } /scrcpy-server-v${ this . config . version } .jar` ;
370+ // Transfer server jar to device...
371+ const jar = prebuilds . getScrcpyJar ( this . config . version ) ;
318372
319373 const srcStat : fs . Stats | null = await fs . promises . stat ( jar ) . catch ( ( ) => null ) ;
320374 const dstStat : Stats | null = await this . client . stat ( jarDest ) . catch ( ( ) => null ) ;
@@ -332,9 +386,15 @@ export default class Scrcpy extends EventEmitter {
332386 } else {
333387 debug ( `scrcpy-server.jar already present in ${ this . client . serial } , keep it` ) ;
334388 }
335- // Start server
389+ ///////
390+ // Build the commandline to start the server
336391 try {
337392 const cmdLine = this . _getStartupLine ( jarDest ) ;
393+
394+ // console.log("starting scrcpy server with cmdLine:");
395+ // console.log(cmdLine);
396+ // console.log("");
397+
338398 if ( this . closed ) // can not start once stop called
339399 return this ;
340400 const duplex = await this . client . shell ( cmdLine ) ;
@@ -350,37 +410,67 @@ export default class Scrcpy extends EventEmitter {
350410 throw e ;
351411 }
352412
353- let info = '' ;
413+ let stdoutContent = '' ;
354414 for ( ; ; ) {
355415 if ( ! await Utils . waitforReadable ( this . scrcpyServer , this . config . tunnelDelay , 'scrcpyServer stdout loading' ) ) {
356416 // const msg = `First line should be '[server] // INFO: Device: Name (Version), reveived:\n\n${info}`
357- const error = `Starting scrcpyServer failed, scrcpy stdout:${ info } ` ;
417+ if ( ! stdoutContent )
418+ stdoutContent = "no stdout content" ;
419+ const error = `Starting scrcpyServer failed, scrcpy stdout:${ stdoutContent } ` ;
358420 this . _setFatalError ( error ) ;
359421 this . stop ( ) ;
360422 throw Error ( error ) ;
361423 }
362424 const srvOut = await this . scrcpyServer . read ( ) ;
363- info += ( srvOut ) ? srvOut . toString ( ) : '' ;
364- if ( info . includes ( '[server] INFO: Device: ' ) )
425+ stdoutContent += ( srvOut ) ? srvOut . toString ( ) : '' ;
426+ // the server may crash within the first message
427+ const errorIndex = stdoutContent . indexOf ( "[server] ERROR:" ) ;
428+ if ( errorIndex >= 0 ) {
429+ const error = stdoutContent . substring ( errorIndex )
430+ this . _setFatalError ( error ) ;
431+ this . stop ( ) ;
432+ throw Error ( error ) ;
433+ }
434+ if ( stdoutContent . includes ( '[server] INFO: Device: ' ) )
365435 break ;
366436 }
367437
368438 this . throwsErrors ( this . scrcpyServer ) ;
369439
440+ // from V2.0 SC_SOCKET_NAME name can be change
441+ const strVersion = this . strVersion ;
442+ let SC_SOCKET_NAME = 'scrcpy' ;
443+ if ( strVersion >= "02.00.00" ) {
444+ SC_SOCKET_NAME = SC_SOCKET_NAME_PREFIX + this . config . scid ;
445+ assert ( this . config . scid . length == 8 , `scid length should be 8` ) ;
446+ }
447+
370448 // Wait 1 sec to forward to work
371449 // await Util.delay(this.config.tunnelDelay);
372450
373451 if ( this . closed ) // can not start once stop called
374452 return this ;
453+
375454 // Connect videoSocket
376455 await Utils . delay ( 100 ) ;
377- this . videoSocket = await this . client . openLocal2 ( ' localabstract:scrcpy' , 'first connection to scrcpy for video' ) ;
378- // Connect controlSocket
456+ this . videoSocket = await this . client . openLocal2 ( ` localabstract:${ SC_SOCKET_NAME } ` , 'first connection to scrcpy for video' ) ;
457+
379458 if ( this . closed ) {
380459 this . stop ( ) ;
381460 return this ;
382461 }
383- this . controlSocket = await this . client . openLocal2 ( 'localabstract:scrcpy' , 'second connection to scrcpy for control' ) ;
462+
463+ if ( strVersion >= "02.00.00" && ! this . config . noAudio ) {
464+ // Connect audioSocket
465+ this . audioSocket = await this . client . openLocal2 ( `localabstract:${ SC_SOCKET_NAME } ` , 'first connection to scrcpy for audio' ) ;
466+ // Connect controlSocket
467+ if ( this . closed ) {
468+ this . stop ( ) ;
469+ return this ;
470+ }
471+ }
472+
473+ this . controlSocket = await this . client . openLocal2 ( `localabstract:${ SC_SOCKET_NAME } ` , 'second connection to scrcpy for control' ) ;
384474 if ( this . closed ) {
385475 this . stop ( ) ;
386476 return this ;
@@ -406,7 +496,10 @@ export default class Scrcpy extends EventEmitter {
406496 }
407497
408498 if ( this . config . sendFrameMeta ) {
409- void this . startStreamWithMeta ( ) . catch ( ( ) => this . stop ( ) ) ;
499+ void this . startStreamWithMeta ( ) . catch ( ( e ) => {
500+ this . _setFatalError ( e ) ;
501+ this . stop ( ) ;
502+ } ) ;
410503 } else {
411504 this . startStreamRaw ( ) ;
412505 }
@@ -423,6 +516,11 @@ export default class Scrcpy extends EventEmitter {
423516 this . videoSocket = undefined ;
424517 close = true ;
425518 }
519+ if ( this . audioSocket ) {
520+ this . audioSocket . destroy ( ) ;
521+ this . audioSocket = undefined ;
522+ close = true ;
523+ }
426524 if ( this . controlSocket ) {
427525 this . controlSocket . destroy ( ) ;
428526 this . controlSocket = undefined ;
@@ -450,17 +548,32 @@ export default class Scrcpy extends EventEmitter {
450548 * get resolve once capture stop
451549 */
452550 private async startStreamWithMeta ( ) : Promise < void > {
551+ const strVersion = this . strVersion ;
453552 assert ( this . videoSocket ) ;
454553 this . videoSocket . stream . pause ( ) ;
455554 await Utils . waitforReadable ( this . videoSocket , 0 , 'videoSocket header' ) ;
456- const chunk = this . videoSocket . stream . read ( 68 ) as Buffer ;
457- const name = chunk . toString ( 'utf8' , 0 , 64 ) . trim ( ) ;
458- this . setName ( name ) ;
459- const width = chunk . readUint16BE ( 64 ) ;
460- this . setWidth ( width ) ;
461- const height = chunk . readUint16BE ( 66 ) ;
462- this . setHeight ( height ) ;
463-
555+ if ( strVersion >= "02.00.00" ) {
556+ const chunk = this . videoSocket . stream . read ( 64 ) as Buffer ;
557+ if ( ! chunk )
558+ throw Error ( 'fail to read firstChunk, inclease tunnelDelay for this device.' ) ;
559+ const name = chunk . toString ( 'utf8' , 0 , 64 ) . trim ( ) ;
560+ this . setName ( name ) ;
561+ // const width = chunk.readUint16BE(64);
562+ // this.setWidth(width);
563+ // const height = chunk.readUint16BE(66);
564+ // this.setHeight(height);
565+ } else {
566+ const chunk = this . videoSocket . stream . read ( 68 ) as Buffer ;
567+ if ( ! chunk )
568+ throw Error ( 'fail to read firstChunk, inclease tunnelDelay for this device.' ) ;
569+ const name = chunk . toString ( 'utf8' , 0 , 64 ) . trim ( ) ;
570+ this . setName ( name ) ;
571+ const width = chunk . readUint16BE ( 64 ) ;
572+ this . setWidth ( width ) ;
573+ const height = chunk . readUint16BE ( 66 ) ;
574+ this . setHeight ( height ) ;
575+ }
576+
464577 // let header: Uint8Array | undefined;
465578
466579 let pts = BigInt ( 0 ) ; // Buffer.alloc(0);
@@ -475,12 +588,20 @@ export default class Scrcpy extends EventEmitter {
475588 // regular end condition
476589 return ;
477590 }
478- // console.log(frameMeta.toString('hex').replace(/(........)/g, '$1 '))
479- pts = frameMeta . readBigUint64BE ( ) ;
480- len = frameMeta . readUInt32BE ( 8 ) ;
481- // else {bufferInfo.presentationTimeUs - ptsOrigin}
482- // debug(`\tHeader:PTS =`, pts);
483- // debug(`\tHeader:len =`, len);
591+ if ( strVersion >= "02.00.00" ) {
592+ const codecId = frameMeta . readUInt32BE ( 0 ) ;
593+ // Read width (4 bytes)
594+ const width = frameMeta . readUInt32BE ( 4 ) ;
595+ // Read height (4 bytes)
596+ const height = frameMeta . readUInt32BE ( 8 ) ;
597+ this . setWidth ( width ) ;
598+ this . setHeight ( height ) ;
599+ } else {
600+ pts = frameMeta . readBigUint64BE ( ) ;
601+ len = frameMeta . readUInt32BE ( 8 ) ;
602+ // debug(`\tHeader:PTS =`, pts);
603+ // debug(`\tHeader:len =`, len);
604+ }
484605 }
485606
486607 const config = ! ! ( pts & PACKET_FLAG_CONFIG ) ;
0 commit comments