Skip to content

Commit 77990bd

Browse files
committed
Add support for metadata
1 parent 92294f1 commit 77990bd

File tree

5 files changed

+106
-14
lines changed

5 files changed

+106
-14
lines changed

index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ <h1>Improv Audio Player</h1>
9696
<div id="status-display" class="status"></div>
9797
<button class="clear-logs" id="clear-logs-btn">Clear Logs</button>
9898
</div>
99+
<div id="metadata" class="metadata"></div>
99100
</div>
100101
</div>
101102

@@ -109,6 +110,7 @@ <h1>Improv Audio Player</h1>
109110
const statusDisplay = document.getElementById("status-display");
110111
const connectionStatus = document.getElementById("connection-status");
111112
const clearLogsBtn = document.getElementById("clear-logs-btn");
113+
const metadata = document.getElementById("metadata");
112114

113115
playerIdInput.value = `player-${Math.floor(Math.random() * 1000)}`;
114116
urlInput.value = `ws://${window.location.hostname}:3001`;
@@ -239,6 +241,16 @@ <h1>Improv Audio Player</h1>
239241
url,
240242
logger,
241243
});
244+
player.on("metadata-update", (data) => {
245+
metadata.innerHTML = data
246+
? `<strong>Metadata:</strong><br><pre>${JSON.stringify(
247+
data,
248+
undefined,
249+
2,
250+
)}</pre>`
251+
: "";
252+
logger.log("Received metadata:", data);
253+
});
242254
player.on("close", (ev) => {
243255
if (!ev.expected) {
244256
logger.error("Connection closed unexpectedly");
@@ -249,6 +261,7 @@ <h1>Improv Audio Player</h1>
249261
player = null;
250262
connectButton.textContent = "Connect";
251263
connectionStatus.textContent = "Disconnected";
264+
metadata.innerHTML = "";
252265
logger.log("Disconnected from server");
253266
});
254267
player.connect();

server.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,38 @@ async function main() {
9999
wavData.channels,
100100
wavData.bitDepth,
101101
);
102+
session.sendMetadata({
103+
title: "Sample Audio",
104+
artist: "Someone on the internet",
105+
album: null,
106+
year: null,
107+
track: null,
108+
group_members: [],
109+
support_commands: [],
110+
repeat: "off",
111+
shuffle: false,
112+
});
102113
let start = performance.timeOrigin + performance.now() + 500;
103114
const timeSlice = 50; // ms
104115
const bytesPerSlice =
105116
(timeSlice / 1000) * wavData.sampleRate * wavData.channels;
106117

107118
for (let i = 0; i < wavData.audioData.length; i += bytesPerSlice) {
119+
// Mimick metadata update
120+
if (i % (bytesPerSlice * 10) === 0) {
121+
session.sendMetadata({
122+
title: `Sample Audio ${i}`,
123+
artist: "Someone on the internet",
124+
album: null,
125+
year: null,
126+
track: null,
127+
group_members: [],
128+
support_commands: [],
129+
repeat: "off",
130+
shuffle: false,
131+
});
132+
}
133+
108134
const chunk = wavData.audioData.slice(i, i + bytesPerSlice);
109135
session.sendPCMAudioChunk(
110136
chunk,

src/player/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ Fired when the server information has been updated. Event data is the server inf
4848

4949
### `session-update`
5050

51-
Fired when the session information has been updated. Event data is the session info.
51+
Fired when the session information has been updated. Event data is the session info or `null` if no session.
5252

5353
### `metadata-update`
5454

55-
Fired when the metadata has been updated. Event data is the metadata.
55+
Fired when the metadata has been updated. Event data is the metadata or `null` if no metadata.

src/player/player.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,19 @@ export class Player extends EventEmitter<Events> {
153153
break;
154154

155155
case "session/end":
156+
this.metadata = null;
156157
this.sessionInfo = null;
157158
this.logger.log("Session ended");
159+
this.fire("metadata-update", null);
158160
this.fire("session-update", null);
159-
this._sendPlayerTime();
160161
break;
161162

162163
case "metadata/update":
163164
this.metadata = this.metadata
164165
? { ...this.metadata, ...message.payload }
165166
: (message.payload as Metadata);
166167
this.fire("metadata-update", this.metadata);
168+
console.log("METADATA UPDATED", this.metadata);
167169
break;
168170

169171
case "source/time":

src/server/server-session.ts

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import {
22
SessionInfo,
33
BinaryMessageType,
44
SessionEndMessage,
5+
Metadata,
6+
MetadataUpdateMessage,
7+
ClientMessages,
8+
ServerMessages,
59
} from "../messages.js";
610
import type { Logger } from "../logging.js";
711
import { ServerClient } from "./server-client.js";
@@ -11,22 +15,57 @@ const HEADER_SIZE = 13;
1115
export class ServerSession {
1216
sessionActive: Set<string> = new Set();
1317

18+
private _lastReportedMetadata: Metadata | null = null;
19+
1420
constructor(
1521
private readonly sessionInfo: SessionInfo,
1622
private readonly clients: Map<string, ServerClient>,
1723
private readonly logger: Logger,
1824
private readonly onSessionEnd: () => void,
1925
) {}
2026

21-
end() {
22-
// Send session end message
27+
public sendMetadata(metadata: Metadata) {
28+
// we are going to send the whole metadata object if we didn't share one yet
29+
// otherwise only include the keys that are different from the last reported metadata
30+
let payload: Partial<Metadata>;
31+
32+
if (!this._lastReportedMetadata) {
33+
payload = metadata;
34+
} else {
35+
payload = {};
36+
// Find updated fields
37+
for (const key in metadata) {
38+
// @ts-ignore
39+
if (this._lastReportedMetadata[key] !== metadata[key]) {
40+
// @ts-ignore
41+
payload[key] = metadata[key];
42+
}
43+
}
44+
if (Object.keys(payload).length === 0) {
45+
return;
46+
}
47+
}
48+
this.sendMessage({
49+
type: "metadata/update",
50+
payload,
51+
});
52+
this._lastReportedMetadata = this._lastReportedMetadata
53+
? {
54+
...this._lastReportedMetadata,
55+
...payload,
56+
}
57+
: metadata;
58+
}
59+
60+
public end() {
2361
const sessionEndMessage: SessionEndMessage = {
2462
type: "session/end",
2563
payload: {
2664
sessionId: this.sessionInfo.session_id,
2765
},
2866
};
29-
67+
// Send session end message to all active clients
68+
// Avoid sendMessage as it can activate clients
3069
for (const clientId of this.sessionActive) {
3170
const client = this.clients.get(clientId);
3271
if (client && client.isReady()) {
@@ -37,7 +76,7 @@ export class ServerSession {
3776
this.onSessionEnd();
3877
}
3978

40-
writeAudioPacketHeader(
79+
public writeAudioPacketHeader(
4180
data: DataView,
4281
timestamp: number,
4382
sampleCount: number,
@@ -47,13 +86,7 @@ export class ServerSession {
4786
data.setUint32(9, sampleCount, false); // Sample count (big-endian)
4887
}
4988

50-
// Broadcast a binary message to all clients
51-
sendBinary(buffer: ArrayBuffer) {
52-
if (!this.clients.size) {
53-
this.logger.log("No active clients, skipping audio chunk");
54-
return;
55-
}
56-
89+
private *_readyClients() {
5790
for (const client of this.clients.values()) {
5891
if (!client.isReady()) {
5992
this.logger.log(`Client ${client.clientId} not ready, skipping`);
@@ -67,8 +100,26 @@ export class ServerSession {
67100
type: "session/start" as const,
68101
payload: this.sessionInfo,
69102
});
103+
if (this._lastReportedMetadata) {
104+
client.send({
105+
type: "metadata/update" as const,
106+
payload: this._lastReportedMetadata,
107+
});
108+
}
70109
this.sessionActive.add(client.clientId);
71110
}
111+
yield client;
112+
}
113+
}
114+
115+
public sendMessage(message: ServerMessages) {
116+
for (const client of this._readyClients()) {
117+
client.send(message);
118+
}
119+
}
120+
121+
sendBinary(buffer: ArrayBuffer) {
122+
for (const client of this._readyClients()) {
72123
client.sendBinary(buffer);
73124
}
74125
}

0 commit comments

Comments
 (0)