From ed6ef8d2ec3dbf5b2f78d4d2a303103df1861e42 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 07:42:48 +0100 Subject: [PATCH 01/24] feat: reenable bluetooth --- index.js | 23 +++++++++++++++++++---- package.json | 5 +++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 1dc100c..f93c7d0 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ var stream = require('stream'); var mdns = require('mdns-js'); var fs = require('fs'); var AirTunes = require('airtunes2'); +var blue = require('bluetoothctl'); var airtunes = new AirTunes(); @@ -64,18 +65,32 @@ pcmDeviceSearch(); var pcmDeviceSearchLoop = setInterval(pcmDeviceSearch, 10000); // Watch for new Bluetooth devices -/*blue.Bluetooth(); +blue.Bluetooth(); + +setTimeout(function(){ + blue.getPairedDevices() + availableBluetoothInputs = []; + for (var device of blue.devices){ + availableBluetoothInputs.push({ + 'name': 'Bluetooth: '+device.name, + 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=2000' + }); + } + updateAllInputs(); + console.log(blue.devices); +}, 10000); + blue.on(blue.bluetoothEvents.Device, function (devices) { console.log('devices:' + JSON.stringify(devices,null,2)); availableBluetoothInputs = []; for (var device of blue.devices){ availableBluetoothInputs.push({ 'name': 'Bluetooth: '+device.name, - 'id': 'bluealsa:HCI=hci0,DEV='+device.mac+',PROFILE=a2dp,DELAY=10000' + 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=2000' }); } updateAllInputs(); -})*/ +}) function updateAllInputs(){ var defaultInputs = [ @@ -269,4 +284,4 @@ io.on('connection', function(socket){ http.listen(3000, function(){ console.log('listening on *:3000'); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 164f70d..d1691b4 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,10 @@ "version": "0.0.1", "description": "Add line-in and Bluetooth input to the HomePod (or other AirPlay speakers). Intended to run on Raspberry Pi.", "dependencies": { - "airtunes2": "git://github.com/ciderapp/node_airtunes2.git#a8df031a3500f3577733cea8badeb136e5362f49", + "airtunes2": "git://github.com/ciderapp/node_airtunes2.git#v2.4.9", + "bluetoothctl": "https://github.com/DerDaku/node-bluetoothctl.git", "express": "^4.17.1", "mdns-js": "^1.0.3", "socket.io": "^2.2.0" } -} \ No newline at end of file +} From 19b48d6e9c883ce3053b115d9ca9656d7826adf1 Mon Sep 17 00:00:00 2001 From: Ben Lachman Date: Wed, 20 Sep 2023 08:45:30 +0200 Subject: [PATCH 02/24] feat: add first stereo pairs support --- index.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index f93c7d0..56992bc 100644 --- a/index.js +++ b/index.js @@ -33,6 +33,7 @@ var currentOutput = "void"; var inputStream = new FromVoid(); var outputStream = new ToVoid(); var airplayDevice = null; +var stereoAirplayDevice = null; var arecordInstance = null; var aplayInstance = null; var volume = 20; @@ -110,7 +111,8 @@ function updateAllOutputs(){ { 'name': 'None', 'id': 'void', - 'type': 'void' + 'type': 'void', + 'stereo': 'void' } ]; availableOutputs = defaultOutputs.concat(availablePcmOutputs, availableAirplayOutputs); @@ -119,26 +121,67 @@ function updateAllOutputs(){ } updateAllOutputs(); -var browser = mdns.createBrowser(mdns.tcp('raop')); +// var browser = mdns.createBrowser(mdns.tcp('raop')); +// browser.on('ready', function () { +// browser.discover(); +// }); +// browser.on('update', function (data) { +// // console.log("service up: ", data); +// // console.log(service.addresses); +// // console.log(data.fullname); +// if (data.fullname){ +// var splitName = /([^@]+)@(.*)\._raop\._tcp\.local/.exec(data.fullname); +// if (splitName != null && splitName.length > 1){ +// var id = 'airplay_'+data.addresses[0]+'_'+data.port; + +// if (!availableAirplayOutputs.some(e => e.id === id)) { +// availableAirplayOutputs.push({ +// 'name': 'AirPlay: ' + splitName[2], +// 'id': id, +// 'type': 'airplay', +// // 'address': service.addresses[1], +// // 'port': service.port, +// // 'host': service.host +// }); +// updateAllOutputs(); +// } +// } +// } +// // console.log(airplayDevices); +// }); +// // browser.on('serviceDown', function(service) { +// // console.log("service down: ", service); +// // }); + +var browser = mdns.createBrowser(mdns.tcp('airplay')); browser.on('ready', function () { browser.discover(); }); + browser.on('update', function (data) { // console.log("service up: ", data); // console.log(service.addresses); // console.log(data.fullname); if (data.fullname){ - var splitName = /([^@]+)@(.*)\._raop\._tcp\.local/.exec(data.fullname); + var splitName = /(.*)\._airplay\._tcp\.local/.exec(data.fullname); if (splitName != null && splitName.length > 1){ var id = 'airplay_'+data.addresses[0]+'_'+data.port; + var stereoName = null; if (!availableAirplayOutputs.some(e => e.id === id)) { + data.txt.forEach( txtValue => { + if (txtValue.startsWith("gpn=")) { + stereoName = txtValue.substring(4) + } + }); + availableAirplayOutputs.push({ - 'name': 'AirPlay: ' + splitName[2], + 'name': 'AirPlay: ' + splitName[1], 'id': id, - 'type': 'airplay' - // 'address': service.addresses[1], - // 'port': service.port, + 'type': 'airplay', + 'stereo': stereoName, + 'host': data.addresses[0], + 'port': data.port, // 'host': service.host }); updateAllOutputs(); @@ -169,6 +212,13 @@ function cleanupCurrentOutput(){ }) airplayDevice = null; } + if( stereoAirplayDevice !== null ) { + stereoAirplayDevice.stop(function(){ + console.log('stopped airplay device'); + }) + stereoAirplayDevice = null; + } + if (aplayInstance !== null){ aplayInstance.kill(); aplayInstance = null; @@ -202,6 +252,12 @@ io.on('connection', function(socket){ console.log('changed airplay volume'); }); } + if( stereoAirplayDevice !== null ){ + stereoAirplayDevice.setVolume(volume, function(){ + console.log('changed airplay volume'); + }); + } + if (aplayInstance !== null){ console.log('todo: update correct speaker based on currentOutput device ID'); console.log(currentOutput); @@ -220,12 +276,23 @@ io.on('connection', function(socket){ cleanupCurrentOutput(); // TODO: rewrite how devices are stored to avoid the array split thingy - if (msg.startsWith("airplay")){ - var split = msg.split("_"); - var host = split[1]; - var port = split[2]; - console.log('adding device: ' + host + ':' + port); - airplayDevice = airtunes.add(host, {port: port, volume: volume}); + if (msg.startsWith("airplay")) { + var isStereo = false; + selectedOutput = availableAirplayOutputs.find(output => output.id === msg); + + if( selectedOutput.stereo !== null ) { + isStereo = true; + stereoOutput = availableAirplayOutputs.find(output => output.id !== selectedOutput.id && output.stereo === selectedOutput.stereo); + } + + console.log('adding device: ' + selectedOutput.host + ':' + selectedOutput.port); + airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume, stereo: isStereo}); + + if( isStereo && stereoOutput !== null ) { + console.log('adding stereo device: ' + stereoOutput.host + ':' + stereoOutput.port); + stereoAirplayDevice = airtunes.add(stereoOutput.host, {port: stereoOutput.port, volume: volume, stereo: isStereo}); + } + airplayDevice.on('status', function(status) { console.log('airplay status: ' + status); if(status === 'ready'){ @@ -240,6 +307,21 @@ io.on('connection', function(socket){ setTimeout(() => { airplayDevice.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); } }); + + stereoAirplayDevice.on('status', function(status) { + console.log('airplay status: ' + status); + if(status === 'ready'){ + outputStream = airtunes; + inputStream.pipe(outputStream).on('error', logPipeError); + + // at this moment the rtsp setup is not fully done yet and the status + // is still SETVOLUME. There's currently no way to check if setup is + // completed, so we just wait a second before setting the track info. + // Unfortunately we don't have the fancy input name here. Will get fixed + // with a better way of storing devices. + setTimeout(() => { stereoAirplayDevice.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); + } + }); } if (msg.startsWith("plughw:")){ aplayInstance = spawn("aplay", [ From 0cf92ac37b6242b8a45de688b3db904e0b72a678 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 08:59:02 +0100 Subject: [PATCH 03/24] fix: enhance stereo pair support, fix too many handlers --- index.js | 76 +++++++++++++++++--------------------------------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/index.js b/index.js index 56992bc..144f0d1 100644 --- a/index.js +++ b/index.js @@ -70,15 +70,6 @@ blue.Bluetooth(); setTimeout(function(){ blue.getPairedDevices() - availableBluetoothInputs = []; - for (var device of blue.devices){ - availableBluetoothInputs.push({ - 'name': 'Bluetooth: '+device.name, - 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=2000' - }); - } - updateAllInputs(); - console.log(blue.devices); }, 10000); blue.on(blue.bluetoothEvents.Device, function (devices) { @@ -87,7 +78,7 @@ blue.on(blue.bluetoothEvents.Device, function (devices) { for (var device of blue.devices){ availableBluetoothInputs.push({ 'name': 'Bluetooth: '+device.name, - 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=2000' + 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=10000' }); } updateAllInputs(); @@ -166,7 +157,7 @@ browser.on('update', function (data) { var splitName = /(.*)\._airplay\._tcp\.local/.exec(data.fullname); if (splitName != null && splitName.length > 1){ var id = 'airplay_'+data.addresses[0]+'_'+data.port; - var stereoName = null; + var stereoName = false; if (!availableAirplayOutputs.some(e => e.id === id)) { data.txt.forEach( txtValue => { @@ -202,6 +193,21 @@ function cleanupCurrentInput(){ } } +function statHandler(status) { + console.log('airplay status: ' + status); + if(status === 'ready'){ + outputStream = airtunes; + inputStream.pipe(outputStream).on('error', logPipeError); + + // at this moment the rtsp setup is not fully done yet and the status + // is still SETVOLUME. There's currently no way to check if setup is + // completed, so we just wait a second before setting the track info. + // Unfortunately we don't have the fancy input name here. Will get fixed + // with a better way of storing devices. + setTimeout(() => { airplayDevice.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); + } +} + function cleanupCurrentOutput(){ console.log("inputStream", inputStream); console.log("outputStream", outputStream); @@ -210,6 +216,7 @@ function cleanupCurrentOutput(){ airplayDevice.stop(function(){ console.log('stopped airplay device'); }) + airplayDevice.off('status', statHandler) airplayDevice = null; } if( stereoAirplayDevice !== null ) { @@ -231,6 +238,8 @@ app.get('/', function(req, res){ let logPipeError = function(e) {console.log('inputStream.pipe error: ' + e.message)}; + + io.on('connection', function(socket){ console.log('a user connected'); // set current state @@ -269,7 +278,7 @@ io.on('connection', function(socket){ } io.emit('changed_output_volume', msg); }); - + socket.on('switch_output', function(msg){ console.log('switch_output: ' + msg); currentOutput = msg; @@ -277,51 +286,12 @@ io.on('connection', function(socket){ // TODO: rewrite how devices are stored to avoid the array split thingy if (msg.startsWith("airplay")) { - var isStereo = false; selectedOutput = availableAirplayOutputs.find(output => output.id === msg); - if( selectedOutput.stereo !== null ) { - isStereo = true; - stereoOutput = availableAirplayOutputs.find(output => output.id !== selectedOutput.id && output.stereo === selectedOutput.stereo); - } - console.log('adding device: ' + selectedOutput.host + ':' + selectedOutput.port); - airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume, stereo: isStereo}); + airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume}) - if( isStereo && stereoOutput !== null ) { - console.log('adding stereo device: ' + stereoOutput.host + ':' + stereoOutput.port); - stereoAirplayDevice = airtunes.add(stereoOutput.host, {port: stereoOutput.port, volume: volume, stereo: isStereo}); - } - - airplayDevice.on('status', function(status) { - console.log('airplay status: ' + status); - if(status === 'ready'){ - outputStream = airtunes; - inputStream.pipe(outputStream).on('error', logPipeError); - - // at this moment the rtsp setup is not fully done yet and the status - // is still SETVOLUME. There's currently no way to check if setup is - // completed, so we just wait a second before setting the track info. - // Unfortunately we don't have the fancy input name here. Will get fixed - // with a better way of storing devices. - setTimeout(() => { airplayDevice.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); - } - }); - - stereoAirplayDevice.on('status', function(status) { - console.log('airplay status: ' + status); - if(status === 'ready'){ - outputStream = airtunes; - inputStream.pipe(outputStream).on('error', logPipeError); - - // at this moment the rtsp setup is not fully done yet and the status - // is still SETVOLUME. There's currently no way to check if setup is - // completed, so we just wait a second before setting the track info. - // Unfortunately we don't have the fancy input name here. Will get fixed - // with a better way of storing devices. - setTimeout(() => { stereoAirplayDevice.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); - } - }); + airplayDevice.on('status', statHandler); } if (msg.startsWith("plughw:")){ aplayInstance = spawn("aplay", [ From ef09bdc190d211f7948266574a592cbda76976e6 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 12:23:07 +0200 Subject: [PATCH 04/24] feat: keep airtunes stream open and fix stereo --- index.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 144f0d1..c5be2ac 100644 --- a/index.js +++ b/index.js @@ -158,12 +158,15 @@ browser.on('update', function (data) { if (splitName != null && splitName.length > 1){ var id = 'airplay_'+data.addresses[0]+'_'+data.port; var stereoName = false; + var tv = false; if (!availableAirplayOutputs.some(e => e.id === id)) { data.txt.forEach( txtValue => { if (txtValue.startsWith("gpn=")) { stereoName = txtValue.substring(4) - } + } else if (txtValue.startsWith("model=")) { + tv = txtValue.includes('AppleTV'); + } }); availableAirplayOutputs.push({ @@ -173,6 +176,7 @@ browser.on('update', function (data) { 'stereo': stereoName, 'host': data.addresses[0], 'port': data.port, + 'tv': tv // 'host': service.host }); updateAllOutputs(); @@ -197,7 +201,7 @@ function statHandler(status) { console.log('airplay status: ' + status); if(status === 'ready'){ outputStream = airtunes; - inputStream.pipe(outputStream).on('error', logPipeError); + inputStream.pipe(outputStream, {end: false}).on('error', logPipeError); // at this moment the rtsp setup is not fully done yet and the status // is still SETVOLUME. There's currently no way to check if setup is @@ -286,10 +290,22 @@ io.on('connection', function(socket){ // TODO: rewrite how devices are stored to avoid the array split thingy if (msg.startsWith("airplay")) { + var isStereo = false; selectedOutput = availableAirplayOutputs.find(output => output.id === msg); + if( selectedOutput.stereo !== false ) { + isStereo = true; + selectedOutput = availableAirplayOutputs.find(output => output.stereo === selectedOutput.stereo && output.tv === false); + stereoOutput = availableAirplayOutputs.find(output => output.id !== selectedOutput.id && output.stereo === selectedOutput.stereo && output.tv === false); + } + console.log('adding device: ' + selectedOutput.host + ':' + selectedOutput.port); - airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume}) + airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume, stereo: isStereo}) + + if( isStereo && stereoOutput !== null ) { + console.log('adding stereo device: ' + stereoOutput.host + ':' + stereoOutput.port); + stereoAirplayDevice = airtunes.add(stereoOutput.host, {port: stereoOutput.port, volume: volume, stereo: isStereo}); + } airplayDevice.on('status', statHandler); } @@ -317,7 +333,10 @@ io.on('connection', function(socket){ cleanupCurrentInput(); if (msg === "void"){ inputStream = new FromVoid(); - inputStream.pipe(outputStream).on('error', logPipeError); + if (outputStream === airtunes) + inputStream.pipe(outputStream, {end: false}).on('error', logPipeError); + else + inputStream.pipe(outputStream).on('error', logPipeError); } if (msg !== "void"){ arecordInstance = spawn("arecord", [ From 69839645792519e987c662e88b862961219c1637 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 13:45:42 +0200 Subject: [PATCH 05/24] feat: connect to selected bluetooth device --- index.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index c5be2ac..fa25ab7 100644 --- a/index.js +++ b/index.js @@ -78,7 +78,9 @@ blue.on(blue.bluetoothEvents.Device, function (devices) { for (var device of blue.devices){ availableBluetoothInputs.push({ 'name': 'Bluetooth: '+device.name, - 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=10000' + 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=10000', + 'mac': device.mac, + 'connected': device.connected == 'yes' }); } updateAllInputs(); @@ -339,6 +341,27 @@ io.on('connection', function(socket){ inputStream.pipe(outputStream).on('error', logPipeError); } if (msg !== "void"){ + if (msg.includes('bluealsa')) { + let theOutput = availableBluetoothInputs.find(object => object.id === msg); + if (theOutput.connected == false) { + blue.connect(theOutput.mac) + setTimeout(function() { + blue.info(theOutput.mac) + arecordInstance = spawn("arecord", [ + '-D', msg, + '-c', "2", + '-f', "S16_LE", + '-r', "44100" + ]); + inputStream = arecordInstance.stdout; + + inputStream.pipe(outputStream).on('error', logPipeError); + + io.emit('switched_input', msg); + }, 5000) + return; + } + } arecordInstance = spawn("arecord", [ '-D', msg, '-c', "2", @@ -348,6 +371,7 @@ io.on('connection', function(socket){ inputStream = arecordInstance.stdout; inputStream.pipe(outputStream).on('error', logPipeError); + } io.emit('switched_input', msg); }); From 70250a035da27dfccd49fbd8ee674cad3c1517bc Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 14:03:58 +0200 Subject: [PATCH 06/24] fix: AppleTV Detection --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index fa25ab7..b277881 100644 --- a/index.js +++ b/index.js @@ -167,7 +167,7 @@ browser.on('update', function (data) { if (txtValue.startsWith("gpn=")) { stereoName = txtValue.substring(4) } else if (txtValue.startsWith("model=")) { - tv = txtValue.includes('AppleTV'); + tv = txtValue.includes('AppleTV') || tv; } }); From d3fe7b89c4944bc5e380c4eb206d13d8e4dd775a Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 14:17:23 +0200 Subject: [PATCH 07/24] feat: reduce delay --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b277881..a9967b5 100644 --- a/index.js +++ b/index.js @@ -78,7 +78,7 @@ blue.on(blue.bluetoothEvents.Device, function (devices) { for (var device of blue.devices){ availableBluetoothInputs.push({ 'name': 'Bluetooth: '+device.name, - 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp,DELAY=10000', + 'id': 'bluealsa:SRV=org.bluealsa,DEV='+device.mac+',PROFILE=a2dp', 'mac': device.mac, 'connected': device.connected == 'yes' }); From 46832648a8c58a1da5ccebd81298d345bef7a7de Mon Sep 17 00:00:00 2001 From: DerDaku Date: Wed, 20 Sep 2023 17:27:34 +0200 Subject: [PATCH 08/24] feat: repeat mdns discovery --- index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.js b/index.js index a9967b5..5c4dfe8 100644 --- a/index.js +++ b/index.js @@ -148,7 +148,11 @@ updateAllOutputs(); var browser = mdns.createBrowser(mdns.tcp('airplay')); browser.on('ready', function () { + browser.discover(); + setInterval(function () { browser.discover(); + }, 30000); + }); browser.on('update', function (data) { From 072d78ca06239e77524c14e27ea83fc9d59dc14f Mon Sep 17 00:00:00 2001 From: DerDaku Date: Thu, 28 Sep 2023 09:38:31 +0200 Subject: [PATCH 09/24] feat: add error handling for airplay devices --- index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/index.js b/index.js index 5c4dfe8..bb4b5d6 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ var mdns = require('mdns-js'); var fs = require('fs'); var AirTunes = require('airtunes2'); var blue = require('bluetoothctl'); +const { error } = require('console'); var airtunes = new AirTunes(); @@ -218,6 +219,13 @@ function statHandler(status) { } } +function errorHandler(error) { + console.log('airplay error: ' + error); + this.stop(function() { + console.log('device was stopped') + }) +} + function cleanupCurrentOutput(){ console.log("inputStream", inputStream); console.log("outputStream", outputStream); @@ -307,10 +315,12 @@ io.on('connection', function(socket){ console.log('adding device: ' + selectedOutput.host + ':' + selectedOutput.port); airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume, stereo: isStereo}) + airplayDevice.on('error', errorHandler) if( isStereo && stereoOutput !== null ) { console.log('adding stereo device: ' + stereoOutput.host + ':' + stereoOutput.port); stereoAirplayDevice = airtunes.add(stereoOutput.host, {port: stereoOutput.port, volume: volume, stereo: isStereo}); + stereoAirplayDevice.on('error', errorHandler) } airplayDevice.on('status', statHandler); From 3f2632fa3dfe766f1609a6739d9efbd5344e47f2 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 11:54:03 +0100 Subject: [PATCH 10/24] feat: Switch to different mdns browser --- index.js | 26 +++++++++++--------------- package.json | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index bb4b5d6..843df30 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ var io = require('socket.io')(http); var spawn = require('child_process').spawn; var util = require('util'); var stream = require('stream'); -var mdns = require('mdns-js'); +var mdns = require('dnssd2'); var fs = require('fs'); var AirTunes = require('airtunes2'); var blue = require('bluetoothctl'); @@ -71,7 +71,7 @@ blue.Bluetooth(); setTimeout(function(){ blue.getPairedDevices() -}, 10000); +}, 5000); blue.on(blue.bluetoothEvents.Device, function (devices) { console.log('devices:' + JSON.stringify(devices,null,2)); @@ -147,17 +147,10 @@ updateAllOutputs(); // // console.log("service down: ", service); // // }); -var browser = mdns.createBrowser(mdns.tcp('airplay')); -browser.on('ready', function () { - browser.discover(); - setInterval(function () { - browser.discover(); - }, 30000); - -}); +var browser = mdns.Browser(mdns.tcp('airplay')); -browser.on('update', function (data) { - // console.log("service up: ", data); +browser.on('serviceUp', function (data) { + console.log("service up: ", data); // console.log(service.addresses); // console.log(data.fullname); if (data.fullname){ @@ -192,9 +185,12 @@ browser.on('update', function (data) { } // console.log(airplayDevices); }); -// browser.on('serviceDown', function(service) { -// console.log("service down: ", service); -// }); +browser.on('serviceDown', function(service) { + console.log("service down: ", service) + availableAirplayOutputs = availableAirplayOutputs.some(e => e.id !== id) +}); + +browser.start() function cleanupCurrentInput(){ inputStream.unpipe(outputStream); diff --git a/package.json b/package.json index d1691b4..c9b05d1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "airtunes2": "git://github.com/ciderapp/node_airtunes2.git#v2.4.9", "bluetoothctl": "https://github.com/DerDaku/node-bluetoothctl.git", "express": "^4.17.1", - "mdns-js": "^1.0.3", + "dnssd2": "^1.0.0", "socket.io": "^2.2.0" } } From 62c4f7ab6a818072b6d24e7cbd3a287ef695ffea Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 12:41:19 +0100 Subject: [PATCH 11/24] fix: stereo pair and TV detection --- index.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 843df30..658a030 100644 --- a/index.js +++ b/index.js @@ -161,13 +161,10 @@ browser.on('serviceUp', function (data) { var tv = false; if (!availableAirplayOutputs.some(e => e.id === id)) { - data.txt.forEach( txtValue => { - if (txtValue.startsWith("gpn=")) { - stereoName = txtValue.substring(4) - } else if (txtValue.startsWith("model=")) { - tv = txtValue.includes('AppleTV') || tv; - } - }); + + stereoName = data.txt.gpn || false + tv = data.txt.model && data.txt.model.includes('AppleTV') || tv; + availableAirplayOutputs.push({ 'name': 'AirPlay: ' + splitName[1], From 5cb93b244be145623dbd7b152658e37705e861a6 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 13:17:46 +0100 Subject: [PATCH 12/24] feat: better stereo handling --- index.js | 136 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 56 deletions(-) diff --git a/index.js b/index.js index 658a030..dcd981e 100644 --- a/index.js +++ b/index.js @@ -33,14 +33,14 @@ var currentInput = "void"; var currentOutput = "void"; var inputStream = new FromVoid(); var outputStream = new ToVoid(); -var airplayDevice = null; -var stereoAirplayDevice = null; +var airplayDevices = []; var arecordInstance = null; var aplayInstance = null; var volume = 20; var availableOutputs = []; var availablePcmOutputs = [] var availableAirplayOutputs = []; +var availableAirplayStereoOutputs = {} var availableInputs = []; var availableBluetoothInputs = []; var availablePcmInputs = []; @@ -109,7 +109,7 @@ function updateAllOutputs(){ 'stereo': 'void' } ]; - availableOutputs = defaultOutputs.concat(availablePcmOutputs, availableAirplayOutputs); + availableOutputs = defaultOutputs.concat(availablePcmOutputs, Object.values(availableAirplayStereoOutputs), availableAirplayOutputs); // todo only emit if updated io.emit('available_outputs', availableOutputs); } @@ -150,7 +150,7 @@ updateAllOutputs(); var browser = mdns.Browser(mdns.tcp('airplay')); browser.on('serviceUp', function (data) { - console.log("service up: ", data); + // console.log("service up: ", data); // console.log(service.addresses); // console.log(data.fullname); if (data.fullname){ @@ -159,32 +159,60 @@ browser.on('serviceUp', function (data) { var id = 'airplay_'+data.addresses[0]+'_'+data.port; var stereoName = false; var tv = false; - - if (!availableAirplayOutputs.some(e => e.id === id)) { - - stereoName = data.txt.gpn || false - tv = data.txt.model && data.txt.model.includes('AppleTV') || tv; - - + stereoName = data.txt.gpn || false + tv = data.txt.model && data.txt.model.includes('AppleTV') || tv + if (tv && stereoName) return + + if (stereoName) { + if (!availableAirplayStereoOutputs[stereoName]) + availableAirplayStereoOutputs[stereoName] = { + 'id': 'stereoAirplay_' + stereoName, + 'name': stereoName, + 'type': 'stereoAirplay', + 'devices': [] + } + availableAirplayStereoOutputs[stereoName].devices.push({ + 'name': 'AirPlay: ' + splitName[1], + 'id': id, + 'type': 'airplay', + 'host': data.addresses[0], + 'port': data.port + // 'host': service.host + }) + } else availableAirplayOutputs.push({ 'name': 'AirPlay: ' + splitName[1], 'id': id, 'type': 'airplay', - 'stereo': stereoName, 'host': data.addresses[0], - 'port': data.port, - 'tv': tv + 'port': data.port // 'host': service.host - }); - updateAllOutputs(); - } + }) + updateAllOutputs() } } // console.log(airplayDevices); }); browser.on('serviceDown', function(service) { - console.log("service down: ", service) - availableAirplayOutputs = availableAirplayOutputs.some(e => e.id !== id) + var id = 'airplay_'+data.addresses[0]+'_'+data.port; + var stereoName = false; + var tv = false; + if (data.fullname){ + var splitName = /(.*)\._airplay\._tcp\.local/.exec(data.fullname); + if (splitName != null && splitName.length > 1){ + var id = 'airplay_'+data.addresses[0]+'_'+data.port; + var stereoName = false; + var tv = false; + + stereoName = data.txt.gpn || false + tv = data.txt.model && data.txt.model.includes('AppleTV') || tv + if (tv && stereoName) return + if (stereoName) + availableAirplayStereoOutputs = availableAirplayStereoOutputs.some(e => e.id !== stereoName) + else + availableAirplayOutputs = availableAirplayOutputs.some(e => e.id !== id) + } + } }); browser.start() @@ -208,7 +236,7 @@ function statHandler(status) { // completed, so we just wait a second before setting the track info. // Unfortunately we don't have the fancy input name here. Will get fixed // with a better way of storing devices. - setTimeout(() => { airplayDevice.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); + setTimeout(() => { this.setTrackInfo(currentInput, 'BabelPod', '') }, 1000); } } @@ -223,19 +251,17 @@ function cleanupCurrentOutput(){ console.log("inputStream", inputStream); console.log("outputStream", outputStream); inputStream.unpipe(outputStream); - if (airplayDevice !== null) { - airplayDevice.stop(function(){ - console.log('stopped airplay device'); - }) - airplayDevice.off('status', statHandler) - airplayDevice = null; - } - if( stereoAirplayDevice !== null ) { - stereoAirplayDevice.stop(function(){ - console.log('stopped airplay device'); - }) - stereoAirplayDevice = null; - } + airplayDevices.forEach(airplayDevice => { + if (airplayDevice !== null) { + airplayDevice.stop(function(){ + console.log('stopped airplay device'); + }) + airplayDevice.off('status', statHandler) + airplayDevice = null; + } + }) + + airplayDevices = [] if (aplayInstance !== null){ aplayInstance.kill(); @@ -267,16 +293,13 @@ io.on('connection', function(socket){ socket.on('change_output_volume', function(msg){ console.log('change_output_volume: ', msg); volume = msg; - if (airplayDevice !== null) { - airplayDevice.setVolume(volume, function(){ - console.log('changed airplay volume'); - }); - } - if( stereoAirplayDevice !== null ){ - stereoAirplayDevice.setVolume(volume, function(){ - console.log('changed airplay volume'); - }); - } + airplayDevices.forEach(airplayDevice => { + if (airplayDevice !== null) { + airplayDevice.setVolume(volume, function(){ + console.log('changed airplay volume'); + }); + } + }) if (aplayInstance !== null){ console.log('todo: update correct speaker based on currentOutput device ID'); @@ -296,27 +319,28 @@ io.on('connection', function(socket){ cleanupCurrentOutput(); // TODO: rewrite how devices are stored to avoid the array split thingy + if (msg.startsWith("stereoAirplay")) { + selectedOutput = availableAirplayStereoOutputs[msg.substring(14)]; + selectedOutput.devices.forEach(device => { + console.log('adding device: ' + device.host + ':' + device.port); + var airplayDevice = airtunes.add(device.host, {port: device.port, volume: volume, stereo: true}) + airplayDevice.on('error', errorHandler) + + + airplayDevice.on('status', statHandler); + airplayDevices.push(airplayDevice) + }) + } if (msg.startsWith("airplay")) { - var isStereo = false; selectedOutput = availableAirplayOutputs.find(output => output.id === msg); - if( selectedOutput.stereo !== false ) { - isStereo = true; - selectedOutput = availableAirplayOutputs.find(output => output.stereo === selectedOutput.stereo && output.tv === false); - stereoOutput = availableAirplayOutputs.find(output => output.id !== selectedOutput.id && output.stereo === selectedOutput.stereo && output.tv === false); - } - console.log('adding device: ' + selectedOutput.host + ':' + selectedOutput.port); - airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume, stereo: isStereo}) + var airplayDevice = airtunes.add(selectedOutput.host, {port: selectedOutput.port, volume: volume, stereo: false}) airplayDevice.on('error', errorHandler) - if( isStereo && stereoOutput !== null ) { - console.log('adding stereo device: ' + stereoOutput.host + ':' + stereoOutput.port); - stereoAirplayDevice = airtunes.add(stereoOutput.host, {port: stereoOutput.port, volume: volume, stereo: isStereo}); - stereoAirplayDevice.on('error', errorHandler) - } airplayDevice.on('status', statHandler); + airplayDevices.push(airplayDevice) } if (msg.startsWith("plughw:")){ aplayInstance = spawn("aplay", [ From f72ef4fd4d8888fced71f7c0246965fe2e5217ba Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 13:27:49 +0100 Subject: [PATCH 13/24] fix: pipe stream correctly to airtunes2 --- index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index dcd981e..ef32521 100644 --- a/index.js +++ b/index.js @@ -228,8 +228,6 @@ function cleanupCurrentInput(){ function statHandler(status) { console.log('airplay status: ' + status); if(status === 'ready'){ - outputStream = airtunes; - inputStream.pipe(outputStream, {end: false}).on('error', logPipeError); // at this moment the rtsp setup is not fully done yet and the status // is still SETVOLUME. There's currently no way to check if setup is @@ -330,6 +328,10 @@ io.on('connection', function(socket){ airplayDevice.on('status', statHandler); airplayDevices.push(airplayDevice) }) + + + outputStream = airtunes; + inputStream.pipe(outputStream, {end: false}).on('error', logPipeError); } if (msg.startsWith("airplay")) { selectedOutput = availableAirplayOutputs.find(output => output.id === msg); @@ -341,6 +343,9 @@ io.on('connection', function(socket){ airplayDevice.on('status', statHandler); airplayDevices.push(airplayDevice) + + outputStream = airtunes; + inputStream.pipe(outputStream, {end: false}).on('error', logPipeError); } if (msg.startsWith("plughw:")){ aplayInstance = spawn("aplay", [ From 34551c8f40ccdd9f774699c8c3977179d337c8a6 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 11:43:45 +0100 Subject: [PATCH 14/24] fix: add correct AirPlay label to stereo devices --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index ef32521..7aeb17b 100644 --- a/index.js +++ b/index.js @@ -167,7 +167,7 @@ browser.on('serviceUp', function (data) { if (!availableAirplayStereoOutputs[stereoName]) availableAirplayStereoOutputs[stereoName] = { 'id': 'stereoAirplay_' + stereoName, - 'name': stereoName, + 'name': 'AirPlay: ' + stereoName, 'type': 'stereoAirplay', 'devices': [] } From 52bb08b28f96844701f83c6d43e36c6bc4db4b59 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 13:45:45 +0100 Subject: [PATCH 15/24] feat: add handling of mdns service changes --- index.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/index.js b/index.js index 7aeb17b..6c0ebd3 100644 --- a/index.js +++ b/index.js @@ -193,6 +193,38 @@ browser.on('serviceUp', function (data) { } // console.log(airplayDevices); }); +browser.on('serviceChanged', function(service) { + var id = 'airplay_'+data.addresses[0]+'_'+data.port; + var stereoName = false; + var tv = false; + if (data.fullname) { + var splitName = /(.*)\._airplay\._tcp\.local/.exec(data.fullname); + if (splitName != null && splitName.length > 1) { + var id = 'airplay_'+data.addresses[0]+'_'+data.port; + var stereoName = false; + var tv = false; + + stereoName = data.txt.gpn || false + tv = data.txt.model && data.txt.model.includes('AppleTV') || tv + if (tv && stereoName) return + + if (stereoName) { + var device = availableAirplayStereoOutputs[stereoName].devices.find(dev => dev.id === id) + device.name = 'AirPlay: ' + splitName[1] + device.host = service.addresses[0] + device.port = service.port + } else { + var device = availableAirplayOutputs.find(dev => dev.id === id) + device.name = 'AirPlay: ' + splitName[1] + device.host = service.addresses[0] + device.port = service.port + } + + updateAllOutputs() + } + } +}) + browser.on('serviceDown', function(service) { var id = 'airplay_'+data.addresses[0]+'_'+data.port; var stereoName = false; @@ -211,6 +243,8 @@ browser.on('serviceDown', function(service) { availableAirplayStereoOutputs = availableAirplayStereoOutputs.some(e => e.id !== stereoName) else availableAirplayOutputs = availableAirplayOutputs.some(e => e.id !== id) + + updateAllOutputs() } } }); From 307f9f192edf05e4a7b0e7e3d5f6143ad7654760 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 13:48:36 +0100 Subject: [PATCH 16/24] fix: stop logging bluetooth devices to console --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 6c0ebd3..31990db 100644 --- a/index.js +++ b/index.js @@ -74,7 +74,7 @@ setTimeout(function(){ }, 5000); blue.on(blue.bluetoothEvents.Device, function (devices) { - console.log('devices:' + JSON.stringify(devices,null,2)); + // ('devices:' + JSON.stringify(devices,null,2)); availableBluetoothInputs = []; for (var device of blue.devices){ availableBluetoothInputs.push({ From cedb755699c91b7240475299e38bb7b9e6c25785 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 13:51:14 +0100 Subject: [PATCH 17/24] fix: do not get paired devices manually fix: timeout call --- index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/index.js b/index.js index 31990db..2b3f85a 100644 --- a/index.js +++ b/index.js @@ -68,10 +68,7 @@ var pcmDeviceSearchLoop = setInterval(pcmDeviceSearch, 10000); // Watch for new Bluetooth devices blue.Bluetooth(); - -setTimeout(function(){ - blue.getPairedDevices() -}, 5000); +setTimeout(() => blue.getPairedDevices(), 5000) blue.on(blue.bluetoothEvents.Device, function (devices) { // ('devices:' + JSON.stringify(devices,null,2)); From f489d79ae87ff93320641aaeacb370f07f837abe Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 13:55:16 +0100 Subject: [PATCH 18/24] fix: use correct variable on service update and delete --- index.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 2b3f85a..4b739df 100644 --- a/index.js +++ b/index.js @@ -190,10 +190,7 @@ browser.on('serviceUp', function (data) { } // console.log(airplayDevices); }); -browser.on('serviceChanged', function(service) { - var id = 'airplay_'+data.addresses[0]+'_'+data.port; - var stereoName = false; - var tv = false; +browser.on('serviceChanged', function(data) { if (data.fullname) { var splitName = /(.*)\._airplay\._tcp\.local/.exec(data.fullname); if (splitName != null && splitName.length > 1) { @@ -208,13 +205,13 @@ browser.on('serviceChanged', function(service) { if (stereoName) { var device = availableAirplayStereoOutputs[stereoName].devices.find(dev => dev.id === id) device.name = 'AirPlay: ' + splitName[1] - device.host = service.addresses[0] - device.port = service.port + device.host = data.addresses[0] + device.port = data.port } else { var device = availableAirplayOutputs.find(dev => dev.id === id) device.name = 'AirPlay: ' + splitName[1] - device.host = service.addresses[0] - device.port = service.port + device.host = data.addresses[0] + device.port = data.port } updateAllOutputs() @@ -222,10 +219,7 @@ browser.on('serviceChanged', function(service) { } }) -browser.on('serviceDown', function(service) { - var id = 'airplay_'+data.addresses[0]+'_'+data.port; - var stereoName = false; - var tv = false; +browser.on('serviceDown', function(data) { if (data.fullname){ var splitName = /(.*)\._airplay\._tcp\.local/.exec(data.fullname); if (splitName != null && splitName.length > 1){ From 119bfa8fa18a08f4925462e4aaf99616a13182cc Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 14:02:00 +0100 Subject: [PATCH 19/24] fix: do not log streams on cleanup --- index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.js b/index.js index 4b739df..628436a 100644 --- a/index.js +++ b/index.js @@ -271,8 +271,6 @@ function errorHandler(error) { } function cleanupCurrentOutput(){ - console.log("inputStream", inputStream); - console.log("outputStream", outputStream); inputStream.unpipe(outputStream); airplayDevices.forEach(airplayDevice => { if (airplayDevice !== null) { From ff924248110c2f3e2d85617e6c8e404268bb5c0b Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 14:05:47 +0100 Subject: [PATCH 20/24] feat: enable PCM outputs only when env var is set --- index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 628436a..4a30016 100644 --- a/index.js +++ b/index.js @@ -60,11 +60,14 @@ function pcmDeviceSearch(){ updateAllInputs(); updateAllOutputs(); } -// Perform initial search for PCM devices -pcmDeviceSearch(); -// Watch for new PCM input/output devices every 10 seconds -var pcmDeviceSearchLoop = setInterval(pcmDeviceSearch, 10000); +if (process.env.PCM) { + // Perform initial search for PCM devices + pcmDeviceSearch(); + + // Watch for new PCM input/output devices every 10 seconds + var pcmDeviceSearchLoop = setInterval(pcmDeviceSearch, 10000); +} // Watch for new Bluetooth devices blue.Bluetooth(); From 0ec5c9cecb7e9465768772c103dde8caf0bcb9fc Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 14:09:43 +0100 Subject: [PATCH 21/24] feat: increase height of output selector --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 578ff8a..229a9c5 100644 --- a/index.html +++ b/index.html @@ -50,7 +50,7 @@

