11const { Worker} = require ( "worker_threads" ) ;
22var ab2str = require ( 'arraybuffer-to-string' )
3+ const mdns = require ( "mdns-js" ) ;
34var AirTunes = require ( '../lib/' ) ;
4- // var argv = require('optimist')
5- // .usage('Usage: $0 --host [host] --port [num] --volume [num] --password [string] --mode [mode] --airplay2 [1/0] --debug [mode] --ft [featuresHexes] --sf [statusFlags] --et [encryptionTypes] --cn [audioCodecs]')
6- // .default('port', 5002)
7- // .default('volume', 30)
8- // .default('ft',"0x7F8AD0,0x38BCF46")
9- // .default('sf',"0x98404")
10- // .default('cn',"0,1,2,3")
11- // .default('et',"0,3,5")
12- // .default('airplay2',"0")
13- // .default('forceAlac', false)
14- // .demand(['host'])
15- // .argv;
16- // argv.txt = [
17- // `cn=${argv.cn}`,
18- // 'da=true',
19- // `et=${argv.et}`,
20- // `ft=${argv.ft}`,
21- // `sf=${argv.sf}`,
22- // 'md=0,1,2',
23- // 'am=AudioAccessory5,1',
24- // 'pk=lolno',
25- // 'tp=UDP',
26- // 'vn=65537',
27- // 'vs=610.20.41',
28- // 'ov=15.4.1',
29- // 'vv=2'
30- // ];
31- // console.log('pipe PCM data to play over AirTunes');
32- // console.log('example: type sample.pcm | node play_stdin.js --host <AirTunes host>\n (yes doesnt work on windows powershell cus Microsoft is stupid, use cmd)');
33-
34- // Only works on OSX
35- // airtunes.addCoreAudio();
36- process . env . UV_THREADPOOL_SIZE = 6 ;
37- //console.log('adding device: ' + argv.host + ':' + argv.port);
38- var airtunes = new AirTunes ( ) ;
39- //var device = airtunes.add(argv.host, argv);
40-
41-
42- // when the device is online, spawn ffmpeg to transcode the file
43- // device.on('status', function(status) {
44- // console.log('status: ' + status);
5+ var castDevices = [ ] ;
456
46- // // if(status === 'need_password'){
47- // // device.setPasscode(argv.password);
48- // // }
49-
50- // if(status !== 'ready')
51- // return;
52-
53- // if(status == 'ready') {
54- // }
55- // // pipe data to AirTunes
56- // //process.stdin.pipe(airtunes);
57- // });
7+ process . env . UV_THREADPOOL_SIZE = 6 ;
588
59- // Read data from file mirrors.raw
60- var fs = require ( 'fs' ) ;
61- var file = fs . createReadStream ( 'mirrors.raw' ) ;
62- file . pipe ( airtunes ) ;
9+ var airtunes = new AirTunes ( ) ;
6310
64- // process.stdin.on('data', function (data) {
65- // airtunes.write(data);
66- // });
11+ process . stdin . on ( 'data' , function ( data ) {
12+ airtunes . write ( data ) ;
13+ } ) ;
6714
6815// monitor buffer events
6916airtunes . on ( 'buffer' , function ( status ) {
@@ -81,41 +28,19 @@ airtunes.on('buffer', function(status) {
8128 }
8229} ) ;
8330
84- // device.on('status', function(status) {
85- // process.stdin.on('data', function () {
86- // process.stdin.pipe(airtunes);
87- // process.stdin.resume();
88- // });
89-
90- // });
91-
92- // device.on('error', function(err) {
93- // console.log('device error: ' + err);
94- // process.exit(1);
95- // });
96-
97- // setTimeout(function () {
98- // console.log('stopping');
99- // airtunes.stopAll(function () {
100- // console.log('all stopped');
101- // });
102- // }, 1000);
103-
104- // // monitor buffer events
105- // airtunes.on('buffer', function(status) {
106- // console.log('buffer ' + status);
107-
108- // // after the playback ends, give AirTunes some time to finish
109- // if(status === 'end') {
110- // console.log('playback ended, waiting for AirTunes devices');
111- // setTimeout(function() {
112- // airtunes.stopAll(function() {
113- // console.log('end');
114- // process.exit();
115- // });
116- // }, 2000);
117- // }
118- // });
31+
32+ // monitor buffer events
33+ airtunes . on ( 'buffer' , function ( status ) {
34+ console . log ( 'buffer ' + status ) ;
35+ let status_json = {
36+ type : "bufferStatus" ,
37+ status : status ?? "" ,
38+ }
39+ if ( worker != null ) {
40+ worker . postMessage ( JSON . stringify ( status_json ) ) ;
41+ }
42+
43+ } ) ;
11944
12045airtunes . on ( 'device' , function ( key , status , desc ) {
12146 let status_json = {
@@ -139,53 +64,60 @@ var func = `
13964 parentPort.postMessage({message: data});
14065 });
14166 parentPort.on("message", data => {
142- console.log("ass");
14367 ws.send(data);
14468 });
14569 });` ;
14670var worker = new Worker ( func , { eval : true } ) ;
14771worker . on ( "message" , ( result ) => {
14872 parsed_data = JSON . parse ( ab2str ( result . message ) ) ;
149- if ( parsed_data . type == "addDevices" ) {
150- // Sample data for adding devices:
151- //'{"type":"addDevices",
152- // "host":"192.168.3.4",
153- // "args":{"port":7000,
154- // "volume":50,
155- // "password":3000,
156- // "txt":["tp=UDP","sm=false","sv=false","ek=1","et=0,1","md=0,1,2","cn=0,1","ch=2","ss=16","sr=44100","pw=false","vn=3","txtvers=1"],
157- // "airplay2":1,
158- // "debug":true,
159- // "forceAlac":false}}'
160- airtunes . add ( parsed_data . host , parsed_data . args ) ;
73+ if ( parsed_data . type == "scanDevices" ) {
74+ // Sample data for scanning available devices:
75+ //'{"type":"scanDevices",
76+ // "timeout": 3000}
77+ castDevices = [ ] ;
78+ getAvailableDevices ( ) ;
79+ setTimeout ( ( ) => { worker . postMessage ( JSON . stringify ( {
80+ type : "airplayDevices" , devices : castDevices } ) ) ; } , parsed_data . timeout ?? 1000 ) ;
81+ } else if ( parsed_data . type == "addDevices" ) {
82+ // Sample data for adding devices:
83+ //'{"type":"addDevices",
84+ // "host":"192.168.3.4",
85+ // "args":{"port":7000,
86+ // "volume":50,
87+ // "password":3000,
88+ // "txt":["tp=UDP","sm=false","sv=false","ek=1","et=0,1","md=0,1,2","cn=0,1","ch=2","ss=16","sr=44100","pw=false","vn=3","txtvers=1"],
89+ // "airplay2":1,
90+ // "debug":true,
91+ // "forceAlac":false}}'
92+ airtunes . add ( parsed_data . host , parsed_data . args ) ;
16193 } else if ( parsed_data . type == "setVolume" ) {
16294 // Sample data for setting volume:
16395 // {"type":"setVolume",
16496 // "devicekey": "192.168.3.4:7000",
16597 // "volume":30}
166- airtunes . setVolume ( parsed_data . devicekey , parsed_data . volume , null ) ;
98+ airtunes . setVolume ( parsed_data . devicekey , parsed_data . volume , function ( ) { } ) ;
16799 } else if ( parsed_data . type == "setProgress" ) {
168100 // Sample data for setting progress:
169101 // {"type":"setProgress",
170102 // "devicekey": "192.168.3.4:7000",
171103 // "progress": 0,
172104 // "duration": 0}
173- airtunes . setProgress ( parsed_data . devicekey , parsed_data . progress , parsed_data . duration , null ) ;
105+ airtunes . setProgress ( parsed_data . devicekey , parsed_data . progress , parsed_data . duration , function ( ) { } ) ;
174106 } else if ( parsed_data . type == "setArtwork" ) {
175107 // Sample data for setting artwork:
176108 // {"type":"setArtwork",
177109 // "devicekey": "192.168.3.4:7000",
178- // "contentType" : "image/png";
110+ // "contentType" : "image/png",
179111 // "artwork": "hex data"}
180- airtunes . setArtwork ( parsed_data . devicekey , Buffer . from ( parsed_data . artwork , "hex" ) , null ) ;
112+ airtunes . setArtwork ( parsed_data . devicekey , Buffer . from ( parsed_data . artwork , "hex" ) , parsed_data . contentType ) ;
181113 } else if ( parsed_data . type == "setTrackInfo" ) {
182114 // Sample data for setting track info:
183115 // {"type":"setTrackInfo",
184116 // "devicekey": "192.168.3.4:7000",
185117 // "artist": "John Doe",
186118 // "album": "John Doe Album",
187119 // "name": "John Doe Song"}
188- airtunes . setTrackInfo ( parsed_data . devicekey , parsed_data . name , parsed_data . artist , parsed_data . album , parsed_data . name , null ) ;
120+ airtunes . setTrackInfo ( parsed_data . devicekey , parsed_data . name , parsed_data . artist , parsed_data . album , function ( ) { } ) ;
189121 } else if ( parsed_data . type == "setPasscode" ) {
190122 // Sample data for setting passcode:
191123 // {"type":"setPasscode",
@@ -204,3 +136,88 @@ worker.on("message", (result) => {
204136 }
205137} ) ;
206138
139+ function getAvailableDevices ( ) {
140+ const browser = mdns . createBrowser ( mdns . tcp ( "raop" ) ) ;
141+ browser . on ( "ready" , browser . discover ) ;
142+
143+ browser . on ( "update" , ( service ) => {
144+ if ( service . addresses && service . fullname && service . fullname . includes ( "_raop._tcp" ) ) {
145+ // console.log(service.txt)
146+ console . log (
147+ `${ service . name } ${ service . host } :${ service . port } ${ service . addresses } ${ service . fullname } `
148+ )
149+ let itemname = service . fullname . substring ( service . fullname . indexOf ( "@" ) + 1 , service . fullname . indexOf ( "._raop._tcp" ) ) ;
150+ ondeviceup ( itemname , service . host , service . port , service . addresses , service . txt ) ;
151+ }
152+ } ) ;
153+
154+ const browser2 = mdns . createBrowser ( mdns . tcp ( "airplay" ) ) ;
155+ browser2 . on ( "ready" , browser2 . discover ) ;
156+
157+ browser2 . on ( "update" , ( service ) => {
158+ if ( service . addresses && service . fullname && service . fullname . includes ( "_airplay._tcp" ) ) {
159+ // console.log(service.txt)
160+ console . log (
161+ `${ service . name } ${ service . host } :${ service . port } ${ service . addresses } ${ service . fullname } `
162+ )
163+ let itemname = service . fullname . substring ( service . fullname . indexOf ( "@" ) + 1 , service . fullname . indexOf ( "._airplay._tcp" ) ) ;
164+ ondeviceup ( itemname , service . host , service . port , service . addresses , service . txt , true ) ;
165+ }
166+ } ) ;
167+
168+ }
169+
170+ function ondeviceup ( name , host , port , addresses , text , airplay2 = null ) {
171+ // console.log(this.castDevices.findIndex((item) => {return (item.name == host.replace(".local","") && item.port == port )}))
172+
173+ let d = "" ;
174+ let audiook = true ;
175+ try {
176+ d = text . filter ( ( u ) => String ( u ) . startsWith ( "features=" ) ) ;
177+ if ( d . length == 0 ) d = text . filter ( ( u ) => String ( u ) . startsWith ( "ft=" ) ) ;
178+ let features_set = d . length > 0 ? d [ 0 ] . substring ( d [ 0 ] . indexOf ( "=" ) + 1 ) . split ( "," ) : [ ] ;
179+ let features = [ ...( features_set . length > 0 ? parseInt ( features_set [ 0 ] ) . toString ( 2 ) . split ( "" ) : [ ] ) , ...( features_set . length > 1 ? parseInt ( features_set [ 1 ] ) . toString ( 2 ) . split ( "" ) : [ ] ) ] ;
180+ if ( features . length > 0 ) {
181+ audiook = features [ features . length - 1 - 9 ] == "1" ;
182+ }
183+ } catch ( _ ) { }
184+ if ( audiook ) {
185+ let shown_name = name ;
186+ try {
187+ let model = text . filter ( ( u ) => String ( u ) . startsWith ( "model=" ) ) ;
188+ let manufacturer = text . filter ( ( u ) => String ( u ) . startsWith ( "manufacturer=" ) ) ;
189+ let name1 = text . filter ( ( u ) => String ( u ) . startsWith ( "name=" ) ) ;
190+ if ( name1 . length > 0 ) {
191+ shown_name = name1 [ 0 ] . split ( "=" ) [ 1 ] ;
192+ } else if ( manufacturer . length > 0 ) {
193+ shown_name = ( manufacturer . length > 0 ? manufacturer [ 0 ] . substring ( 13 ) : "" ) + " " + ( model . length > 0 ? model [ 0 ] . substring ( 6 ) : "" ) ;
194+ shown_name = shown_name . trim ( ) . length > 1 ? shown_name : ( host ?? "Unknown" ) . replace ( ".local" , "" ) ;
195+ }
196+ } catch ( e ) { }
197+ let host_name = addresses != null && typeof addresses == "object" && addresses . length > 0 ? addresses [ 0 ] : typeof addresses == "string" ? addresses : "" ;
198+
199+ if (
200+ castDevices . findIndex ( ( item ) => {
201+ return item != null && item . name == shown_name && item . host == host_name && item . host != "Unknown" ;
202+ } ) == - 1
203+ ) {
204+ castDevices . push ( {
205+ name : shown_name ,
206+ host : host_name ,
207+ port : port ,
208+ addresses : addresses ,
209+ txt : text ,
210+ airplay2 : airplay2 ,
211+ } ) ;
212+ // if (this.devices.indexOf(host_name) === -1) {
213+ // this.devices.push(host_name);
214+ // }
215+ if ( shown_name ) {
216+ console . log ( "deviceFound" , host_name , shown_name ) ;
217+ }
218+ } else {
219+ console . log ( "deviceFound (added)" , host_name , shown_name ) ;
220+ }
221+ }
222+ }
223+
0 commit comments