streamplace streaming tools
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Cleanup, notes, fixes - Fixed potential ear destruction on reconnect - Added a lot of notes about what i want to change

Woovie 384b82d2 7f8a4dbe

+187 -7
+173 -7
src/App.vue
··· 1 - <script setup lang="ts"></script> 1 + <script setup lang="ts"> 2 + // A lot of initial code structure jacked from Julie 3 + // https://tangled.org/strings/juli.ee/3m2vdj7abbf22 4 + // My implementation focuses on making it "Vueish" and implementing TTS 5 + import { ref, onMounted } from "vue" 6 + 7 + const debug = ref(false) 8 + const websocketStatus = ref('not connected') 9 + const voices = ref([]) 10 + const selectedVoice = ref(null) 11 + const voiceText = ref('') 12 + 13 + const enableTTS = ref(false) 14 + 15 + 16 + onMounted(async () => { 17 + try { 18 + const response = await fetch('/api/voices') 19 + if (!response.ok) throw new Error('oops'); 20 + voices.value = await response.json() 21 + } catch (e) { 22 + console.log(e) 23 + } 24 + 25 + console.log(voices.value) 26 + }) 27 + 28 + async function getAudioClip(chatText: string = "", voice: string = "") { 29 + const options = { 30 + text: chatText, 31 + voice: voice 32 + } 33 + 34 + const req = new Request('/api', { 35 + method: "POST", 36 + body: JSON.stringify(options) 37 + }) 38 + 39 + try { 40 + const response = await fetch(req) 41 + if (!response.ok) throw new Error('oops') 42 + const blob = await response.blob() 43 + const url = URL.createObjectURL(blob) 44 + const audio = new Audio(url) 45 + audio.play() 46 + } catch (e) { 47 + console.log(e) 48 + } 49 + } 50 + 51 + // INFO customize these! 52 + const channel = "woovie.net" 53 + const wsEndpoint = "wss://stream.place/api/websocket/" 54 + const ttsDelay = 3000 // When first connecting, we do not want TTS to play for the initial 100 messages. This is my workaround. 3000 is my way overshooting, but should solve it pretty well. 55 + const disableRepeatNames = false 56 + 57 + const chatMessages = ref([]) 58 + 59 + let failures = 0 60 + 61 + function startChat() { 62 + websocketStatus.value = "connecting" 63 + 64 + try { 65 + const ws = new WebSocket(`${wsEndpoint}${channel}`) 66 + 67 + ws.onopen = () => { 68 + websocketStatus.value = "connected" 69 + setTimeout(() => { 70 + enableTTS.value = true 71 + console.log("enabled TTS") 72 + }, ttsDelay) 73 + } 74 + 75 + ws.onmessage = (event) => { 76 + try { 77 + const data = JSON.parse(event.data) 78 + 79 + if (data.$type === "place.stream.chat.defs#messageView") { 80 + // TODO linter mad, typing and shit. TS is neat but low prio issue 81 + // TODO if we could somehow know that we are getting the initial messages here, we could reverse the payload to ensure order. or, maybe we can get the time from when the message was sent? 82 + // TODO check if data.record.text startsWith !voice to allow user customization, store to sqlite db by DID 83 + chatMessages.value.push({ 84 + handle: data.author.handle, 85 + message: data.record.text, 86 + color: data.chatProfile.color 87 + }) 88 + if (enableTTS.value === true) { 89 + // TODO impl disableRepeatNames so if your chat is getting one guy'd, it doesn't keep saying "person said" and just says the message. it becomes repetitive. 90 + // TODO alter handle to remove common social sites (.bsky.social, maybe others if there's any big ones???) 91 + getAudioClip(`${data.author.handle} said ${data.record.text}`) 92 + } 93 + } 94 + } catch (e) { 95 + console.error("Ooops! ", e) 96 + } 97 + } 98 + 99 + ws.onerror = (error) => { 100 + console.error("Ooops! ", error) 101 + } 102 + 103 + ws.onclose = () => { 104 + console.log("closed") 105 + connectionRestartExponentialBackoff() 106 + } 107 + } catch (e) { 108 + console.error("Ooops! ", e) 109 + connectionRestartExponentialBackoff() 110 + } 111 + } 112 + 113 + // TODO i _think_ i'm calling this too often. i already removed it fro mws.onerror, but maybe it also doesn't need to be called in the catch on the websocket try? 114 + function connectionRestartExponentialBackoff() {// TODO review this function, i'm fairly certain this isn't working right, idk why tho 115 + enableTTS.value = false // We need to disable TTS, otherwise on reconnect we'll get a flood of audio 116 + websocketStatus.value = "disconnected" 117 + failures += 1 118 + console.log(`reconnecting in ${3000 * failures}ms`) 119 + setTimeout(startChat, 3000 * failures) 120 + } 121 + 122 + startChat() 123 + </script> 2 124 3 125 <template> 4 - <h1>You did it!</h1> 5 - <p> 6 - Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the 7 - documentation 8 - </p> 126 + <div id="chatContainer"> 127 + <div id="chats" v-for="chat in chatMessages"> 128 + <p class="chatLine"> 129 + <!-- TODO linter madge --> 130 + <span class="chatHandle" :style="`color: rgb(${chat.color.red}, ${chat.color.green}, ${chat.color.blue})`">{{ 131 + chat.handle }}</span> 132 + <span class="chatMessage">{{ chat.message }}</span> 133 + </p> 134 + </div> 135 + </div> 136 + <div id="debugWindow" v-if="!debug"> 137 + <p> 138 + <select name="model" v-model="selectedVoice"> 139 + <option disabled :value="null">Select a voice</option> 140 + <option v-for="(model, index) in voices" :key="index" :value="index">{{ index }}</option> 141 + </select> 142 + </p> 143 + <p> 144 + <input v-model="voiceText" type="text" placeholder="Enter text"> 145 + </p> 146 + <p> 147 + <button v-on:click="getAudioClip(voiceText)">send the heckin thing</button> 148 + </p> 149 + <p> 150 + webocket status: {{ websocketStatus }} 151 + </p> 152 + </div> 9 153 </template> 10 154 11 - <style scoped></style> 155 + <style> 156 + root { 157 + size: 16px; 158 + } 159 + </style> 160 + 161 + <style scoped> 162 + #debugWindow { 163 + position: absolute; 164 + right: 0; 165 + bottom: 0; 166 + background-color: rgba(0, 0, 0, 0.5); 167 + overflow: auto; 168 + } 169 + 170 + #debugWindow p { 171 + margin: 0 auto; 172 + } 173 + 174 + .chatLine { 175 + margin: 0 auto; 176 + } 177 + </style>
+5
start-piper.sh
··· 1 + #!/bin/bash 2 + # This is the default voice i chose, and you MUST provide at least one default voice. 3 + # get piper how you feel appropriate, i got it from python package, as this Just Works On My Machine. but i don't know best, you decide. 4 + DATA_DIR=${HOME}/projects/piper/ 5 + python -m piper.http_server -m en_US-libritts_r-medium --data-dir ${DATA_DIR}
+9
vite.config.ts
··· 15 15 '@': fileURLToPath(new URL('./src', import.meta.url)) 16 16 }, 17 17 }, 18 + server: { 19 + proxy: { 20 + '/api': { 21 + target: 'http://localhost:5000', 22 + changeOrigin: true, 23 + rewrite: (path) => path.replace(/^\/api/,'') 24 + } 25 + } 26 + } 18 27 })