BabelPod

-
From 1c47bae3d26c14e9ff93d8f4e46868c6b155d381 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 14:18:39 +0100 Subject: [PATCH 22/24] feat: remove now unneeded tv detection --- index.js | 43 +++---------------------------------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 4a30016..b14e2e5 100644 --- a/index.js +++ b/index.js @@ -61,6 +61,7 @@ function pcmDeviceSearch(){ updateAllOutputs(); } +// Only if PCM devices are enabled through env variable if (process.env.PCM) { // Perform initial search for PCM devices pcmDeviceSearch(); @@ -115,37 +116,6 @@ function updateAllOutputs(){ } updateAllOutputs(); -// var browser = mdns.createBrowser(mdns.tcp('raop')); -// browser.on('ready', function () { -// browser.discover(); -// }); -// browser.on('update', function (data) { -// // console.log("service up: ", data); -// // console.log(service.addresses); -// // console.log(data.fullname); -// if (data.fullname){ -// var splitName = /([^@]+)@(.*)\._raop\._tcp\.local/.exec(data.fullname); -// if (splitName != null && splitName.length > 1){ -// var id = 'airplay_'+data.addresses[0]+'_'+data.port; - -// if (!availableAirplayOutputs.some(e => e.id === id)) { -// availableAirplayOutputs.push({ -// 'name': 'AirPlay: ' + splitName[2], -// 'id': id, -// 'type': 'airplay', -// // 'address': service.addresses[1], -// // 'port': service.port, -// // 'host': service.host -// }); -// updateAllOutputs(); -// } -// } -// } -// // console.log(airplayDevices); -// }); -// // browser.on('serviceDown', function(service) { -// // console.log("service down: ", service); -// // }); var browser = mdns.Browser(mdns.tcp('airplay')); @@ -158,10 +128,8 @@ browser.on('serviceUp', function (data) { if (splitName != null && splitName.length > 1){ var id = 'airplay_'+data.addresses[0]+'_'+data.port; var stereoName = false; - var tv = false; + stereoName = data.txt.gpn || false - tv = data.txt.model && data.txt.model.includes('AppleTV') || tv - if (tv && stereoName) return if (stereoName) { if (!availableAirplayStereoOutputs[stereoName]) @@ -199,11 +167,8 @@ browser.on('serviceChanged', function(data) { if (splitName != null && splitName.length > 1) { var id = 'airplay_'+data.addresses[0]+'_'+data.port; var stereoName = false; - var tv = false; stereoName = data.txt.gpn || false - tv = data.txt.model && data.txt.model.includes('AppleTV') || tv - if (tv && stereoName) return if (stereoName) { var device = availableAirplayStereoOutputs[stereoName].devices.find(dev => dev.id === id) @@ -228,11 +193,9 @@ browser.on('serviceDown', function(data) { if (splitName != null && splitName.length > 1){ var id = 'airplay_'+data.addresses[0]+'_'+data.port; var stereoName = false; - var tv = false; stereoName = data.txt.gpn || false - tv = data.txt.model && data.txt.model.includes('AppleTV') || tv - if (tv && stereoName) return + if (stereoName) availableAirplayStereoOutputs = availableAirplayStereoOutputs.some(e => e.id !== stereoName) else From ec2123b40d894160d3f7b28c82ab266eb9d9e0f4 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Sat, 11 Nov 2023 14:53:52 +0100 Subject: [PATCH 23/24] fix: filter array using filter instead of some --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index b14e2e5..68173c4 100644 --- a/index.js +++ b/index.js @@ -197,9 +197,9 @@ browser.on('serviceDown', function(data) { stereoName = data.txt.gpn || false if (stereoName) - availableAirplayStereoOutputs = availableAirplayStereoOutputs.some(e => e.id !== stereoName) + availableAirplayStereoOutputs = availableAirplayStereoOutputs.filter(e => e.id !== stereoName) else - availableAirplayOutputs = availableAirplayOutputs.some(e => e.id !== id) + availableAirplayOutputs = availableAirplayOutputs.filter(e => e.id !== id) updateAllOutputs() } From 95fefaac42a35440d4d6c31381ef23dde51bb430 Mon Sep 17 00:00:00 2001 From: DerDaku Date: Mon, 13 Nov 2023 13:52:29 +0100 Subject: [PATCH 24/24] fix: handle serviceDown correctly for stereo devices --- index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 68173c4..78a073e 100644 --- a/index.js +++ b/index.js @@ -196,9 +196,12 @@ browser.on('serviceDown', function(data) { stereoName = data.txt.gpn || false - if (stereoName) - availableAirplayStereoOutputs = availableAirplayStereoOutputs.filter(e => e.id !== stereoName) - else + if (stereoName && availableAirplayStereoOutputs[stereoName] && availableAirplayStereoOutputs[stereoName].devices) { + availableAirplayStereoOutputs[stereoName].devices = availableAirplayStereoOutputs[stereoName].devices.filter(e => e.id !== id) + if (availableAirplayStereoOutputs[stereoName].devices.length <= 0) { + delete availableAirplayStereoOutputs[stereoName] + } + } else if (!stereoName) availableAirplayOutputs = availableAirplayOutputs.filter(e => e.id !== id) updateAllOutputs()