Music Together is Tamed’s collaborative listening feature. It lets multiple Tamed users share the same playback queue and stay perfectly in sync, regardless of whether they are on the same local network or connecting through the internet. The host’sDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/calmerism/Tamed/llms.txt
Use this file to discover all available pages before exploring further.
MusicService acts as the single source of truth; all participants receive the same queue, position, and play/pause state in real time.
Under the hood, Together uses a Ktor WebSocket server (TogetherServer) on the host device and a Ktor WebSocket client (TogetherClient) on each participant’s device. Synchronisation messages are serialised with kotlinx.serialization.
Architecture Overview
TogetherServer
Runs inside
MusicService on the host device. Opens a WebSocket endpoint at /together on a configurable local port. Manages participant connections, approval, and state broadcast.TogetherClient
Connects to a session via a
TogetherJoinInfo (WebSocket URL, session ID, session key). Runs on each participant device, including the host’s own client view.TogetherOnlineApi
REST API wrapper for the Tamed relay server. Allows hosting and joining sessions without requiring the participants to be on the same local network.
TogetherOnlineEndpoint
Discovers the current relay server base URL from
tamed.app, caches it for 6 hours, and constructs the correct WebSocket URL for remote connections.Server Events
TogetherServer emits a TogetherServerEvent sealed interface to MusicService for every significant participant action:
JoinRequestedis emitted whenrequireHostApprovalToJoinis enabled; the host must callapproveParticipant()to accept or reject.ControlRequestedcarries aControlRequestmessage, which contains aControlActionvariant (Play,Pause,SkipNext,SkipPrevious,SeekTo,SeekToIndex,SeekToTrack,SetRepeatMode,SetShuffleEnabled) submitted by a guest.AddTrackRequestedcarries anAddTrackRequestmessage, which contains theTogetherTrackto add and anAddTrackMode— eitherPLAY_NEXTorADD_TO_QUEUE.
Session Data Model
TogetherParticipant carries id, name, isHost, isPending, and isConnected fields. Pending participants receive a restricted RoomStateMessage with an empty queue until approved.
Join Link Format
ATogetherJoinInfo is serialised into a deep link for sharing:
TogetherLink.encode() / TogetherLink.decode() handle serialisation and deserialisation. Three link formats are supported:
- Deep link (
tamed://together?…) — the standard shareable format. - WebSocket URL (
ws://host:port/together?sid=…&key=…) — useful for manual entry. - Compact string (
host|port|sessionId|sessionKey) — minimal pipe-separated format.
Host Configuration
All host settings are persisted in DataStore and applied when starting a session:| Key | Type | Description |
|---|---|---|
TogetherDisplayNameKey | String | The name shown to participants for the host |
TogetherClientIdKey | String | A stable UUID identifying this device across sessions |
TogetherDefaultPortKey | Int | Local port the WebSocket server listens on |
TogetherAllowGuestsToAddTracksKey | Boolean | Whether guests can submit AddTrackRequest |
TogetherAllowGuestsToControlPlaybackKey | Boolean | Whether guests can send ControlRequest |
TogetherRequireHostApprovalToJoinKey | Boolean | Hold new joiners in a pending state until approved |
TogetherRoomSettings data class:
Session State Machine
TogetherSessionState is a sealed class flowing from MusicService:
| State | Description |
|---|---|
Idle | No active session |
Hosting | Local server running; join link available |
HostingOnline | Session relayed through the Tamed online server |
Joining | Client connecting to a local host |
JoiningOnline | Client connecting via online relay code |
Joined | Connected; role is either Host or Guest |
Error | Connection failed or session terminated unexpectedly |
How to Host a Session
Open Music Together
Navigate to Now Playing → Together or find Together in the main menu. The welcome screen appears on first use (
TogetherWelcomeShownKey tracks whether it has been shown).Configure your display name
Set your display name in Settings → Together → Display Name (
TogetherDisplayNameKey). This is shown to participants in the listener list.Choose hosting mode
Select Local to host directly from your device (participants must be on the same network or reach your IP) or Online to use the Tamed relay server for internet-accessible sessions.
Configure room settings
Toggle whether guests can add tracks (
TogetherAllowGuestsToAddTracksKey), control playback (TogetherAllowGuestsToControlPlaybackKey), and whether new joiners need your approval (TogetherRequireHostApprovalToJoinKey).Start the session
Tap Start Session. Tamed starts
TogetherServer (or calls TogetherOnlineApi.createSession() for online mode) and generates a join link. Share the link with friends.How to Join a Session
Receive the join link
Ask the host to share the join link. It will look like
tamed://together?host=… or a short invite code if the session is online.Tap the link or enter manually
Tapping the deep link opens Tamed directly into the join flow. Alternatively, open Music Together → Join and paste the link. The last used link is remembered in
TogetherLastJoinLinkKey.Enter your display name
Type the name you want other participants to see. Tamed uses your
TogetherDisplayNameKey as the default.Connect
TogetherClient.connect() opens a WebSocket connection, sends a ClientHello handshake with the session ID, session key, client ID, and display name, then listens for a ServerWelcome response.Security
Every session has two secrets generated at creation time:- Session ID — identifies the session (shared publicly in the join link).
- Session Key — a shared secret that authenticates every client joining the session. Any client presenting the wrong key receives an
Invalid sessionerror and is disconnected.
hostKey and guestKey values. Guests receive only the guestKey; the host retains the hostKey for administrative control.
