From 3b6739cfe01471ff2c8d7f245ec09f83979f7332 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Wed, 30 Dec 2020 03:31:57 +1300 Subject: [PATCH] Notification TTS --- CefService.cs | 1 + html/src/app.js | 33 +++++++++++++ html/src/index.pug | 13 ++++- html/src/vr.js | 116 +++++++++++++++++++++++++++++---------------- 4 files changed, 121 insertions(+), 42 deletions(-) diff --git a/CefService.cs b/CefService.cs index 1f789fd2..e999767f 100644 --- a/CefService.cs +++ b/CefService.cs @@ -39,6 +39,7 @@ namespace VRCX cefSettings.CefCommandLineArgs.Add("disable-spell-checking"); cefSettings.CefCommandLineArgs.Add("disable-pdf-extension"); cefSettings.CefCommandLineArgs.Add("disable-extensions"); + cefSettings.CefCommandLineArgs["autoplay-policy"] = "no-user-gesture-required"; // cefSettings.CefCommandLineArgs.Add("allow-universal-access-from-files"); // cefSettings.CefCommandLineArgs.Add("disable-web-security"); cefSettings.SetOffScreenRenderingBestPerformanceArgs(); diff --git a/html/src/app.js b/html/src/app.js index bff59675..d914d6ef 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -16,6 +16,8 @@ import configRepository from './repository/config.js'; import webApiService from './service/webapi.js'; import gameLogService from './service/gamelog.js' +speechSynthesis.getVoices(); + (async function () { await CefSharp.BindObjectAsync( 'AppApi', @@ -3411,6 +3413,7 @@ import gameLogService from './service/gamelog.js' AppApi.CheckGameRunning().then(([isGameRunning, isGameNoVR]) => { if (isGameRunning !== this.isGameRunning) { this.isGameRunning = isGameRunning; + sharedRepository.setBool('isGameRunning', isGameRunning); Discord.SetTimestamps(Date.now(), 0); } this.isGameNoVR = isGameNoVR; @@ -5690,6 +5693,8 @@ import gameLogService from './service/gamelog.js' $app.data.hideDevicesFromFeed = configRepository.getBool('VRCX_hideDevicesFromFeed'); $app.data.overlayNotifications = configRepository.getBool('VRCX_overlayNotifications'); $app.data.minimalFeed = configRepository.getBool('VRCX_minimalFeed'); + $app.data.notificationTTS = configRepository.getBool('VRCX_notificationTTS'); + $app.data.notificationTTSVoice = configRepository.getString('VRCX_notificationTTSVoice'); $app.data.notificationTimeout = configRepository.getString('VRCX_notificationTimeout'); var saveOpenVROption = function () { configRepository.setBool('openVR', this.openVR); @@ -5700,6 +5705,13 @@ import gameLogService from './service/gamelog.js' configRepository.setBool('VRCX_overlayNotifications', this.overlayNotifications); configRepository.setBool('VRCX_minimalFeed', this.minimalFeed); }; + $app.data.TTSvoices = speechSynthesis.getVoices(); + var saveNotificationTTS = function () { + configRepository.setBool('VRCX_notificationTTS', this.notificationTTS); + if (this.notificationTTS) { + this.speak('Notification text-to-speech enabled'); + } + }; $app.watch.openVR = saveOpenVROption; $app.watch.openVRAlways = saveOpenVROption; $app.watch.hidePrivateFromFeed = saveOpenVROption; @@ -5707,6 +5719,7 @@ import gameLogService from './service/gamelog.js' $app.watch.hideDevicesFromFeed = saveOpenVROption; $app.watch.overlayNotifications = saveOpenVROption; $app.watch.minimalFeed = saveOpenVROption; + $app.watch.notificationTTS = saveNotificationTTS; $app.data.isDarkMode = configRepository.getBool('isDarkMode'); $appDarkStyle.disabled = $app.data.isDarkMode === false; $app.watch.isDarkMode = function () { @@ -5744,6 +5757,10 @@ import gameLogService from './service/gamelog.js' $app.data.notificationPosition = 'topCenter'; configRepository.setString('VRCX_notificationPosition', $app.data.notificationPosition); } + if (!configRepository.getString('VRCX_notificationTTSVoice')) { + $app.data.notificationTTSVoice = '0'; + configRepository.setString('VRCX_notificationTTSVoice', $app.data.notificationTTSVoice); + } $app.data.notificationJoinLeaveFilter = configRepository.getString('VRCX_notificationJoinLeaveFilter'); $app.methods.changeNotificationJoinLeaveFilter = function () { configRepository.setString('VRCX_notificationJoinLeaveFilter', this.notificationJoinLeaveFilter); @@ -5816,6 +5833,22 @@ import gameLogService from './service/gamelog.js' } }; + $app.methods.changeTTSVoice = function (index) { + this.notificationTTSVoice = index; + configRepository.setString('VRCX_notificationTTSVoice', this.notificationTTSVoice); + var voices = speechSynthesis.getVoices(); + var voiceName = voices[index].name; + this.speak(voiceName); + }; + + $app.methods.speak = function (text) { + var tts = new SpeechSynthesisUtterance(); + var voices = speechSynthesis.getVoices(); + tts.voice = voices[this.notificationTTSVoice]; + tts.text = text; + speechSynthesis.speak(tts); + }; + $app.methods.refreshConfigTreeData = function () { this.configTreeData = buildTreeData(API.cachedConfig); }; diff --git a/html/src/index.pug b/html/src/index.pug index 0fd1fa2f..f1a1df07 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -568,13 +568,22 @@ html div(style="font-size:12px;margin-top:5px") span(style="display:inline-block;min-width:150px") Overlay Notifications el-switch(v-model="overlayNotifications" :disabled="!openVR") + div(style="font-size:12px;margin-top:5px") + span(style="display:inline-block;min-width:150px") Notification TTS + el-switch(v-model="notificationTTS" :disabled="!openVR") + div(style="font-size:12px;margin-top:5px") + span(style="display:inline-block;min-width:150px") TTS Voice + el-dropdown(@command="(voice) => changeTTSVoice(voice)" trigger="click" size="small") + el-button(v-text="TTSvoices[notificationTTSVoice].name" size="mini" :disabled="!openVR || !notificationTTS") + el-dropdown-menu(#default="dropdown") + el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index") div(style="font-size:12px;margin-top:5px") el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="!overlayNotifications || !openVR") Notification Timeout el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position div(style="font-size:12px;margin-top:5px") span Join/Leave Notifications br - el-radio-group(v-model="notificationJoinLeaveFilter" size="mini" @change="changeNotificationJoinLeaveFilter" :disabled="!overlayNotifications || !openVR") + el-radio-group(v-model="notificationJoinLeaveFilter" size="mini" @change="changeNotificationJoinLeaveFilter" :disabled="!overlayNotifications && !notificationTTS || !openVR") el-radio(label="VIP" v-model="notificationJoinLeaveFilter") VIP el-radio(label="Friends" v-model="notificationJoinLeaveFilter") Friends el-radio(label="Everyone" v-model="notificationJoinLeaveFilter") Everyone @@ -582,7 +591,7 @@ html div(style="font-size:12px;margin-top:5px") span Online/Offline Notifications br - el-radio-group(v-model="notificationOnlineOfflineFilter" size="mini" @change="changeNotificationOnlineOfflineFilter" :disabled="!overlayNotifications || !openVR") + el-radio-group(v-model="notificationOnlineOfflineFilter" size="mini" @change="changeNotificationOnlineOfflineFilter" :disabled="!overlayNotifications && !notificationTTS || !openVR") el-radio(label="VIP" v-model="notificationOnlineOfflineFilter") VIP el-radio(label="Friends" v-model="notificationOnlineOfflineFilter") Friends el-radio(label="Off" v-model="notificationOnlineOfflineFilter") Off diff --git a/html/src/vr.js b/html/src/vr.js index 9c0989da..5d3b8a42 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -13,6 +13,8 @@ import sharedRepository from './repository/shared.js'; import configRepository from './repository/config.js'; import webApiService from './service/webapi.js'; +speechSynthesis.getVoices(); + (async function () { await CefSharp.BindObjectAsync( 'AppApi', @@ -662,7 +664,7 @@ import webApiService from './service/webapi.js'; var _feeds = this.feeds; this.feeds = feeds; - if (this.appType === '2') { + if ((this.appType === '2') && sharedRepository.getBool('isGameRunning')) { var map = {}; _feeds.forEach((feed) => { if (feed.type === 'OnPlayerJoined' || @@ -690,47 +692,47 @@ import webApiService from './service/webapi.js'; if (this.currentUserStatus === 'busy') { return; } - if (configRepository.getBool('VRCX_overlayNotifications') === true) { - var notys = []; - this.feeds.forEach((feed) => { - if (((notificationOnlineOfflineFilter === "Friends") && (feed.isFriend)) || - ((notificationOnlineOfflineFilter === "VIP") && (feed.isFavorite))) { - if (feed.type === 'Online' || - feed.type === 'Offline') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - notys.push(feed); - } - } - } else if ((notificationJoinLeaveFilter === "Everyone") || - ((notificationJoinLeaveFilter === "Friends") && (feed.isFriend)) || - ((notificationJoinLeaveFilter === "VIP") && (feed.isFavorite))) { - if (feed.type === 'OnPlayerJoined' || - feed.type === 'OnPlayerLeft') { - if (!map[feed.data] || - map[feed.data] < feed.created_at) { - map[feed.data] = feed.created_at; - notys.push(feed); - } - } - } else if (feed.type === 'invite' || - feed.type === 'requestInvite' || - feed.type === 'friendRequest') { - if (!map[feed.senderUsername] || - map[feed.senderUsername] < feed.created_at) { - map[feed.senderUsername] = feed.created_at; + var notys = []; + this.feeds.forEach((feed) => { + if (((notificationOnlineOfflineFilter === "Friends") && (feed.isFriend)) || + ((notificationOnlineOfflineFilter === "VIP") && (feed.isFavorite))) { + if (feed.type === 'Online' || + feed.type === 'Offline') { + if (!map[feed.displayName] || + map[feed.displayName] < feed.created_at) { + map[feed.displayName] = feed.created_at; notys.push(feed); } } - }); - var bias = new Date(Date.now() - 60000).toJSON(); - var theme = 'relax'; - if (configRepository.getBool('isDarkMode') === true) { - theme = 'sunset'; + } else if ((notificationJoinLeaveFilter === "Everyone") || + ((notificationJoinLeaveFilter === "Friends") && (feed.isFriend)) || + ((notificationJoinLeaveFilter === "VIP") && (feed.isFavorite))) { + if (feed.type === 'OnPlayerJoined' || + feed.type === 'OnPlayerLeft') { + if (!map[feed.data] || + map[feed.data] < feed.created_at) { + map[feed.data] = feed.created_at; + notys.push(feed); + } + } + } else if (feed.type === 'invite' || + feed.type === 'requestInvite' || + feed.type === 'friendRequest') { + if (!map[feed.senderUsername] || + map[feed.senderUsername] < feed.created_at) { + map[feed.senderUsername] = feed.created_at; + notys.push(feed); + } } - notys.forEach((noty) => { - if (noty.created_at > bias) { + }); + var bias = new Date(Date.now() - 60000).toJSON(); + var theme = 'relax'; + if (configRepository.getBool('isDarkMode') === true) { + theme = 'sunset'; + } + notys.forEach((noty) => { + if (noty.created_at > bias) { + if (configRepository.getBool('VRCX_overlayNotifications')) { switch (noty.type) { case 'OnPlayerJoined': new Noty({ @@ -797,8 +799,33 @@ import webApiService from './service/webapi.js'; break; } } - }); - } + if (configRepository.getBool('VRCX_notificationTTS')) { + switch (noty.type) { + case 'OnPlayerJoined': + this.speak(`${noty.data} has joined`); + break; + case 'OnPlayerLeft': + this.speak(`${noty.data} has left`); + break; + case 'Online': + this.speak(`${noty.displayName} has logged in`); + break; + case 'Offline': + this.speak(`${noty.displayName} has logged out`); + break; + case 'invite': + this.speak(`${noty.senderUsername} has invited you to ${noty.details.worldName}`); + break; + case 'requestInvite': + this.speak(`${noty.senderUsername} has requested an invite`); + break; + case 'friendRequest': + this.speak(`${noty.senderUsername} has sent you a friend request`); + break; + } + } + } + }); } }; @@ -818,6 +845,15 @@ import webApiService from './service/webapi.js'; return style; }; + $app.methods.speak = function (text) { + var tts = new SpeechSynthesisUtterance(); + var voices = speechSynthesis.getVoices(); + var voiceIndex = configRepository.getString('VRCX_notificationTTSVoice'); + tts.voice = voices[voiceIndex]; + tts.text = text; + speechSynthesis.speak(tts); + }; + $app = new Vue($app); window.$app = $app; })();