External Airplay integration

I asked Claude your question and the answer is mixed:

"Yes, I think we should switch to @sonn-audio/node-airplay. But upon examining it, I found a more important issue than the choice of the library: it affects HomePods regardless of the library used.

Why change

  • Maintenance: @lox-audioserver/node-airplay-sender is archived, while the new one was published at the end of August and version 0.1.2 dates from September 22.
  • License: the new one is under Apache-2.0, like the template, and has no dependencies. The integration could therefore leave the AGPL that I had imposed on it because of the old one.
  • Quality: the author says he tested it on a HomePod (OS 27) and a BeoLab 50. It really handles AirPlay 2, automatically choosing the synchronization method that the speaker accepts (PTP or NTP). It also handles the authentication exchange of some old AirPlay 1 receivers that refuse the connection without it.
  • A strong argument: its README indicates that recent HomePods and Apple TVs (OS 27) accept an AirPlay 1 session but remain silent. The old library only did a « best-effort Â» AirPlay 2. On a recent HomePod, the current integration may therefore produce no sound.

What it costs

  • The library is young (0.1.x, single author).
  • Its interface is low-level: connection, synchronization, audio packets. The reading logic would need to be rewritten, about 250 lines in src/player.js.
  • This file is isolated from the rest: discovery, scenes, the widget, and the application tests do not change. Only the et field of the mDNS records would need to be kept in addition.

The underlying problem, which the library does not solve

I checked in Gladys’ code how it creates the container of an integration. It is on a Docker network in bridge mode, with all Linux capabilities removed. No host network mode is possible, neither for the main container nor for a sub-container, and the exposed ports are assigned randomly.

  • For HomePods/Apple TVs (PTP): the sender must serve the clock on UDP ports 319/320, and the speaker comes to query it at the address that the sender gives it. This address would be the internal IP of the container (172.x), unreachable from the local network. These devices would therefore probably remain silent.
  • For third-party speakers in NTP and AirPlay 1 receivers: it is more favorable, but nothing guarantees it, because the speaker must also recontact the sender.

I had reported it as a limitation; it is actually potentially blocking for HomePods. The real solution would be for Gladys to offer a network option for this type of integration. It would be worth discussing it with the Gladys maintainers."

Maybe keep this solution in mind and wait for it to mature a bit?

And in reality, I haven’t had the opportunity to test it yet, but it may not be possible to switch to external integration as it stands.

Yes, I had seen the lib for the « old Â» internal integration, but you might be right, we can keep an eye on it to use it later. In general, it’s always better to have a maintained lib than an old abandoned one.

Given the last sentence you posted from your Claude, yes indeed, I don’t think it’s possible. I’ll try to test it quickly, and I’ll let you know if it works

@GBoulvin It doesn’t seem to work, I’ll do more tests on Sunday with a real Homepod

[2026-10-29T21:40:02.135Z] [INFO] [app] Action test_speaker <- ext:ext-gboulvin-gladys-airplay:speaker:faee67b415d7
[2026-10-29T21:40:02.160Z] [INFO] [player] Playing on Speaker (172.16.8.255:7000, AirPlay 2)
/app/node_modules/bplist-parser/bplistParser.js:64
    throw new Error("Invalid binary plist. Expected 'bplist' at offset 0.");
    ^

Error: Invalid binary plist. Expected 'bplist' at offset 0.
    at exports.parseBuffer (/app/node_modules/bplist-parser/bplistParser.js:64:11)
    at Client.processData (/app/node_modules/@lox-audioserver/node-airplay-sender/dist/core/rtsp.js:1501:43)
    at Socket.<anonymous> (/app/node_modules/@lox-audioserver/node-airplay-sender/dist/core/rtsp.js:211:18)
    at Socket.emit (node:events:514:28)
    at addChunk (node:internal/streams/readable:569:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:520:3)
    at Readable.push (node:internal/streams/readable:400:5)
    at TCP.onStreamRead (node:internal/stream_base_commons:194:23)

According to Claude, your log suggests that you should use the new library. A new version of the integration has been released. Can you test it?

Yes, I’ll test that for you