From 327929d2566f8a4d6a265c8ff94074f417a623a0 Mon Sep 17 00:00:00 2001 From: Solomon Cammack Date: Thu, 17 Nov 2022 01:40:56 +0000 Subject: [PATCH 1/4] Big work --- server/package.json | 3 + server/src/airtable-interface.js | 2 +- server/src/cache.js | 20 +- server/src/custom-datasets.js | 1 + server/src/discord/bot-controller.js | 229 ++++++++++++++++++ server/src/index.js | 1 + server/yarn.lock | 193 ++++++++++++++- website/package.json | 1 + .../src/components/broadcast/PlayerAudio.vue | 64 +++++ .../src/components/broadcast/Standings.vue | 1 + website/src/utils/pcmplayer.js | 59 +++++ website/src/views/lists/EventDisplay.vue | 11 +- website/yarn.lock | 19 ++ 13 files changed, 590 insertions(+), 14 deletions(-) create mode 100644 server/src/discord/bot-controller.js create mode 100644 website/src/components/broadcast/PlayerAudio.vue create mode 100644 website/src/utils/pcmplayer.js diff --git a/server/package.json b/server/package.json index c2ed96bc..e836a77b 100644 --- a/server/package.json +++ b/server/package.json @@ -19,6 +19,8 @@ "nodemon": "^2.0.7" }, "dependencies": { + "@discordjs/opus": "^0.9.0", + "@discordjs/voice": "^0.13.0", "@twurple/api": "^5.2.5", "@twurple/auth": "^5.2.5", "airtable": "^0.10.1", @@ -28,6 +30,7 @@ "discord.js": "^13.3.1", "dotenv": "^8.2.0", "express": "^4.17.1", + "libsodium-wrappers": "^0.7.10", "node-fetch": "^2.6.1", "ora": "^5.4.0", "sharp": "^0.30.1", diff --git a/server/src/airtable-interface.js b/server/src/airtable-interface.js index afe253ea..233521f6 100644 --- a/server/src/airtable-interface.js +++ b/server/src/airtable-interface.js @@ -82,7 +82,7 @@ function setRebuilding(isRebuilding) { // Starting with syncing Matches // const tables = ["Matches", "Teams", "Themes", "Events", "Players", "Player Relationships"]; -const tables = ["Broadcasts", "Clients", "Channels", "Players", "Events", "Event Series", "Teams", "Ad Reads", "Ad Read Groups", "News", "Matches", "Themes", "Socials", "Accolades", "Player Relationships", "Brackets", "Live Guests", "Headlines", "Maps", "Map Data", "Heroes", "Log Files", "Tracks", "Track Groups", "Track Group Roles"]; +const tables = ["Broadcasts", "Clients", "Channels", "Discord Bots", "Players", "Events", "Event Series", "Teams", "Ad Reads", "Ad Read Groups", "News", "Matches", "Themes", "Socials", "Accolades", "Player Relationships", "Brackets", "Live Guests", "Headlines", "Maps", "Map Data", "Heroes", "Log Files", "Tracks", "Track Groups", "Track Group Roles"]; const staticTables = ["Redirects"]; function deAirtable(obj) { diff --git a/server/src/cache.js b/server/src/cache.js index 5703ec0a..014dc3ae 100644 --- a/server/src/cache.js +++ b/server/src/cache.js @@ -89,10 +89,10 @@ async function dataUpdate(id, data, options) { if (JSON.stringify(store.get(id)) !== JSON.stringify(data)) { // console.log(`Data update on [${id}]`); recents.sent++; - if (!(options && options.custom)) updateFunction(id, { oldData: store.get(id), newData: data }); if (data) data = await removeAntiLeak(id, data); // if (options?.eager) console.log("Sending"); await broadcast(id, "data_update", id, data); + if (!(options && options.custom)) updateFunction(id, { oldData: store.get(id), newData: data }); } } @@ -198,7 +198,13 @@ async function set(id, data, options) { if (data?.__tableName === "Channels") { auth.set(`channel_${id}`, data); - return; // not setting it on global requestble store + return; // not setting it on global requestable store + } + + if (data?.__tableName === "Discord Bots") { + auth.set(`bot_${cleanID(id)}`, data); + + return; // not setting it on global requestable store } if (data?.__tableName === "Events") { @@ -266,6 +272,7 @@ async function set(id, data, options) { await dataUpdate(id, data, options); store.set(id, data); + } function cleanID(id) { if (!id) return null; @@ -318,12 +325,18 @@ async function getPlayer(discordID) { async function getChannel(airtableID) { return auth.get(`channel_${cleanID(airtableID)}`); } +async function getBot(airtableID) { + return auth.get(`bot_${cleanID(airtableID)}`); +} async function getChannelByID(channelID) { return (await getChannels()).find(channel => channel.channel_id === channelID); } async function getChannels() { return await Promise.all(((await get("Channels"))?.ids || []).map(id => getChannel(id))); } +async function getBots() { + return await Promise.all(((await get("Discord Bots"))?.ids || []).map(id => getBot(id))); +} async function getTwitchAccessToken(channel) { // get stored access token, check if it's valid @@ -349,6 +362,7 @@ module.exports = { getPlayer, getChannel, getChannelByID, - getTwitchAccessToken + getTwitchAccessToken, + getBots } }; diff --git a/server/src/custom-datasets.js b/server/src/custom-datasets.js index 1b59be59..e9588aee 100644 --- a/server/src/custom-datasets.js +++ b/server/src/custom-datasets.js @@ -6,6 +6,7 @@ function tableUpdated(tableName, Cache) { if (tableName === "Matches") matchUpdate(Cache); if (tableName === "Broadcasts") broadcastUpdate(Cache); if (tableName === "Players") playerList(Cache); + // TODO: maybe add discord bots here? } module.exports = tableUpdated; diff --git a/server/src/discord/bot-controller.js b/server/src/discord/bot-controller.js new file mode 100644 index 00000000..0f0520c0 --- /dev/null +++ b/server/src/discord/bot-controller.js @@ -0,0 +1,229 @@ +const {Client, Intents} = require("discord.js"); +const {joinVoiceChannel, EndBehaviorType} = require("@discordjs/voice"); + +const { onUpdate, auth: { getBots } } = require("../cache.js"); + + +// hivemind time + +/** + * this is not jsdoc`````` + * + * - get all tokens from airtable "Discord Bots".token + * - connect them all to Discord and track them + * - keep them in a pool and send them to join voice channels when requested*** + * - Send out events when players start/stop talking -> show who is talking using team cams position data + * + * [requested] - what triggers the bots to join? + * - Run Airtable query every few seconds that checks for any broadcast with the flag "Enable Teams comms"? + * (we can probably hook into the updating signals that are sent out to socket.io) + * + */ + + +onUpdate((id, { newData, oldData }) => { + if (id !== "Discord Bots") return; + setTimeout(async () => { + let botData = await getBots(); // update manager? + manager.setTokens(botData.map(d => d.token).filter(d => d)); + + manager.createJob("996236081819303936"); + }, 100); +}); + + +/** + * @typedef Job + * @param channelID {Snowflake} + * @param status {("unfulfilled"|"working")} + * @param client {DiscordBot} + */ + +/*** + * @property jobs {Job[]} + * @property clients {DiscordBot[]} + */ +class BotManager { + constructor() { + this.clients = []; + this.jobs = []; + } + + setTokens(tokens) { + tokens.forEach(token => { + this.createClient(token); + }); + // not join vc just connect to discord + // possibly also check to see if they + } + + createClient(token) { + if (this.clients.some(client => client.token === token)) return; + this.clients.push(new DiscordBot({ botToken: token, manager: this })); + } + + createJob(channelID) { + console.log(`[BotManager] New job created for channel ${channelID}`); + this.jobs.push({ + status: "unfulfilled", + client: null, + channelID + }); + } + + printStatus() { + return; + console.log("jobs", this.jobs.map(job => ({ status: job.status, channelID: job.channelID }))); + console.log("clients", this.clients.map(client => ({ token: client.token.slice(-6), status: client.status, tag: client.discord.tag }))); + } + + assignJobs() { + this.printStatus(); + + this.jobs.filter(job => job.status === "unfulfilled").forEach(job => { + let client = this.clients.find(client => client.status === "ready"); + if (!client) return console.log("[BotManager] No client available for job", job); + client.setJob(job); + job.status = "attempting"; + job.client = client; + this.printStatus(); + }); + } + + setWorkerJobStatus(worker, status) { + let job = this.jobs.find(job => job.client.token === worker.token); + job.status = status; + this.printStatus(); + } +} + +const manager = new BotManager(); + +class DiscordBot { + constructor({ botToken, manager }) { + this.manager = manager; + this.token = botToken; + this.id = this.token.slice(-6); + this.status = "connecting"; + + this.log("New bot setting up"); + + this.channel = null; + this.discord = { + tag: null, + id: null + }; + this.connection = null; + this.audios = []; + + this.client = new Client({ + intents: [ + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_VOICE_STATES + ] + }); + + this.client.once("ready", async () => { + this.setStatus("ready"); + this.log(`Connected new bot as ${this.client.user.tag}`); + this.discord = { + tag: this.client.user.tag, + id: this.client.user.id + }; + }); + + this.client.on("voiceStateUpdate", async (oldState, newState) => { + + if (newState.member.id === this.discord.id) { + return this.checkJob(newState.channelId); + } + + if (!this.job?.channelID || !this.connection) return; + if (newState.channelId === this.job?.channelID) { + await this.subscribeUserAudio(newState.member); + } + }); + + this.client.on("debug", (m) => { + // this.log(m); + }); + + this.client.login(botToken); + } + + log(...str) { + console.log(`[Bot-${this.id}] [${this.status}] ${this.discord?.tag || ""} ${str.join(" ")}`); + } + + setStatus(status) { + this.status = status; + this.manager.assignJobs(); + } + + setJob(job) { + this.status = "attempting"; + this.job = job; + this.connect(); + } + + checkJob(currentChannelID) { + if (!this.job) return this.log(`No job but currently in channel ${currentChannelID}`); + + if (this.job.channelID === currentChannelID) { + this.setStatus("working"); + this.log(`Found the right channel and is working ${currentChannelID}`); + this.manager.setWorkerJobStatus(this, "working"); + } else if (this.job.channelID && currentChannelID) { + this.setStatus("lost"); + this.log(`In the wrong channel (should be ${this.job.channelID} but is in ${currentChannelID})`); + this.connect(); // this might cause some bugs so use carefully + } else { + this.setStatus("disconnected"); + this.log("No longer in a channel"); + } + } + + + async connect() { + this.channel = await this.client.channels.resolve(this.job.channelID); + this.log("Connecting to", this.job.channelID, this.channel.name, this.channel.id); + if (this.connection) this.connection.destroy(); + + this.connection = joinVoiceChannel({ + channelId: this.job.channelID, + guildId: this.channel.guild.id, + adapterCreator: this.channel.guild.voiceAdapterCreator, + selfDeaf: false + }); + } + + async subscribeUserAudio(member) { + if (this.audios[member.user.id]) { + // console.log("Already subscribed for this user", this.audios[member.user.id]); + return; + } + + console.log(`Subscribing to audio from [${member.user.id}] ${member.user.username}`); + + const audio = this.connection.receiver.subscribe(member.user.id, {end: {behavior: EndBehaviorType.Manual}}); + audio.on("data", (data) => { + // TODO: checks before transmitting data + // TODO: How do we manage sockets + // io.emit("audio", {data, user: member.user.id}); + }); + + ["close", "end", "error", "pause"].forEach(eventType => { + audio.on(eventType, (...data) => console.log("[Stream]", eventType, member.user.id, data)); + // TODO: if this Readable pipe is closed then we can unset this.audios[member.user.id] so it reconnects the user + }); + + this.audios[member.user.id] = audio; + } + + async destroy() { + // TODO: implement + this.connection.disconnect(); + this.connection.destroy(); + } +} + diff --git a/server/src/index.js b/server/src/index.js index d6f7fc3d..a9f4b2a2 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -9,6 +9,7 @@ const meta = require("./meta.js"); const routes = require("./routes.js"); const images = require("./images.js"); const discordAuth = require("./discord/auth.js"); +const botController = require("./discord/bot-controller.js"); /* The staff module should only run on the server, probably not your local machine. */ let staffKeysRequired = ["DISCORD_TOKEN", "STAFFAPPS_GUILD_ID", "STAFFAPPS_CATEGORY_ID", "STAFFAPPS_APPLICATION_CHANNEL_ID", "IS_SLMNGG_MAIN_SERVER"]; diff --git a/server/yarn.lock b/server/yarn.lock index 792b6c28..47ccbf15 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -105,6 +105,40 @@ resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.7.0.tgz#1a6c00198b744ba2b73a64442145da637ac073b8" integrity sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA== +"@discordjs/node-pre-gyp@^0.4.5": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@discordjs/node-pre-gyp/-/node-pre-gyp-0.4.5.tgz#b33e38cedd821268c75923641783c68fcd1b55ae" + integrity sha512-YJOVVZ545x24mHzANfYoy0BJX5PDyeZlpiJjDkUBM/V/Ao7TFX9lcUvCN4nr0tbr5ubeaXxtEBILUrHtTphVeQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@discordjs/opus@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@discordjs/opus/-/opus-0.9.0.tgz#bbd9f78bd6bc399885bcb48645c6d2b1f0efa0c5" + integrity sha512-NEE76A96FtQ5YuoAVlOlB3ryMPrkXbUCTQICHGKb8ShtjXyubGicjRMouHtP1RpuDdm16cDa+oI3aAMo1zQRUQ== + dependencies: + "@discordjs/node-pre-gyp" "^0.4.5" + node-addon-api "^5.0.0" + +"@discordjs/voice@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.13.0.tgz#dd7eb490246ce00ccdad486859b9ccc4ef275ac9" + integrity sha512-ZzwDmVINaLgkoDUeTJfpN9TkjINMLfTVoLMtEygm0YC5jTTw7AvKGqhc+Ae/2kNLywd0joyFVNrLp94yCkQ9SA== + dependencies: + "@types/ws" "^8.5.3" + discord-api-types "^0.37.12" + prism-media "^1.3.4" + tslib "^2.4.0" + ws "^8.9.0" + "@eslint/eslintrc@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" @@ -275,6 +309,13 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + airtable@^0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/airtable/-/airtable-0.10.1.tgz#0b311002bb44b39f19bf7c4bd2d47d75c733bf87" @@ -365,6 +406,19 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@~1.1.2: version "1.1.7" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" @@ -574,6 +628,11 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -645,6 +704,11 @@ color-string@^1.9.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" @@ -682,7 +746,7 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -748,6 +812,13 @@ debug@2.6.9, debug@^2.2.0: dependencies: ms "2.0.0" +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -846,6 +917,11 @@ discord-api-types@^0.36.2: resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.36.3.tgz#a931b7e57473a5c971d6937fa5f392eb30047579" integrity sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg== +discord-api-types@^0.37.12: + version "0.37.18" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.18.tgz#1f0ca95cea4b2380ba77623a62b1d285f1237d7a" + integrity sha512-mJ+9C8gmG5csssVZPH06Y8IGiJykljFyZc6n6F+T3vKo6yNBI5TtLIbwt6t9hJzsR5f1ITzRZ6cuPrTvRCUxqA== + discord.js@^13.3.1: version "13.12.0" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.12.0.tgz#e4839c14a02b1947e063b72f09a49b11336a58f5" @@ -1262,6 +1338,13 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1297,6 +1380,21 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -1457,7 +1555,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -1501,6 +1599,14 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1862,6 +1968,18 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +libsodium-wrappers@^0.7.10: + version "0.7.10" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#13ced44cacb0fc44d6ac9ce67d725956089ce733" + integrity sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg== + dependencies: + libsodium "^0.7.0" + +libsodium@^0.7.0: + version "0.7.10" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.10.tgz#c2429a7e4c0836f879d701fec2c8a208af024159" + integrity sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ== + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -1912,7 +2030,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -1990,11 +2108,31 @@ minimist@^1.2.0, minimist@^1.2.3: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minipass@^3.0.0: + version "3.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2042,6 +2180,11 @@ node-addon-api@^4.3.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" + integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== + node-fetch@2.6.7, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -2070,6 +2213,13 @@ nodemon@^2.0.7: undefsafe "^2.0.3" update-notifier "^4.1.0" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -2097,12 +2247,22 @@ npmlog@^4.0.1: gauge "~2.7.3" set-blocking "~2.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -object-assign@^4, object-assign@^4.1.0: +object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -2251,6 +2411,11 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prism-media@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.4.tgz#7951f26a9186b791dc8c820ff07310ec46a8a5f1" + integrity sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -2337,7 +2502,7 @@ readable-stream@^2.0.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -2490,7 +2655,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-blocking@~2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -2623,7 +2788,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4": +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2770,6 +2935,18 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.1.11: + version "6.1.12" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" + integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" @@ -2978,7 +3155,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: +wide-align@^1.1.0, wide-align@^1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== diff --git a/website/package.json b/website/package.json index 660dbfd4..1b8acfed 100644 --- a/website/package.json +++ b/website/package.json @@ -14,6 +14,7 @@ "howler": "^2.2.3", "jimp": "^0.16.1", "marked": "^2.1.3", + "opus-decoder": "^0.5.3", "socket.io-client": "^4.0.1", "spacetime": "^6.16.0", "spacetime-informal": "^0.6.1", diff --git a/website/src/components/broadcast/PlayerAudio.vue b/website/src/components/broadcast/PlayerAudio.vue new file mode 100644 index 00000000..3da39f2f --- /dev/null +++ b/website/src/components/broadcast/PlayerAudio.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/website/src/components/broadcast/Standings.vue b/website/src/components/broadcast/Standings.vue index e6abfd2c..23cc89a1 100644 --- a/website/src/components/broadcast/Standings.vue +++ b/website/src/components/broadcast/Standings.vue @@ -353,6 +353,7 @@ export default { font-weight: bold; text-transform: uppercase; line-height: 1; + margin-bottom: .2em; } .team-name { margin-left: 2em; diff --git a/website/src/utils/pcmplayer.js b/website/src/utils/pcmplayer.js new file mode 100644 index 00000000..ddfedb2e --- /dev/null +++ b/website/src/utils/pcmplayer.js @@ -0,0 +1,59 @@ +function PCMPlayer(option) { + this.init(option); +} + +PCMPlayer.prototype.init = function(option) { + const defaults = { + encoding: "16bitInt", + channels: 1, + sampleRate: 8000, + flushingTime: 1000 + }; + this.option = Object.assign({}, defaults, option); + this.createContext(); +}; + + +PCMPlayer.prototype.createContext = function() { + this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + this.gainNode = this.audioCtx.createGain(); + this.gainNode.gain.value = 1; + this.gainNode.connect(this.audioCtx.destination); + this.startTime = this.audioCtx.currentTime; +}; + + +PCMPlayer.prototype.feed = function({ channelData, length }) { + const audioSrc = this.audioCtx.createBufferSource(); + const audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate); + + for (let c = 0; c < this.option.channels; c++) { + if (audioBuffer.copyToChannel) { + audioBuffer.copyToChannel(channelData[c], c); + } else { + console.log("copyToChannel not supported"); + const audioData = audioBuffer.getChannelData(c); + for (let i = 0; i < channelData[c].byteLength; i++) { + audioData[i] = channelData[c][i]; + } + } + } + + if (this.startTime < this.audioCtx.currentTime) { + this.startTime = this.audioCtx.currentTime; + } + + audioSrc.buffer = audioBuffer; + audioSrc.connect(this.gainNode); + audioSrc.start(this.startTime); + this.startTime += audioBuffer.duration; +}; + +PCMPlayer.prototype.destroy = function() { + if (this.interval) { + clearInterval(this.interval); + } + this.samples = null; + this.audioCtx.close(); + this.audioCtx = null; +}; diff --git a/website/src/views/lists/EventDisplay.vue b/website/src/views/lists/EventDisplay.vue index a91006f3..7f893a9e 100644 --- a/website/src/views/lists/EventDisplay.vue +++ b/website/src/views/lists/EventDisplay.vue @@ -3,15 +3,19 @@
-
+
{{ event.name }}
+
+ {{ startMonth }} +
diff --git a/website/src/router/broadcast.js b/website/src/router/broadcast.js index 43673f90..14dd1759 100644 --- a/website/src/router/broadcast.js +++ b/website/src/router/broadcast.js @@ -1,3 +1,4 @@ +const PlayerAudio = () => import("@/components/broadcast/PlayerAudio"); const MVPOverlay = () => import("@/components/broadcast/roots/MVPOverlay"); const MultiStandingsOverlay = () => import("@/components/broadcast/roots/MultiStandingsOverlay"); const ClientOverview = () => import("@/components/broadcast/roots/ClientOverview"); @@ -189,5 +190,12 @@ export default [ { path: "stinger", component: EmptyStingerOverlay }, { path: "empty", redirect: "stinger" }, { path: "broadcasts", component: OtherBroadcastsOverlay }, - { path: "mvp", component: MVPOverlay } + { path: "mvp", component: MVPOverlay }, + { + path: "audio", + component: PlayerAudio, + props: route => ({ + taskKey: route.query.key + }) + } ]; diff --git a/website/src/utils/pcmplayer.js b/website/src/utils/pcmplayer.js index ddfedb2e..500613d6 100644 --- a/website/src/utils/pcmplayer.js +++ b/website/src/utils/pcmplayer.js @@ -1,59 +1,57 @@ -function PCMPlayer(option) { - this.init(option); -} +export default class PCMPlayer { + constructor(option) { + const defaults = { + encoding: "16bitInt", + channels: 1, + sampleRate: 8000, + flushingTime: 1000 + }; + this.option = Object.assign({}, defaults, option); + this.createContext(); + } + + createContext() { + this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + this.gainNode = this.audioCtx.createGain(); + this.gainNode.gain.value = 1; + this.gainNode.connect(this.audioCtx.destination); + this.startTime = this.audioCtx.currentTime; + } -PCMPlayer.prototype.init = function(option) { - const defaults = { - encoding: "16bitInt", - channels: 1, - sampleRate: 8000, - flushingTime: 1000 - }; - this.option = Object.assign({}, defaults, option); - this.createContext(); -}; - - -PCMPlayer.prototype.createContext = function() { - this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); - this.gainNode = this.audioCtx.createGain(); - this.gainNode.gain.value = 1; - this.gainNode.connect(this.audioCtx.destination); - this.startTime = this.audioCtx.currentTime; -}; - - -PCMPlayer.prototype.feed = function({ channelData, length }) { - const audioSrc = this.audioCtx.createBufferSource(); - const audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate); - - for (let c = 0; c < this.option.channels; c++) { - if (audioBuffer.copyToChannel) { - audioBuffer.copyToChannel(channelData[c], c); - } else { - console.log("copyToChannel not supported"); - const audioData = audioBuffer.getChannelData(c); - for (let i = 0; i < channelData[c].byteLength; i++) { - audioData[i] = channelData[c][i]; + feed({ channelData, length }) { + const audioSrc = this.audioCtx.createBufferSource(); + const audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate); + + for (let c = 0; c < this.option.channels; c++) { + if (audioBuffer.copyToChannel) { + audioBuffer.copyToChannel(channelData[c], c); + } else { + console.log("copyToChannel not supported"); + const audioData = audioBuffer.getChannelData(c); + for (let i = 0; i < channelData[c].byteLength; i++) { + audioData[i] = channelData[c][i]; + } } } + + if (this.startTime < this.audioCtx.currentTime) { + this.startTime = this.audioCtx.currentTime; + } + + audioSrc.buffer = audioBuffer; + audioSrc.connect(this.gainNode); + audioSrc.start(this.startTime); + this.startTime += audioBuffer.duration; } - if (this.startTime < this.audioCtx.currentTime) { - this.startTime = this.audioCtx.currentTime; + destroy() { + if (this.interval) { + clearInterval(this.interval); + } + this.samples = null; + this.audioCtx.close(); + this.audioCtx = null; } +} - audioSrc.buffer = audioBuffer; - audioSrc.connect(this.gainNode); - audioSrc.start(this.startTime); - this.startTime += audioBuffer.duration; -}; -PCMPlayer.prototype.destroy = function() { - if (this.interval) { - clearInterval(this.interval); - } - this.samples = null; - this.audioCtx.close(); - this.audioCtx = null; -}; From 97b5e46872fa3649f8b3486085b94c28e6b784b0 Mon Sep 17 00:00:00 2001 From: Solomon Cammack Date: Sat, 19 Nov 2022 19:16:34 +0000 Subject: [PATCH 3/4] Many updates --- server/src/discord/bot-controller.js | 69 ++++++++++--- server/src/index.js | 2 +- .../src/components/broadcast/ListenInBug.vue | 39 ++++++++ .../src/components/broadcast/PlayerAudio.vue | 30 ++++-- .../components/broadcast/ThemeTransition.vue | 5 +- .../broadcast/roots/IngameCommsOverlay.vue | 99 +++++++++++++++++++ .../website/dashboard/CommsControls.vue | 55 +++++++++++ website/src/main.js | 1 + website/src/router/broadcast.js | 8 ++ website/src/views/Dashboard.vue | 8 ++ 10 files changed, 292 insertions(+), 24 deletions(-) create mode 100644 website/src/components/broadcast/ListenInBug.vue create mode 100644 website/src/components/broadcast/roots/IngameCommsOverlay.vue create mode 100644 website/src/components/website/dashboard/CommsControls.vue diff --git a/server/src/discord/bot-controller.js b/server/src/discord/bot-controller.js index b1137437..0edb8ae7 100644 --- a/server/src/discord/bot-controller.js +++ b/server/src/discord/bot-controller.js @@ -133,6 +133,37 @@ onUpdate(async(id, { newData, oldData }) => { * @param client {DiscordBot} */ +class Job { + constructor({ channelID, broadcastKey, taskKey, team}) { + this.channelID = channelID; + this.broadcastKey = broadcastKey; + this.taskKey = taskKey; + this.team = team; + this.teamName = team?.name; + + this.client = null; + this.status = "unfulfilled"; + } + + sync(customSocket) { + if (!io) return; + let audioRoom = `${this.broadcastKey}/${this.taskKey}`; + let destination = customSocket || io.to(audioRoom); + destination.emit("audio_job_status", audioRoom, this.serialize()); + } + + serialize() { + return ({ + status: this.status, + channelID: this.channelID, + broadcastKey: this.broadcastKey, + taskKey: this.taskKey, + teamID: this.team?.id + }); + } +} + + /*** * @property jobs {Job[]} * @property clients {DiscordBot[]} @@ -160,17 +191,15 @@ class BotManager { createJob(channelID, broadcastKey, taskKey, team) { console.log(`[BotManager] New job [${broadcastKey}/${taskKey}] created for channel ${channelID}`); - let job = { - status: "unfulfilled", - client: null, + let job = new Job({ channelID, broadcastKey, taskKey, - team, - teamName: team?.name - }; + team + }); this.jobs.push(job); this.assignJobs(); + job.sync(); return job; } @@ -193,6 +222,9 @@ class BotManager { getClient(taskKey, broadcastKey) { return this.clients.find(client => client?.job?.broadcastKey === broadcastKey && client?.job?.taskKey === taskKey); } + getJob(taskKey, broadcastKey) { + return this.jobs.find(job => job?.broadcastKey === broadcastKey && job?.taskKey === taskKey); + } printStatus() { // return; @@ -222,6 +254,7 @@ class BotManager { client.setJob(job); job.status = "attempting"; job.client = client; + job.sync(); // this.printStatus(); }); } @@ -230,11 +263,13 @@ class BotManager { let job = this.jobs.find(job => job.client.token === worker.token); if (!job) return console.warn(`[BotManager] No job for this worker ${worker.id}`); job.status = status; + job.sync(); // this.printStatus(); } deleteWorkerJob(worker) { let job = this.jobs.find(job => job.client.token === worker.token); + // job.prepareForDeletion(); if (!job) return console.error("Tried to delete a worker's job but couldn't find it"); this.jobs = this.jobs.filter(job => !(job.client.token === worker.token)); } @@ -408,7 +443,7 @@ class DiscordBot { // TODO: checks before transmitting data // TODO: How do we manage sockets // console.log("emitting", this.socketRoom, member.user.id); - io.to(this.socketRoom).emit("audio", {data, user: member.user.id}); + io.to(this.socketRoom).emit("audio", this.socketRoom, {data, user: member.user.id}); }); ["close", "end", "error", "pause"].forEach(eventType => { @@ -465,7 +500,7 @@ class MemberList { async sync(customSocket) { let destination = customSocket || io.to(this.bot.socketRoom); // this.bot.log("updating member list"); - destination.emit("audio_member_list", await this.getList()); + destination.emit("audio_member_list", this.bot.socketRoom, await this.getList()); } } @@ -476,13 +511,21 @@ module.exports = { io.on("connect", socket => { socket.on("audio_subscribe", ({ taskKey, broadcastKey }) => { console.log("[audio] sub", taskKey, broadcastKey); - if (socket._audioRoom) socket.leave(socket._audioRoom); - socket._audioRoom = `${broadcastKey}/${taskKey}`; - socket.join(socket._audioRoom); + let audioRoom = `${broadcastKey}/${taskKey}`; + // if (socket._audioRoom) socket.leave(socket._audioRoom); + // socket._audioRoom = `${broadcastKey}/${taskKey}`; + socket.join(audioRoom); let client = manager.getClient(taskKey, broadcastKey); - if (!client) return; - client.memberList.sync(socket); + if (client) { + client.memberList.sync(socket); + } + let job = manager.getJob(taskKey, broadcastKey); + if (job) { + job.sync(socket); + } else { + socket.emit("audio_job_status", audioRoom, null); + } }); }); } diff --git a/server/src/index.js b/server/src/index.js index 8901b642..f1398598 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -137,7 +137,7 @@ io.on("connection", (socket) => { console.log("get and subscribe out:", id); }); socket.on("prod-join", (clientName) => { - console.log("[prod] join ", clientName); + console.log("[prod] join", clientName); socket._clientName = clientName; socket.join(`prod:client-${clientName}`); }); diff --git a/website/src/components/broadcast/ListenInBug.vue b/website/src/components/broadcast/ListenInBug.vue new file mode 100644 index 00000000..16a2d22a --- /dev/null +++ b/website/src/components/broadcast/ListenInBug.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/website/src/components/broadcast/PlayerAudio.vue b/website/src/components/broadcast/PlayerAudio.vue index 73706372..7e5d7b55 100644 --- a/website/src/components/broadcast/PlayerAudio.vue +++ b/website/src/components/broadcast/PlayerAudio.vue @@ -1,10 +1,6 @@