@jokio/sdk 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/auth.service.d.ts +3 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.html +51 -8
- package/dist/jokio.sdk.es.js +1125 -1094
- package/dist/jokio.sdk.umd.js +24 -24
- package/dist/nats.service.d.ts +13 -4
- package/dist/tts.service.d.ts +11 -9
- package/dist/voicevoxTts.d.ts +11 -0
- package/package.json +3 -1
- package/src/auth.service.ts +37 -9
- package/src/index.ts +10 -8
- package/src/nats.service.ts +59 -38
- package/src/tts.service.ts +134 -75
- package/src/voicevoxTts.ts +94 -0
- package/test/index.html +51 -8
- package/dist/edgeTts.service.d.ts +0 -13
- package/src/edgeTts.service.ts +0 -153
package/test/index.html
CHANGED
|
@@ -9,29 +9,72 @@
|
|
|
9
9
|
<body>
|
|
10
10
|
<h1>Library Test</h1>
|
|
11
11
|
|
|
12
|
-
<button onclick="playAudio()">
|
|
13
|
-
|
|
12
|
+
<button onclick="playAudio()">TTS</button>
|
|
14
13
|
<button onclick="playAudio2()">Voicevox TTS</button>
|
|
15
14
|
|
|
16
15
|
<script type="module">
|
|
17
|
-
import { jok } from '
|
|
16
|
+
// import { jok } from 'https://esm.run/@jokio/sdk';
|
|
17
|
+
import { jok, VoicevoxTtsService } from './dist/jokio.sdk.es.js';
|
|
18
|
+
|
|
18
19
|
window.jok = jok
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
window.getVoicevoxVoices = async function () {
|
|
22
|
+
const voices = await jok.voicevoxTts.getVoices()
|
|
23
|
+
|
|
24
|
+
console.log(voices)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
window.getVoices = async function () {
|
|
28
|
+
const voices = await jok.tts.getVoices()
|
|
29
|
+
|
|
30
|
+
console.log(voices)
|
|
31
|
+
}
|
|
21
32
|
|
|
22
33
|
window.playAudio = async function () {
|
|
23
|
-
const audio = await jok.
|
|
34
|
+
const audio = await jok.tts.getAudio("Hello there, how are you?", "en-US-AvaNeural")
|
|
24
35
|
|
|
25
36
|
audio.play()
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
|
|
29
39
|
window.playAudio2 = async function () {
|
|
30
|
-
const
|
|
40
|
+
const tts = new VoicevoxTtsService({ voicevoxUrl: 'https://voicevox.fly.dev' })
|
|
41
|
+
|
|
42
|
+
const audio = await tts.getAudio("こんにちわ、おげんきですか?あなたはあたまがいいですね。", 3)
|
|
31
43
|
|
|
32
44
|
audio.play()
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
window.requestEmailLogin = async function (email) {
|
|
48
|
+
const res = await jok.auth.requestEmailLogin(email, 'https://localhost:5173')
|
|
49
|
+
|
|
50
|
+
return res
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
window.completeEmailLogin = async function (email, otpCode) {
|
|
54
|
+
const res = await jok.auth.completeEmailLogin(email, otpCode)
|
|
55
|
+
|
|
56
|
+
return res
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
window.passkeyLogin = async function (displayName) {
|
|
60
|
+
const res = await jok.auth.requestPasskeyLogin(displayName)
|
|
61
|
+
|
|
62
|
+
return res
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
window.natsConnect = async function () {
|
|
66
|
+
await jok.nats.connect()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
window.natsSubscribe = async function () {
|
|
70
|
+
await jok.nats.on('dev.test', (data, ctx) => {
|
|
71
|
+
console.lg({ data, ctx })
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
window.natsPublish = async function () {
|
|
76
|
+
await jok.nats.publish('dev.test', { name: 'User', age: 11 })
|
|
77
|
+
}
|
|
35
78
|
|
|
36
79
|
window.p2pChat = async function () {
|
|
37
80
|
const k1 = await jok.crypto.createSessionKeyPair()
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class EdgeTtsService {
|
|
2
|
-
getAudioBuffer(text: string, voice: string, options?: {
|
|
3
|
-
pitch?: string;
|
|
4
|
-
rate?: string;
|
|
5
|
-
volume?: string;
|
|
6
|
-
}): Promise<ArrayBuffer>;
|
|
7
|
-
getAudio(text: string, voice: string, options?: {
|
|
8
|
-
pitch?: string;
|
|
9
|
-
rate?: string;
|
|
10
|
-
volume?: string;
|
|
11
|
-
}): Promise<HTMLAudioElement>;
|
|
12
|
-
getVoices(): Promise<any[]>;
|
|
13
|
-
}
|
package/src/edgeTts.service.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { playAudioFromBuffer } from './common/playAudioFromBuffer'
|
|
2
|
-
|
|
3
|
-
export class EdgeTtsService {
|
|
4
|
-
async getAudioBuffer(
|
|
5
|
-
text: string,
|
|
6
|
-
voice: string,
|
|
7
|
-
options?: { pitch?: string; rate?: string; volume?: string },
|
|
8
|
-
): Promise<ArrayBuffer> {
|
|
9
|
-
const audioChunks: Uint8Array[] = []
|
|
10
|
-
const reqId = crypto.randomUUID() // Supported in most modern browsers
|
|
11
|
-
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
const ws = new WebSocket(
|
|
14
|
-
`${Constants.WSS_URL}?trustedclienttoken=${Constants.TRUSTED_CLIENT_TOKEN}&ConnectionId=${reqId}`,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
ws.binaryType = 'arraybuffer'
|
|
18
|
-
|
|
19
|
-
const SSML_text = getSSML(text, voice, options)
|
|
20
|
-
|
|
21
|
-
ws.addEventListener('open', () => {
|
|
22
|
-
const configMessage = buildTTSConfigMessage()
|
|
23
|
-
ws.send(configMessage)
|
|
24
|
-
|
|
25
|
-
const speechMessage =
|
|
26
|
-
`X-RequestId:${reqId}\r\n` +
|
|
27
|
-
`Content-Type:application/ssml+xml\r\n` +
|
|
28
|
-
`X-Timestamp:${new Date().toISOString()}Z\r\n` +
|
|
29
|
-
`Path:ssml\r\n\r\n` +
|
|
30
|
-
SSML_text
|
|
31
|
-
|
|
32
|
-
ws.send(speechMessage)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
ws.addEventListener('message', event => {
|
|
36
|
-
const data = event.data
|
|
37
|
-
|
|
38
|
-
if (data instanceof ArrayBuffer) {
|
|
39
|
-
const buffer = new Uint8Array(data)
|
|
40
|
-
const needle = new TextEncoder().encode('Path:audio\r\n')
|
|
41
|
-
|
|
42
|
-
const index = findSubarray(buffer, needle)
|
|
43
|
-
if (index !== -1) {
|
|
44
|
-
const audioData = buffer.subarray(index + needle.length)
|
|
45
|
-
audioChunks.push(audioData)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const textData = new TextDecoder().decode(buffer)
|
|
49
|
-
if (textData.includes('Path:turn.end')) {
|
|
50
|
-
ws.close()
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
if (data.includes('Path:turn.end')) {
|
|
54
|
-
ws.close()
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
ws.addEventListener('close', () => {
|
|
60
|
-
if (!audioChunks.length) {
|
|
61
|
-
reject('No audio data available to save.')
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Combine all Uint8Arrays into one ArrayBuffer
|
|
66
|
-
const totalLength = audioChunks.reduce(
|
|
67
|
-
(acc, chunk) => acc + chunk.length,
|
|
68
|
-
0,
|
|
69
|
-
)
|
|
70
|
-
const finalBuffer = new Uint8Array(totalLength)
|
|
71
|
-
let offset = 0
|
|
72
|
-
for (const chunk of audioChunks) {
|
|
73
|
-
finalBuffer.set(chunk, offset)
|
|
74
|
-
offset += chunk.length
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
resolve(finalBuffer.buffer) // ArrayBuffer
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
ws.addEventListener('error', err => {
|
|
81
|
-
reject(err)
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async getAudio(
|
|
87
|
-
text: string,
|
|
88
|
-
voice: string,
|
|
89
|
-
options?: { pitch?: string; rate?: string; volume?: string },
|
|
90
|
-
) {
|
|
91
|
-
return this.getAudioBuffer(text, voice, options).then(x =>
|
|
92
|
-
playAudioFromBuffer(x),
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async getVoices(): Promise<any[]> {
|
|
97
|
-
const response = await fetch(
|
|
98
|
-
`${Constants.VOICES_URL}?trustedclienttoken=${Constants.TRUSTED_CLIENT_TOKEN}`,
|
|
99
|
-
)
|
|
100
|
-
const data = await response.json()
|
|
101
|
-
return data.map((voice: any) => ({
|
|
102
|
-
id: voice.ShortName,
|
|
103
|
-
locale: voice.Locale,
|
|
104
|
-
description: voice.FriendlyName,
|
|
105
|
-
}))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const Constants = {
|
|
110
|
-
TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',
|
|
111
|
-
WSS_URL:
|
|
112
|
-
'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1',
|
|
113
|
-
VOICES_URL:
|
|
114
|
-
'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const buildTTSConfigMessage = (): string => {
|
|
118
|
-
return (
|
|
119
|
-
`X-Timestamp:${new Date().toISOString()}Z\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n` +
|
|
120
|
-
`{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const getSSML = (
|
|
125
|
-
text: string,
|
|
126
|
-
voice: string,
|
|
127
|
-
options: any = {},
|
|
128
|
-
): string => {
|
|
129
|
-
options.pitch = options.pitch?.replace('hz', 'Hz')
|
|
130
|
-
const pitch = options.pitch || '0Hz' // this.validatePitch(options.pitch || '0Hz');
|
|
131
|
-
const rate = options.rate || '0%' // this.validateRate(options.rate || '0%');
|
|
132
|
-
const volume = options.volume || '0%' // this.validateVolume(options.volume || '0%');
|
|
133
|
-
|
|
134
|
-
return `<speak version='1.0' xml:lang='en-US'><voice name='${voice}'><prosody pitch='${pitch}' rate='${rate}' volume='${volume}'>${text}</prosody></voice></speak>`
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Helper: Finds a subarray inside another Uint8Array
|
|
138
|
-
function findSubarray(
|
|
139
|
-
haystack: Uint8Array,
|
|
140
|
-
needle: Uint8Array,
|
|
141
|
-
): number {
|
|
142
|
-
for (let i = 0; i <= haystack.length - needle.length; i++) {
|
|
143
|
-
let match = true
|
|
144
|
-
for (let j = 0; j < needle.length; j++) {
|
|
145
|
-
if (haystack[i + j] !== needle[j]) {
|
|
146
|
-
match = false
|
|
147
|
-
break
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (match) return i
|
|
151
|
-
}
|
|
152
|
-
return -1
|
|
153
|
-
}
|