Skip to content

Instantly share code, notes, and snippets.

@playerx
Last active August 18, 2025 15:00
Show Gist options
  • Select an option

  • Save playerx/e03765b03f3924a29c61536306a0c517 to your computer and use it in GitHub Desktop.

Select an option

Save playerx/e03765b03f3924a29c61536306a0c517 to your computer and use it in GitHub Desktop.
Edge-TTS low level client implementation (Deno example)
import WebSocket from "ws";
export async function textToSpeech(
text: string,
voice: string,
options: { pitch?: string; rate?: string; volume?: string }
): Promise<Buffer<ArrayBuffer>> {
const audio_stream: any[] = [];
const secMsGEC = await generateSecMsGec(
Constants.TRUSTED_CLIENT_TOKEN,
)
return new Promise((resolve, reject) => {
const reqId = crypto.randomUUID();
const url = `${Constants.WSS_URL}?TrustedClientToken=${Constants.TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGEC}&Sec-MS-GEC-Version=1-130.0.2849.68&ConnectionId=${reqId}`
const ws = new WebSocket(url);
const SSML_text = getSSML(text, voice, options);
ws.on("open", () => {
const message = buildTTSConfigMessage();
ws.send(message);
const speechMessage = `X-RequestId:${reqId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${new Date().toISOString()}Z\r\nPath:ssml\r\n\r\n${SSML_text}`;
ws.send(speechMessage);
});
ws.on("message", (data: any) => {
if (Buffer.isBuffer(data)) {
const needle = Buffer.from("Path:audio\r\n");
const uint8Data = new Uint8Array(data);
const start_ind = uint8Data.indexOf(needle[0]);
if (start_ind !== -1) {
const audioData = data.subarray(start_ind + needle.length);
audio_stream.push(audioData);
}
if (data.includes("Path:turn.end")) {
ws.close();
}
} else {
console.log(3, "not buffer");
}
});
ws.on("close", () => {
if (!audio_stream.length) {
reject("No audio data available to save.");
return;
}
const audioBuffer = Buffer.concat(audio_stream);
resolve(audioBuffer);
});
ws.on("error", (err: Error) => {
reject(err);
});
});
}
export async function getVoices(): Promise<any[]> {
const response = await fetch(
`${Constants.VOICES_URL}?trustedclienttoken=${Constants.TRUSTED_CLIENT_TOKEN}`
);
const data = await response.json();
return data.map((voice: any) => {
delete voice.VoiceTag;
delete voice.SuggestedCodec;
delete voice.Status;
return voice;
});
}
const Constants = {
TRUSTED_CLIENT_TOKEN: "6A5AA1D4EAFF4E9FB37E23D68491D6F4",
WSS_URL:
"wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1",
VOICES_URL:
"https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list",
};
const buildTTSConfigMessage = (): string => {
return (
`X-Timestamp:${new Date().toISOString()}Z\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n` +
`{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`
);
};
const getSSML = (text: string, voice: string, options: any = {}): string => {
options.pitch = options.pitch?.replace("hz", "Hz");
const pitch = options.pitch || "0Hz"; // this.validatePitch(options.pitch || '0Hz');
const rate = options.rate || "0%"; // this.validateRate(options.rate || '0%');
const volume = options.volume || "0%"; // this.validateVolume(options.volume || '0%');
return `<speak version='1.0' xml:lang='en-US'><voice name='${voice}'><prosody pitch='${pitch}' rate='${rate}' volume='${volume}'>${text}</prosody></voice></speak>`;
};
const generateSecMsGec = async (
trustedClientToken: string,
): Promise<string> => {
const ticks = Math.floor(Date.now() / 1000) + 11644473600
const rounded = ticks - (ticks % 300)
const windowsTicks = rounded * 10000000
const encoder = new TextEncoder()
const data = encoder.encode(`${windowsTicks}${trustedClientToken}`)
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()
}
const res = await textToSpeech(
text,
speaker || 'echo', //"ka-GE-EkaNeural",
{
rate: "0%", // Speech rate (range: -100% to 100%)
volume: "0%", // Speech volume (range: -100% to 100%)
pitch: "0Hz", // Voice pitch (range: -100Hz to 100Hz)
}
);
return new Response(res, {
headers: {
"content-type": "audio/mp3",
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment