[RFC] Android Audio Sink Binding — Play TTS/Audio on Android Devices

[RFC] Android Audio Sink Binding — Play TTS/Audio on Android Devices

Category: Development

Summary

I’d like to propose a new binding that registers Android devices (phones, tablets, Wear OS watches) as audio sinks in openHAB. This would allow TTS messages and audio streams to be played on any paired Android device running the openHAB app, similar to how the Chromecast binding exposes Chromecast devices as audio sinks.

Usage would look like:

say("Dinner is ready", "voicerss:default", "androidaudio:device:robert_pixel")
say("Alarm triggered", "voicerss:default", "androidaudio:broadcast:all")

Motivation

There are many situations where you want openHAB to speak to you on a device that’s always in your pocket — door opened alerts, temperature warnings, arrival announcements, etc. Currently this requires a dedicated speaker (Chromecast, Sonos) or relies on text-only notifications. Android phones already have speakers and are always nearby.

Proposed Architecture

The design leverages the existing openHAB Cloud notification infrastructure (FCM) with zero changes to the cloud service itself.

Flow

openHAB TTS Engine → AudioStream
    ↓
Android Audio Sink Binding (server-side, new)
    ↓ serves audio via AudioHTTPServer
    ↓ sends notification with extra fields: audioUrl, audioFormat, targetDeviceId
    ↓
openHAB Cloud (unchanged — passes payload through transparently)
    ↓ FCM data message to device(s)
    ↓
Android App (modified)
    ↓ detects audioUrl in FCM payload
    ↓ resolves URL (local connection or cloud proxy)
    ↓ plays audio via MediaPlayer

Key Design Decisions

  1. No new FCM message type — The audioUrl and targetDeviceId fields are added to the existing notification payload. The cloud already passes arbitrary fields through to FCM as data messages. The Android app just checks for the presence of audioUrl and routes to audio playback instead of showing a notification.

  2. Targeting behavior:

    • targetDeviceId present → only that device plays
    • targetDeviceId absent → all capable devices play (broadcast)
  3. URL resolution on the client side — The Android app already knows whether it has a local or remote connection. If the audio URL is relative (/audio/stream123.mp3), the app resolves it against whichever connection is available (direct LAN preferred, cloud proxy as fallback).

  4. Extended TTL for audio streams — Chromecast uses 10s TTL. Because of FCM delivery latency (0.5-3s) + app wake time + download time, we’d use 30s TTL in AudioHTTPServer.

  5. AudioSinkAsync base class — Same pattern as the Chromecast binding.

Components Affected

Component Changes Scope
openhab-addons New org.openhab.binding.androidaudio binding New add-on
openhab-cloud None
openhab-android FCM handler for audioUrl + new AudioPlaybackService Moderate
openhab-android-wear FCM support + audio playback (Phase 2) Future

Thing Types

  • androidaudio:device — a specific Android device (configured by deviceId from cloud registration)
  • androidaudio:broadcast — virtual thing that targets all capable devices

Questions for the Community

  1. Standalone binding vs Cloud Connector extension? Should this be its own binding (org.openhab.binding.androidaudio) or integrated into the existing openHAB Cloud Connector add-on? A standalone binding is architecturally cleaner but means an additional add-on to install.

  2. Payload convention — Is piggybacking on the existing notification payload (adding audioUrl/targetDeviceId fields) acceptable? Or should this use a distinct type (e.g. "playAudio") to keep concerns separated?

  3. Device discovery — Phase 1 would use manual configuration (user enters deviceId). Phase 2 could auto-discover devices from cloud registration data. Is there an existing pattern for this in other bindings?

  4. FOSS flavor — The Android app’s FOSS build uses polling instead of FCM. Audio sink would not work there. Is this acceptable, or should we design an alternative path (SSE/WebSocket)?

  5. Concurrent playback — If two audio messages arrive close together, should the second interrupt the first, queue behind it, or be discarded?

  6. Is anyone already working on something similar?

Implementation Plan

  • Phase 1 (MVP): Server binding with manual device config, AudioSink impl (MP3/WAV), Android app FCM handler + MediaPlayer playback, broadcast sink, 30s audio TTL
  • Phase 2: Auto-discovery, volume control, playback feedback, Wear OS
  • Phase 3: Streaming, queue management, priority levels, DND awareness

I have a more detailed technical spec available if anyone is interested in the implementation details (AudioSink class structure, FCM payload format, URL resolution logic, etc.).

Looking forward to feedback on the approach before I start coding. Happy to adjust the design based on community input.

The idea sounds sound to me, but the presentation looks very much AI generated. If that means that you mean to vibe code the binding too, I’d say “save your tokens”. Others might disagree, but the chance that you get AI generated code up to an “acceptable quality level” is slim to none in my view. At a minimum, you’d have to go through it all manually with eagle eyes and correct all the mistakes. If you don’t possess the knowledge to do that, there’s no way to salvage it.

  1. I much prefer integrated into the “official” core, add-ons, and apps over soemthing third party hosted elsewhere. (Same goes for the Wear app (which we are discussing on the other thread) which I’d like to see either merged with the Android app eventually or at least moved under the openHAB repo so if it becomes abandonded we are not left holding the bag.
  2. I don’t know enough technically to answer.
  3. I am not sure discovery in the other bindings would be relevant to this.
  4. It would be nice if there were parity between the Google Play version and the FOSS version, but there are technical limitations. If it’s technically feasible I think it would be good to pursue it. Maybe doesn’t need to be part of the first MVP though.
  5. What ever the current behavior is for the existing audio sinks.
  6. I don’t know of anyone working this right now. It’s been brought up in the past as many users have an old Android tablet they use as a central control display mounted to a wall.
  1. im planning a regular binding. nothing third party thing hosting. just your local OH instance, cloud connector and the official apps running on your phone/watch.

Regarding the Wear OS app my plans are to move it to openhab repo too, but this must be discussed with the @maintainers first. It was easier now to publish it under my google developer account to start the internal testing and have the first feedbacks. Phase 2 might target adding a UI editor to MainUI (e.g. new page type - Tile Layout)…

My wish (or dream) as one of the maintainers of the OH Android app would be the Wear app to use the official app in the backend (for its connection management etc. - that is, the mobile app would track connection or item changes, update the list of relevant items, and send that to the Wear app via the data API); then the Wear app works from there. Doing so will vastly improve UX as it doesn’t require the user to setup his server connection twice, and it’ll also be more efficient.
All of the above obviously applies to the final solution / end goal only, any way to get there is fine :slightly_smiling_face:

Second for the @maniac103 sounds like a better direction but the android app used as a sink its really a nice to have feature i already have some nice usage for it like water leak burglar alarm but we should also implement in Notification Channels like Urgent/High/Low

Does using such a watch require you to have an Android phone as well? Because, if true, it sounds like a better approach, if not…

Technically no (you can use a Wear OS watch with iOS), but I don’t think that distinction is relevant in practice. 99.9% of smartwatch users on iOS will have an Apple Watch.
Having that ruled out, there’s the topic of Internet access. Watches without LTE won’t have that without a companion phone app. Even for watches with LTE, setting up the app can be a hassle on the tiny watch display, so that’s usually deferred to the phone app.
So maybe the approach should be to use the phone app backend if present, and fall back to native communication otherwise, if possible (the Wear side can query for phone app presence).
I guess we should continue this discussion in the thread of the Wear app, though.