@@ -7,7 +7,7 @@ import PromiseDuplex from 'promise-duplex';
77import DeviceClient from '../../DeviceClient.js' ;
88import Utils from '../../utils.js' ;
99import { Duplex } from 'node:stream' ;
10- import { type MotionEvent , MotionEventMap , OrientationMap , ControlMessageMap } from './ScrcpyConst.js' ;
10+ import { type MotionEvent , MotionEventMap , OrientationMap , ControlMessageMap , codexMap } from './ScrcpyConst.js' ;
1111import { KeyCodes } from '../../keycode.js' ;
1212import { BufWrite } from '../minicap/BufWrite.js' ;
1313// import ThirdUtils from '../ThirdUtils.js';
@@ -88,6 +88,7 @@ export default class Scrcpy extends EventEmitter {
8888 ///////
8989 // promise holders
9090 private _name : Promise < string > ;
91+ private _codec : Promise < string > ;
9192 private _width : Promise < number > ;
9293 private _height : Promise < number > ;
9394 private _onTermination : Promise < string > ;
@@ -96,6 +97,7 @@ export default class Scrcpy extends EventEmitter {
9697 ////////
9798 // promise resolve calls
9899
100+ private setCodec ! : ( name : string ) => void ;
99101 private setName ! : ( name : string ) => void ;
100102 private setWidth ! : ( width : number ) => void ;
101103 private setHeight ! : ( height : number ) => void ;
@@ -137,10 +139,25 @@ export default class Scrcpy extends EventEmitter {
137139 this . _name = new Promise < string > ( ( resolve ) => this . setName = resolve ) ;
138140 this . _width = new Promise < number > ( ( resolve ) => this . setWidth = resolve ) ;
139141 this . _height = new Promise < number > ( ( resolve ) => this . setHeight = resolve ) ;
142+ this . _codec = new Promise < string > ( ( resolve ) => this . setCodec = resolve ) ;
140143 this . _onTermination = new Promise < string > ( ( resolve ) => this . setFatalError = resolve ) ;
141144 this . _firstFrame = new Promise < void > ( ( resolve ) => this . setFirstFrame = resolve ) ;
145+
146+ let versionSplit = this . config . version . split ( "." ) . map ( Number ) ;
147+ if ( versionSplit . length === 2 ) {
148+ versionSplit = [ ...versionSplit , 0 ] ;
149+ }
150+ this . major = versionSplit [ 0 ] ;
151+ this . minor = versionSplit [ 1 ] ;
152+ this . patch = versionSplit [ 2 ] ;
153+ this . strVersion = `${ this . major . toString ( ) . padStart ( 2 , '0' ) } .${ this . minor . toString ( ) . padStart ( 2 , '0' ) } .${ this . patch . toString ( ) . padStart ( 2 , '0' ) } ` ;
142154 }
143155
156+ readonly strVersion : string ;
157+ readonly major : number ;
158+ readonly minor : number ;
159+ readonly patch : number ;
160+
144161 public override on = < K extends keyof IEmissions > ( event : K , listener : IEmissions [ K ] ) : this => super . on ( event , listener )
145162 public override off = < K extends keyof IEmissions > ( event : K , listener : IEmissions [ K ] ) : this => super . off ( event , listener )
146163 public override once = < K extends keyof IEmissions > ( event : K , listener : IEmissions [ K ] ) : this => super . once ( event , listener )
@@ -162,15 +179,22 @@ export default class Scrcpy extends EventEmitter {
162179 */
163180 get firstFrame ( ) : Promise < void > { return this . _firstFrame ; }
164181
182+ /**
183+ * return the used codex can be "H264", "H265", "AV1", "AAC" or "OPUS"
184+ */
185+ get codec ( ) : Promise < string > { return this . _codec ; }
186+
165187 /**
166188 * emit scrcpyServer output as Error
167189 * @param duplex
168190 * @returns
169191 */
170- async throwsErrors ( duplex : PromiseDuplex < Duplex > ) {
192+ async ListenErrors ( duplex : PromiseDuplex < Duplex > ) {
171193 try {
172194 const errors = [ ] ;
173195 for ( ; ; ) {
196+ if ( ! duplex . readable ) // the server is stoped
197+ break ;
174198 await Utils . waitforReadable ( duplex , 0 , 'wait for error from ScrcpyServer' ) ;
175199 const data = await duplex . read ( ) ;
176200 if ( data ) {
@@ -191,6 +215,7 @@ export default class Scrcpy extends EventEmitter {
191215 }
192216 // eslint-disable-next-line @typescript-eslint/no-unused-vars
193217 } catch ( e : unknown ) {
218+ // must never throw
194219 //this.emit('error', e as Error);
195220 //this.setError((e as Error).message);
196221 }
@@ -239,13 +264,6 @@ export default class Scrcpy extends EventEmitter {
239264 throw Error ( `Unsupported message type:${ type } ` ) ;
240265 }
241266 }
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- }
249267
250268 private _getStartupLine ( jarDest : string ) : string {
251269 const args : Array < string | number | boolean > = [ ] ;
@@ -269,7 +287,7 @@ export default class Scrcpy extends EventEmitter {
269287 }
270288 // args.push(this.config.version); // arg 0 Scrcpy server version
271289 //if (this.config.version <= 20) {
272- if ( versionStr <= "02.00.00" ) {
290+ if ( this . major < 2 ) {
273291 // Version 11 => 20
274292 args . push ( "info" ) ; // Log level: info, verbose...
275293 args . push ( maxSize ) ; // Max screen width (long side)
@@ -287,7 +305,7 @@ export default class Scrcpy extends EventEmitter {
287305 args . push ( encoderName || '-' ) ; // Encoder name
288306 args . push ( powerOffScreenOnClose ) ; // Power off screen after server closed
289307 } else {
290- if ( versionStr >= "02.00.00" ) {
308+ if ( this . major >= 2 ) {
291309 args . push ( `scid=${ this . config . scid } ` ) ;
292310 if ( this . config . noAudio )
293311 args . push ( `audio=false` ) ;
@@ -302,7 +320,7 @@ export default class Scrcpy extends EventEmitter {
302320 args . push ( "log_level=info" ) ;
303321 args . push ( `max_size=${ maxSize } ` ) ;
304322 args . push ( "clipboard_autosync=false" ) ; // cause crash on some newer phone and we do not use that feature.
305- if ( versionStr >= "02.00.00" ) {
323+ if ( this . major >= 2 ) {
306324 args . push ( `video_bit_rate=${ bitrate } ` ) ;
307325 } else {
308326 args . push ( `bit_rate=${ bitrate } ` ) ;
@@ -433,14 +451,14 @@ export default class Scrcpy extends EventEmitter {
433451 }
434452 if ( stdoutContent . includes ( '[server] INFO: Device: ' ) )
435453 break ;
454+ // console.log('stdoutContent:', stdoutContent);
436455 }
437456
438- this . throwsErrors ( this . scrcpyServer ) ;
457+ this . ListenErrors ( this . scrcpyServer ) . then ( ( ) => { } , ( ) => { } ) ;
439458
440459 // from V2.0 SC_SOCKET_NAME name can be change
441- const strVersion = this . strVersion ;
442460 let SC_SOCKET_NAME = 'scrcpy' ;
443- if ( strVersion >= "02.00.00" ) {
461+ if ( this . major >= 2 ) {
444462 SC_SOCKET_NAME = SC_SOCKET_NAME_PREFIX + this . config . scid ;
445463 assert ( this . config . scid . length == 8 , `scid length should be 8` ) ;
446464 }
@@ -454,13 +472,16 @@ export default class Scrcpy extends EventEmitter {
454472 // Connect videoSocket
455473 await Utils . delay ( 100 ) ;
456474 this . videoSocket = await this . client . openLocal2 ( `localabstract:${ SC_SOCKET_NAME } ` , 'first connection to scrcpy for video' ) ;
475+ this . videoSocket . stream . on ( 'error' , ( e ) => {
476+ console . error ( 'videoSocket error' , e ) ;
477+ } ) ;
457478
458479 if ( this . closed ) {
459480 this . stop ( ) ;
460481 return this ;
461482 }
462483
463- if ( strVersion >= "02.00.00" && ! this . config . noAudio ) {
484+ if ( this . major >= 2 && ! this . config . noAudio ) {
464485 // Connect audioSocket
465486 this . audioSocket = await this . client . openLocal2 ( `localabstract:${ SC_SOCKET_NAME } ` , 'first connection to scrcpy for audio' ) ;
466487 // Connect controlSocket
@@ -478,14 +499,14 @@ export default class Scrcpy extends EventEmitter {
478499 // First chunk is 69 bytes length -> 1 dummy byte, 64 bytes for deviceName, 2 bytes for width & 2 bytes for height
479500 try {
480501 await Utils . waitforReadable ( this . videoSocket , 0 , 'videoSocket 1st 1 bit chunk' ) ;
481- const firstChunk = await this . videoSocket . read ( 1 ) as Buffer ;
502+ const firstChunk = await this . videoSocket . read ( 1 ) as Uint8Array ;
482503 if ( ! firstChunk ) {
483504 throw Error ( 'fail to read firstChunk, inclease tunnelDelay for this device.' ) ;
484505 }
485506
486507 // old protocol
487- const control = ( firstChunk as unknown as Uint8Array ) . at ( 0 ) ;
488- if ( ( firstChunk as unknown as Uint8Array ) . at ( 0 ) !== 0 ) {
508+ const control = firstChunk . at ( 0 ) ;
509+ if ( firstChunk . at ( 0 ) !== 0 ) {
489510 if ( control )
490511 throw Error ( `Control code should be 0x00, receves: 0x${ control . toString ( 16 ) . padStart ( 2 , '0' ) } ` ) ;
491512 throw Error ( `Control code should be 0x00, receves nothing.` ) ;
@@ -552,7 +573,7 @@ export default class Scrcpy extends EventEmitter {
552573 assert ( this . videoSocket ) ;
553574 this . videoSocket . stream . pause ( ) ;
554575 await Utils . waitforReadable ( this . videoSocket , 0 , 'videoSocket header' ) ;
555- if ( strVersion >= "02.00.00" ) {
576+ if ( this . major >= 2 ) {
556577 const chunk = this . videoSocket . stream . read ( 64 ) as Buffer ;
557578 if ( ! chunk )
558579 throw Error ( 'fail to read firstChunk, inclease tunnelDelay for this device.' ) ;
@@ -574,7 +595,41 @@ export default class Scrcpy extends EventEmitter {
574595 this . setHeight ( height ) ;
575596 }
576597
598+ let codec = "H264" ;
577599 // let header: Uint8Array | undefined;
600+ if ( this . major >= 2 ) {
601+ const frameMeta = this . videoSocket . stream . read ( 12 ) as Buffer ;
602+ const codecId = frameMeta . readUInt32BE ( 0 ) ;
603+ // Read width (4 bytes)
604+ const width = frameMeta . readUInt32BE ( 4 ) ;
605+ // Read height (4 bytes)
606+ const height = frameMeta . readUInt32BE ( 8 ) ;
607+ switch ( codecId ) {
608+ case codexMap . H264 :
609+ codec = "H264" ;
610+ break ;
611+ case codexMap . H265 :
612+ codec = "H265" ;
613+ break ;
614+ case codexMap . AV1 :
615+ codec = "AV1" ;
616+ break ;
617+ case codexMap . OPUS :
618+ codec = "OPUS" ;
619+ break ;
620+ case codexMap . AAC :
621+ codec = "AAC" ;
622+ break ;
623+ case codexMap . RAW :
624+ codec = "RAW" ;
625+ break ;
626+ default :
627+ codec = "UNKNOWN" ;
628+ }
629+ this . setCodec ( codec ) ;
630+ this . setWidth ( width ) ;
631+ this . setHeight ( height ) ;
632+ }
578633
579634 let pts = BigInt ( 0 ) ; // Buffer.alloc(0);
580635 for ( ; ; ) {
@@ -588,34 +643,29 @@ export default class Scrcpy extends EventEmitter {
588643 // regular end condition
589644 return ;
590645 }
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- }
646+ pts = frameMeta . readBigUint64BE ( ) ;
647+ len = frameMeta . readUInt32BE ( 8 ) ;
648+ // debug(`\tHeader:PTS =`, pts);
649+ // debug(`\tHeader:len =`, len);
605650 }
606651
607652 const config = ! ! ( pts & PACKET_FLAG_CONFIG ) ;
608653
609- let streamChunk : Buffer | null = null ;
654+ let streamChunk : Uint8Array | null = null ; // Buffer
610655 while ( streamChunk === null ) {
656+ if ( ! this . videoSocket ) // the server is stoped
657+ break ;
658+ if ( ! this . videoSocket . stream . readable ) // the server is stoped
659+ break ;
611660 await Utils . waitforReadable ( this . videoSocket , 0 , 'videoSocket streamChunk' ) ;
612- streamChunk = this . videoSocket . stream . read ( len ) as Buffer ;
661+ streamChunk = this . videoSocket . stream . read ( len ) as Uint8Array ;
613662 if ( streamChunk ) {
614- if ( config ) { // non-media data packet len: 33
663+ // const chunk_Uint8Array = streamChunk as unknown as Uint8Array;
664+ if ( config ) { // non-media data packet len: 30 .. 33
615665 /**
616666 * is a config package pts have PACKET_FLAG_CONFIG flag
617667 */
618- const sequenceParameterSet = parse_sequence_parameter_set ( streamChunk as unknown as ArrayBuffer ) ;
668+ const sequenceParameterSet = parse_sequence_parameter_set ( streamChunk , codec ) ;
619669 const {
620670 profile_idc : profileIndex ,
621671 constraint_set : constraintSet ,
@@ -640,7 +690,7 @@ export default class Scrcpy extends EventEmitter {
640690 const videoConf : H264Configuration = {
641691 profileIndex, constraintSet, levelIndex, encodedWidth, encodedHeight,
642692 cropLeft, cropRight, cropTop, cropBottom, croppedWidth, croppedHeight,
643- data : streamChunk as unknown as Uint8Array ,
693+ data : streamChunk ,
644694 } ;
645695 this . lastConf = videoConf ;
646696 this . emit ( 'config' , videoConf ) ;
@@ -652,7 +702,7 @@ export default class Scrcpy extends EventEmitter {
652702 if ( keyframe ) {
653703 pts &= ~ PACKET_FLAG_KEY_FRAME ;
654704 }
655- const frame = { keyframe, pts, data : streamChunk as unknown as Uint8Array , config : this . lastConf } ;
705+ const frame = { keyframe, pts, data : streamChunk , config : this . lastConf } ;
656706 if ( this . setFirstFrame ) {
657707 this . setFirstFrame ( ) ;
658708 this . setFirstFrame = undefined ;
@@ -707,12 +757,19 @@ export default class Scrcpy extends EventEmitter {
707757 * @param position
708758 * @param screenSize
709759 * @param pressure
760+ *
761+ * see parseInjectTouchEvent()
710762 */
711763 // usb.data_len == 28
712- async injectTouchEvent ( action : MotionEvent , pointerId : bigint , position : Point , screenSize : Point , pressure ?: number ) : Promise < void > {
713- const chunk = new BufWrite ( 28 ) ;
764+ async injectTouchEvent ( action : MotionEvent , pointerId : bigint , position : Point , screenSize : Point , pressure ?: number ) : Promise < boolean > {
765+ let size = 28 ;
766+ if ( this . major >= 2 ) {
767+ size += 4 ;
768+ }
769+ const chunk = new BufWrite ( size ) ;
714770 chunk . writeUint8 ( ControlMessageMap . TYPE_INJECT_TOUCH_EVENT ) ;
715- chunk . writeUint8 ( action ) ;
771+ chunk . writeUint8 ( action ) ; // action readUnsignedByte
772+
716773 if ( pressure === undefined ) {
717774 if ( action == MotionEventMap . ACTION_UP )
718775 pressure = 0x0
@@ -722,15 +779,26 @@ export default class Scrcpy extends EventEmitter {
722779 pressure = 0xffff
723780 }
724781 // Writes a long to the underlying output stream as eight bytes, high byte first.
725- chunk . writeBigUint64BE ( pointerId ) ;
726- chunk . writeUint32BE ( position . x | 0 ) ;
727- chunk . writeUint32BE ( position . y | 0 ) ;
728- chunk . writeUint16BE ( screenSize . x | 0 ) ;
729- chunk . writeUint16BE ( screenSize . y | 0 ) ;
730- chunk . writeUint16BE ( pressure ) ;
731- chunk . writeUint32BE ( MotionEventMap . BUTTON_PRIMARY ) ;
782+ chunk . writeBigUint64BE ( pointerId ) ; // long pointerId = dis.readLong();
783+ // Position position = parsePosition();
784+ chunk . writeUint32BE ( position . x | 0 ) ; // int x = dis.readInt();
785+ chunk . writeUint32BE ( position . y | 0 ) ; // int y = dis.readInt();
786+ chunk . writeUint16BE ( screenSize . x | 0 ) ; // int screenWidth = dis.readUnsignedShort();
787+ chunk . writeUint16BE ( screenSize . y | 0 ) ; // int screenHeight = dis.readUnsignedShort();
788+ chunk . writeUint16BE ( pressure ) ; // Binary.u16FixedPointToFloat(dis.readShort());
789+ chunk . writeUint32BE ( MotionEventMap . BUTTON_PRIMARY ) ; // int actionButton = dis.readInt();
790+ if ( this . major >= 2 ) {
791+ chunk . writeUint32BE ( MotionEventMap . BUTTON_PRIMARY ) ; // int buttons = dis.readInt();
792+ }
732793 assert ( this . controlSocket ) ;
733- await this . controlSocket . write ( chunk . buffer ) ;
794+ try {
795+ await this . controlSocket . write ( chunk . buffer ) ;
796+ return true ;
797+ } catch ( e ) {
798+ debug ( `injectTouchEvent failed:` , e ) ;
799+ return false ;
800+ // if the device is not connected anymore, we can not write to the controlSocket
801+ }
734802 // console.log(chunk.buffer.toString('hex'))
735803 }
736804
0 commit comments