Skip to content

Commit

Permalink
stage
Browse files Browse the repository at this point in the history
  • Loading branch information
vapormusic committed Jan 28, 2023
1 parent e015977 commit d44bfee
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 4,160 deletions.
6 changes: 4 additions & 2 deletions example_execlient.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ ffmpeg.stderr.on('data', function(data) {
setTimeout(()=>{
const ws = new WebSocket('ws://localhost:8980');
airtunes.stdout.pipe(process.stdout);
airtunes.stderr.pipe(process.stdout);
ws.on('error', console.error);

ws.on('open', function open() {
ws.send(JSON.stringify({"type":"addDevices",
"host":"192.168.100.12",
"args":{"port":7000,
"volume":20, "airplay2": false,
"txt":["cn=0,1,2,3","da=true","et=0,3,5","ft=0x4A7FCA00,0xBC354BD0","sf=0xa0404","md=0,1,2","am=AudioAccessory5,1","pk=lolno","tp=UDP","vn=65537","vs=670.6.2","ov=16.2","vv=2"],
"volume":20, "airplay2": true ,
//"txt":["cn=0,1,2,3","da=true","et=0,3,5","ft=0x4A7FCA00,0xBC354BD0","sf=0xa0404","md=0,1,2","am=AudioAccessory5,1","pk=lolno","tp=UDP","vn=65537","vs=670.6.2","ov=16.2","vv=2"],
"txt":["cn=0,1,2,3","da=true","et=0,3,5","ft=0x4A7FCA00,0xBC354BD0","sf=0x80484","md=0,1,2","am=AudioAccessory5,1","pk=lol","tp=UDP","vn=65537","vs=670.6.2","ov=16.2","vv=2"],
"debug":true,
"forceAlac":false}}))
});
Expand Down
19 changes: 19 additions & 0 deletions examples/play_stdin.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,24 @@ function ondeviceup(name, host, port, addresses, text, airplay2 = null) {
} catch (e) {}
let host_name = addresses != null && typeof addresses == "object" && addresses.length > 0 ? addresses[0] : typeof addresses == "string" ? addresses : "";

let needPassword = false;
let needPin = false;
let transient = false;
let c = text.filter((u) => String(u).startsWith('sf='))
let statusflags = c[0] ? parseInt(c[0].substring(3)).toString(2).split('') : []
if (c.length == 0) {
c = text.filter((u) => String(u).startsWith('flags='))
statusflags = c[0] ? parseInt(c[0].substring(6)).toString(2).split('') : []
}
if (statusflags != []){
let PasswordRequired = (statusflags[statusflags.length - 1 - 7] == '1')
let PinRequired = (statusflags[statusflags.length - 1 - 3] == '1')
let OneTimePairingRequired = (statusflags[statusflags.length - 1 - 9] == '1')
needPassword = PasswordRequired;
needPin = (PinRequired || OneTimePairingRequired)
transient = (!(PasswordRequired || PinRequired || OneTimePairingRequired)) ?? true
}

if (
castDevices.findIndex((item) => {
return item != null && item.name == shown_name && item.host == host_name && item.host != "Unknown";
Expand All @@ -208,6 +226,7 @@ function ondeviceup(name, host, port, addresses, text, airplay2 = null) {
addresses: addresses,
txt: text,
airplay2: airplay2,
needPassword: needPassword,
});
// if (this.devices.indexOf(host_name) === -1) {
// this.devices.push(host_name);
Expand Down
243 changes: 130 additions & 113 deletions examples/play_stdin_sample.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,16 @@
const {Worker} = require("worker_threads");
var ab2str = require('arraybuffer-to-string')
const mdns = require("mdns-js");
var AirTunes = require('../lib/');
// var argv = require('optimist')
// .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]')
// .default('port', 5002)
// .default('volume', 30)
// .default('ft',"0x7F8AD0,0x38BCF46")
// .default('sf',"0x98404")
// .default('cn',"0,1,2,3")
// .default('et',"0,3,5")
// .default('airplay2',"0")
// .default('forceAlac', false)
// .demand(['host'])
// .argv;
// argv.txt = [
// `cn=${argv.cn}`,
// 'da=true',
// `et=${argv.et}`,
// `ft=${argv.ft}`,
// `sf=${argv.sf}`,
// 'md=0,1,2',
// 'am=AudioAccessory5,1',
// 'pk=lolno',
// 'tp=UDP',
// 'vn=65537',
// 'vs=610.20.41',
// 'ov=15.4.1',
// 'vv=2'
// ];
// console.log('pipe PCM data to play over AirTunes');
// 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)');

// Only works on OSX
// airtunes.addCoreAudio();
process.env.UV_THREADPOOL_SIZE = 6;
//console.log('adding device: ' + argv.host + ':' + argv.port);
var airtunes = new AirTunes();
//var device = airtunes.add(argv.host, argv);


// when the device is online, spawn ffmpeg to transcode the file
// device.on('status', function(status) {
// console.log('status: ' + status);
var castDevices = [];

// // if(status === 'need_password'){
// // device.setPasscode(argv.password);
// // }

// if(status !== 'ready')
// return;

// if(status == 'ready') {
// }
// // pipe data to AirTunes
// //process.stdin.pipe(airtunes);
// });
process.env.UV_THREADPOOL_SIZE = 6;

// Read data from file mirrors.raw
var fs = require('fs');
var file = fs.createReadStream('mirrors.raw');
file.pipe(airtunes);
var airtunes = new AirTunes();

// process.stdin.on('data', function (data) {
// airtunes.write(data);
// });
process.stdin.on('data', function (data) {
airtunes.write(data);
});

// monitor buffer events
airtunes.on('buffer', function(status) {
Expand All @@ -81,41 +28,19 @@ airtunes.on('buffer', function(status) {
}
});

// device.on('status', function(status) {
// process.stdin.on('data', function () {
// process.stdin.pipe(airtunes);
// process.stdin.resume();
// });

// });

// device.on('error', function(err) {
// console.log('device error: ' + err);
// process.exit(1);
// });

// setTimeout(function () {
// console.log('stopping');
// airtunes.stopAll(function () {
// console.log('all stopped');
// });
// }, 1000);

// // monitor buffer events
// airtunes.on('buffer', function(status) {
// console.log('buffer ' + status);

// // after the playback ends, give AirTunes some time to finish
// if(status === 'end') {
// console.log('playback ended, waiting for AirTunes devices');
// setTimeout(function() {
// airtunes.stopAll(function() {
// console.log('end');
// process.exit();
// });
// }, 2000);
// }
// });

// monitor buffer events
airtunes.on('buffer', function(status) {
console.log('buffer ' + status);
let status_json = {
type : "bufferStatus",
status: status ?? "",
}
if(worker != null){
worker.postMessage(JSON.stringify(status_json));
}

});

airtunes.on('device', function(key, status, desc) {
let status_json = {
Expand All @@ -139,53 +64,60 @@ var func = `
parentPort.postMessage({message: data});
});
parentPort.on("message", data => {
console.log("ass");
ws.send(data);
});
});`;
var worker = new Worker(func, {eval: true});
worker.on("message", (result) => {
parsed_data = JSON.parse(ab2str(result.message));
if (parsed_data.type == "addDevices") {
// Sample data for adding devices:
//'{"type":"addDevices",
// "host":"192.168.3.4",
// "args":{"port":7000,
// "volume":50,
// "password":3000,
// "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"],
// "airplay2":1,
// "debug":true,
// "forceAlac":false}}'
airtunes.add(parsed_data.host, parsed_data.args);
if (parsed_data.type == "scanDevices") {
// Sample data for scanning available devices:
//'{"type":"scanDevices",
// "timeout": 3000}
castDevices = [];
getAvailableDevices();
setTimeout(() => { worker.postMessage(JSON.stringify({
type : "airplayDevices", devices: castDevices}));}, parsed_data.timeout ?? 1000);
} else if (parsed_data.type == "addDevices") {
// Sample data for adding devices:
//'{"type":"addDevices",
// "host":"192.168.3.4",
// "args":{"port":7000,
// "volume":50,
// "password":3000,
// "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"],
// "airplay2":1,
// "debug":true,
// "forceAlac":false}}'
airtunes.add(parsed_data.host, parsed_data.args);
} else if (parsed_data.type == "setVolume"){
// Sample data for setting volume:
// {"type":"setVolume",
// "devicekey": "192.168.3.4:7000",
// "volume":30}
airtunes.setVolume(parsed_data.devicekey, parsed_data.volume,null);
airtunes.setVolume(parsed_data.devicekey, parsed_data.volume,function(){});
} else if (parsed_data.type == "setProgress"){
// Sample data for setting progress:
// {"type":"setProgress",
// "devicekey": "192.168.3.4:7000",
// "progress": 0,
// "duration": 0}
airtunes.setProgress(parsed_data.devicekey, parsed_data.progress, parsed_data.duration,null);
airtunes.setProgress(parsed_data.devicekey, parsed_data.progress, parsed_data.duration,function(){});
} else if (parsed_data.type == "setArtwork"){
// Sample data for setting artwork:
// {"type":"setArtwork",
// "devicekey": "192.168.3.4:7000",
// "contentType" : "image/png";
// "contentType" : "image/png",
// "artwork": "hex data"}
airtunes.setArtwork(parsed_data.devicekey, Buffer.from(parsed_data.artwork,"hex"),null);
airtunes.setArtwork(parsed_data.devicekey, Buffer.from(parsed_data.artwork,"hex"),parsed_data.contentType);
} else if (parsed_data.type == "setTrackInfo"){
// Sample data for setting track info:
// {"type":"setTrackInfo",
// "devicekey": "192.168.3.4:7000",
// "artist": "John Doe",
// "album": "John Doe Album",
// "name": "John Doe Song"}
airtunes.setTrackInfo(parsed_data.devicekey, parsed_data.name, parsed_data.artist, parsed_data.album, parsed_data.name,null);
airtunes.setTrackInfo(parsed_data.devicekey, parsed_data.name, parsed_data.artist, parsed_data.album, function(){});
} else if (parsed_data.type == "setPasscode"){
// Sample data for setting passcode:
// {"type":"setPasscode",
Expand All @@ -204,3 +136,88 @@ worker.on("message", (result) => {
}
});

function getAvailableDevices() {
const browser = mdns.createBrowser(mdns.tcp("raop"));
browser.on("ready", browser.discover);

browser.on("update", (service) => {
if (service.addresses && service.fullname && service.fullname.includes("_raop._tcp")) {
// console.log(service.txt)
console.log(
`${service.name} ${service.host}:${service.port} ${service.addresses} ${service.fullname}`
)
let itemname = service.fullname.substring(service.fullname.indexOf("@") + 1, service.fullname.indexOf("._raop._tcp"));
ondeviceup(itemname, service.host, service.port, service.addresses, service.txt);
}
});

const browser2 = mdns.createBrowser(mdns.tcp("airplay"));
browser2.on("ready", browser2.discover);

browser2.on("update", (service) => {
if (service.addresses && service.fullname && service.fullname.includes("_airplay._tcp")) {
// console.log(service.txt)
console.log(
`${service.name} ${service.host}:${service.port} ${service.addresses} ${service.fullname}`
)
let itemname = service.fullname.substring(service.fullname.indexOf("@") + 1, service.fullname.indexOf("._airplay._tcp"));
ondeviceup(itemname, service.host, service.port, service.addresses, service.txt, true);
}
});

}

function ondeviceup(name, host, port, addresses, text, airplay2 = null) {
// console.log(this.castDevices.findIndex((item) => {return (item.name == host.replace(".local","") && item.port == port )}))

let d = "";
let audiook = true;
try {
d = text.filter((u) => String(u).startsWith("features="));
if (d.length == 0) d = text.filter((u) => String(u).startsWith("ft="));
let features_set = d.length > 0 ? d[0].substring(d[0].indexOf("=") + 1).split(",") : [];
let features = [...(features_set.length > 0 ? parseInt(features_set[0]).toString(2).split("") : []), ...(features_set.length > 1 ? parseInt(features_set[1]).toString(2).split("") : [])];
if (features.length > 0) {
audiook = features[features.length - 1 - 9] == "1";
}
} catch (_) {}
if (audiook) {
let shown_name = name;
try {
let model = text.filter((u) => String(u).startsWith("model="));
let manufacturer = text.filter((u) => String(u).startsWith("manufacturer="));
let name1 = text.filter((u) => String(u).startsWith("name="));
if (name1.length > 0) {
shown_name = name1[0].split("=")[1];
} else if (manufacturer.length > 0) {
shown_name = (manufacturer.length > 0 ? manufacturer[0].substring(13) : "") + " " + (model.length > 0 ? model[0].substring(6) : "");
shown_name = shown_name.trim().length > 1 ? shown_name : (host ?? "Unknown").replace(".local", "");
}
} catch (e) {}
let host_name = addresses != null && typeof addresses == "object" && addresses.length > 0 ? addresses[0] : typeof addresses == "string" ? addresses : "";

if (
castDevices.findIndex((item) => {
return item != null && item.name == shown_name && item.host == host_name && item.host != "Unknown";
}) == -1
) {
castDevices.push({
name: shown_name,
host: host_name,
port: port,
addresses: addresses,
txt: text,
airplay2: airplay2,
});
// if (this.devices.indexOf(host_name) === -1) {
// this.devices.push(host_name);
// }
if (shown_name) {
console.log("deviceFound", host_name, shown_name);
}
} else {
console.log("deviceFound (added)", host_name, shown_name);
}
}
}

Binary file removed file.txt
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var Config = {
packets_in_buffer: 200, // increase this buffer protects against network issues
coreaudio_min_level: 5, // if CoreAudio's internal buffer drops too much, inject some silence to raise it
coreaudio_check_period: 2000, // CoreAudio buffer level check period
coreaudio_preload: 1408*50, // ~0.5s of silence pushed to CoreAudio to avoid draining AudioQueue
coreaudio_preload: 352*2*2*50, // ~0.5s of silence pushed to CoreAudio to avoid draining AudioQueue
sampling_rate: 44100, // fixed by AirTunes v2
sync_period: 126, // UDP sync packets are sent to all AirTunes devices regularly
stream_latency: 100, // audio UDP packets are flushed in bursts periodically
Expand Down
10 changes: 5 additions & 5 deletions lib/device_airtunes.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ function AirTunesDevice(host, audioOut, options, mode = 0, txt = "") {
let PinRequired = (this.statusflags[this.statusflags.length - 1 - 3] == '1')
let OneTimePairingRequired = (this.statusflags[this.statusflags.length - 1 - 9] == '1')
console.log('needPss', PasswordRequired, PinRequired, OneTimePairingRequired)
this.needPassword = (PasswordRequired || PinRequired || OneTimePairingRequired)
this.needPassword = PasswordRequired;
this.needPin = (PinRequired || OneTimePairingRequired)
this.transient = (!this.needPassword) ?? true
this.transient = (!(PasswordRequired || PinRequired || OneTimePairingRequired)) ?? true
console.log('needPss', this.needPassword)
}
console.log('transient', this.transient)
Expand All @@ -117,10 +117,10 @@ function AirTunesDevice(host, audioOut, options, mode = 0, txt = "") {
this.borkedshp = true;
}
let k = this.txt.filter((u) => String(u).startsWith('am='))
if ((k[0] ?? "").includes("AppleTV3,1") || (k[0] ?? "").includes("AirReceiver3,1") || (k[0] ?? "").includes("AirRecever3,1") || (k[0] ?? "").includes('Shairport Sync')){
if ((k[0] ?? "").includes("AppleTV3,1") || (k[0] ?? "").includes("AirReceiver3,1") || (k[0] ?? "").includes("AirRecever3,1") || (k[0] ?? "").includes('Shairport')){
this.alacEncoding = true
}
if ((k[0] ?? "").includes('Shairport Sync')){
if ((k[0] ?? "").includes('Shairport')){
// shairport sync doesn't support airplay 2 via NTP
this.airplay2 = false
}
Expand Down Expand Up @@ -392,7 +392,7 @@ function pcmToALAC(encoder, pcmData, bindings, bindingsok) {
} else {
// I only did the actual computational part, the rest that I didn't do should be realitively simple to do.
let bsize = 352, frames = 352; // Set these to whatever they should be
const p = new Uint8Array(1416); // p = *out;
const p = new Uint8Array((352 * 2 * 2) + 8); // p = *out;
const input = new Uint32Array(pcmData.length / 4);
let j = 0;
for (let i = 0; i < pcmData.length; i+=4) {
Expand Down
Loading

0 comments on commit d44bfee

Please sign in to comment.