diff --git a/package.json b/package.json
index 16a29bc..e705bc7 100644
--- a/package.json
+++ b/package.json
@@ -7,32 +7,40 @@
"start": "electron .",
"build": "electron-builder",
"prod": "NODE_ENV=production electron .",
- "build:mac": "PYTHON_PATH=/opt/anaconda3/envs/quiz/bin/python electron-builder build --mac --x64 --arm64",
+ "build:mac": "electron-builder build --mac --x64 --arm64",
"build:win": "electron-builder build --win",
"build:linux": "electron-builder build --linux",
"clean": "rm -rf dist node_modules",
- "rebuild": "npm run clean && npm cache clean --force && npm install && npm run patch-dmg-builder && DEBUG=electron-builder PYTHON_PATH=/opt/anaconda3/envs/quiz/bin/python npm run build:mac",
+ "rebuild": "npm run clean && npm cache clean --force && npm install && npm run patch-dmg-builder && DEBUG=electron-builder npm run build:mac",
"patch-dmg-builder": "node patch-dmg-builder.js"
},
"author": "Your Name",
"license": "ISC",
"devDependencies": {
"electron": "^32.1.2",
- "electron-builder": "^25.0.5"
+ "electron-builder": "^25.1.8"
},
"dependencies": {
"axios": "^1.7.7",
"dotenv": "^10.0.0",
+ "electron-store": "^8.1.0",
"groq-sdk": "^0.7.0",
"reveal.js": "^5.1.0",
"ws": "^8.18.0"
},
"build": {
- "appId": "com.yourdomain.streamsage",
+ "appId": "com.quizzible.streamsage",
"productName": "StreamSage",
"mac": {
- "category": "public.app-category.productivity"
+ "category": "public.app-category.productivity",
+ "hardenedRuntime": true,
+ "entitlements": "build/entitlements.mac.plist",
+ "entitlementsInherit": "build/entitlements.mac.plist",
+ "target": ["dmg"],
+ "artifactName": "${productName}.${ext}"
},
+ "icon": "assets/icon.icns",
+
"files": [
"src/**/*",
"StreamSage/**/*",
@@ -45,8 +53,13 @@
"filter": [
"**/*"
]
+ },
+ {
+ "from": "src/assets/",
+ "to": "assets"
}
],
+
"afterPack": "./after-pack.js"
}
}
diff --git a/src/S_16.png b/src/S_16.png
new file mode 100644
index 0000000..d7f95ba
Binary files /dev/null and b/src/S_16.png differ
diff --git a/src/S_210.png b/src/S_210.png
new file mode 100644
index 0000000..efd0d09
Binary files /dev/null and b/src/S_210.png differ
diff --git a/src/S_32.png b/src/S_32.png
new file mode 100644
index 0000000..f8a2b76
Binary files /dev/null and b/src/S_32.png differ
diff --git a/src/S_64.png b/src/S_64.png
new file mode 100644
index 0000000..f5557c6
Binary files /dev/null and b/src/S_64.png differ
diff --git a/src/assets/beep.mp3 b/src/assets/beep.mp3
new file mode 100644
index 0000000..9b6561c
Binary files /dev/null and b/src/assets/beep.mp3 differ
diff --git a/src/assets/hey.m4a b/src/assets/hey.m4a
new file mode 100644
index 0000000..b3d07e2
Binary files /dev/null and b/src/assets/hey.m4a differ
diff --git a/src/assets/icon.icns b/src/assets/icon.icns
new file mode 100644
index 0000000..3e58f42
Binary files /dev/null and b/src/assets/icon.icns differ
diff --git a/src/download_512.png b/src/download_512.png
new file mode 100644
index 0000000..04b4bd8
Binary files /dev/null and b/src/download_512.png differ
diff --git a/src/icon.icns b/src/icon.icns
new file mode 100644
index 0000000..3e58f42
Binary files /dev/null and b/src/icon.icns differ
diff --git a/streamsage_extension/icons/icon16.png b/src/icon16.png
similarity index 100%
rename from streamsage_extension/icons/icon16.png
rename to src/icon16.png
diff --git a/src/index.html b/src/index.html
index 4d0559c..b4139d0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,8 +1,8 @@
-
-
+
+
Mic: Unmuted
Press (M) to mute, (R) to record
+
+
+
+
Idle
-
-
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index a03866e..54ac771 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,6 +4,7 @@ const path = require('path');
const fs = require('fs');
const dotenv = require('dotenv');
const WebSocket = require('ws');
+const store = require('./store');
// Load environment variables from .env file
dotenv.config();
@@ -37,7 +38,7 @@ async function listDevices()
}
function createTray() {
- tray = new Tray(path.join(__dirname, 'icon16.png')); // Replace with your icon path
+ tray = new Tray(path.join(__dirname, 'S_16.png')); // Replace with your icon path
const contextMenu = Menu.buildFromTemplate([
{
label: 'Help',
@@ -79,10 +80,25 @@ function createWindow()
{
nodeIntegration: true, // Enable Node.js integration
contextIsolation: false, // Disable context isolation
+ // Add these permissions
+ webSecurity: true,
+ allowRunningInsecureContent: false,
+ enableRemoteModule: true,
devTools: true // Enable developer tools
},
show: false,
});
+ // Request microphone permission
+ mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
+ const allowedPermissions = ['media', 'microphone'];
+ if (allowedPermissions.includes(permission)) {
+ callback(true);
+ }
+ else
+ {
+ callback(false);
+ }
+ });
// Load the main HTML file into the window
mainWindow.loadFile('src/index.html');
@@ -105,8 +121,11 @@ function createWindow()
return false;
});
- // Initialize the backend processor
- initializeBackendProcessor();
+ // Initialize backend processor if API key exists
+ const apiKey = store.get('groqApiKey');
+ if (apiKey) {
+ initializeBackendProcessor();
+ }
// Enable the console window for debugging
// mainWindow.webContents.openDevTools();
@@ -126,8 +145,8 @@ function handleSaveAudio(arrayBuffer, fileName)
return; // Exit the function if the data is invalid
}
- // Construct the file path for saving the audio
- const filePath = path.join(__dirname, '..', 'StreamSage', fileName);
+ // Construct the file path for saving the audio using app.getPath('userData')
+ const filePath = path.join(app.getPath('userData'), fileName);
const buffer = Buffer.from(arrayBuffer); // Convert ArrayBuffer to Buffer
// Write the buffer to the specified file
@@ -166,8 +185,8 @@ function handleSaveQuestion(arrayBuffer)
return; // Exit the function if the data is invalid
}
- // Construct the file path for saving the user question audio
- const filePath = path.join(__dirname, '..', 'StreamSage', 'user_question.wav');
+ // Construct the file path for saving the user question audio using app.getPath('userData')
+ const filePath = path.join(app.getPath('userData'), 'user_question.wav');
const buffer = Buffer.from(arrayBuffer); // Convert ArrayBuffer to Buffer
// Write the buffer to the specified file
@@ -326,7 +345,7 @@ ipcMain.on('save-question', (event, arrayBuffer) =>
// });
// Clean up on app quit
-const filePathToDelete = path.join(__dirname, `../StreamSage/${screenAudio_filename}`); // Define the file path to delete
+const filePathToDelete = path.join(app.getPath('userData'), screenAudio_filename); // Define the file path to delete
app.on('before-quit', () =>
{
@@ -368,4 +387,13 @@ function sendToggleCommand(isMuted)
}
}
);
-}
\ No newline at end of file
+}
+
+// Handle API key updates
+ipcMain.on('api-key-updated', (event, apiKey) => {
+ if (backendProcessor) {
+ backendProcessor.updateApiKey(apiKey);
+ } else {
+ initializeBackendProcessor();
+ }
+});
\ No newline at end of file
diff --git a/src/main_backend.js b/src/main_backend.js
index 364b5fd..427e9df 100644
--- a/src/main_backend.js
+++ b/src/main_backend.js
@@ -1,30 +1,61 @@
const { Groq } = require('groq-sdk');
const { recordAudioWithSox } = require('./mic_record');
-// Initialize the Groq client
-const groq = new Groq();
const { MacTTS, DeepGram } = require('./tts');
const fs_promises = require('fs').promises;
const path = require('path');
const { spawn } = require('child_process');
-const dotenv = require('dotenv');
+// const dotenv = require('dotenv');
const { exec } = require('child_process');
+const store = require('./store');
+const { app } = require('electron');
const fs = require("fs");
// Load environment variables from .env file
-dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
-
+// dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
+function createLogger()
+{
+ const logFile = path.join(app.getPath('userData'), 'backend.log');
+
+ return (message, tag = '') => {
+ const timestamp = new Date().toISOString();
+ const logMessage = `[${timestamp}] [${tag || 'BACKEND'}] ${message}\n`;
+
+ // Log to console
+ console.log(logMessage);
+
+ // Append to file
+ try {
+ fs.appendFileSync(logFile, logMessage);
+ } catch (error) {
+ console.error('Failed to write to log file:', error);
+ }
+ };
+}
+const log = createLogger();
class BackendProcessor
{
constructor(mainWindow)
{
this.mainWindow = mainWindow;
- this.file_to_watch = path.join(__dirname, "../StreamSage/recorded_audio.webm");
- this.user_question_file = "user_question.wav";
+ // Use app.getPath to get the user data directory and store files there
+ const userDataPath = app.getPath('userData');
+ this.file_to_watch = path.join(userDataPath, "recorded_audio.webm");
+ this.api_key = store.get('groqApiKey');
+
+ log('Initializing BackendProcessor');
+
+ if (!this.api_key)
+ {
+ log('No API key found', 'CONFIG');
+ this.mainWindow.webContents.send('show-api-key-prompt');
+ return;
+ }
+ this.user_question_file = path.join(userDataPath, "user_question.wav");
this.user_question = "";
this.voice_enabled = true;
this.muted = true;
@@ -38,13 +69,26 @@ class BackendProcessor
this.debug_enabled = true;
this.prev_modified_time = 0;
- this.transcriber = new Groq({ apiKey: process.env.GROQ_API_KEY });
- this.chat_client = new Groq({ apiKey: process.env.GROQ_API_KEY2 });
+ this.transcriber = new Groq({ apiKey: this.api_key });
+ this.chat_client = new Groq({ apiKey: this.api_key });
this.tts = process.platform === 'darwin' ? new MacTTS() : new DeepGram();
+ log(`Initialized with API key, watching file: ${this.file_to_watch}`, 'CONFIG');
+
+ // Set up audio paths properly for both development and production
+ if (app.isPackaged) {
+ // In production, use the resources path
+ this.audioPath = path.join(process.resourcesPath, 'assets', 'beep.mp3');
+ } else {
+ // In development, use the src path
+ this.audioPath = path.join(__dirname, 'assets', 'beep.mp3');
+ }
+
+ log(`Audio path set to: ${this.audioPath}`, 'CONFIG');
}
async sendStatusToRenderer(status)
{
+ log(`Status update: ${status}`, 'UI');
this.mainWindow.webContents.send('update-status', status);
}
log(message, tag = "")
@@ -58,33 +102,38 @@ class BackendProcessor
// Record audio from mic
async trigger_mic_recording(outputFile, sampleRate = 16000, device = 1)
{
- console.log('Triggering mic recording...');
+ log('Triggering mic recording...', 'AUDIO');
try
{
await recordAudioWithSox(outputFile, sampleRate, device);
- console.log('Mic recording completed successfully.');
+ log('Mic recording completed successfully', 'AUDIO');
}
catch (error)
{
- console.error('An error occurred during mic recording:', error);
+ log(`Mic recording error: ${error.message}`, 'ERROR');
throw error; // Re-throw the error to be caught in the main function
}
}
async groq_transcribe(filename)
{
- // Create a transcription job
- const transcription = await this.transcriber.audio.transcriptions.create({
- file: fs.createReadStream(filename), // Required path to audio file - replace with your audio file!
- model: "distil-whisper-large-v3-en", // Required model to use for transcription
- prompt: "Specify context or spelling", // Optional
- response_format: "json", // Optional
- language: "en", // Optional
- temperature: 0.0, // Optional
- });
- // Log the transcribed text
- return transcription;
+ log(`Starting transcription for file: ${filename}`, 'GROQ');
+ try {
+ const transcription = await this.transcriber.audio.transcriptions.create({
+ file: fs.createReadStream(filename), // Required path to audio file - replace with your audio file!
+ model: "distil-whisper-large-v3-en", // Required model to use for transcription
+ prompt: "Specify context or spelling", // Optional
+ response_format: "json", // Optional
+ language: "en", // Optional
+ temperature: 0.0, // Optional
+ });
+ log('Transcription completed successfully', 'GROQ');
+ return transcription;
+ } catch (error) {
+ log(`Transcription error: ${error.message}`, 'ERROR');
+ throw error;
+ }
}
async transcribeAudio(filename)
@@ -185,11 +234,12 @@ class BackendProcessor
async run()
{
+ log('Starting main processing loop', 'SYSTEM');
while (this.should_run)
{
await new Promise(resolve => setTimeout(resolve, 500)); // Check every 0.5 seconds
- console.log("is_recording: " + this.is_recording);
+ log(`Recording status: ${this.is_recording}`, 'STATUS');
// Check if the recording is currently active
// Log a message indicating that recording is active and we are waiting for a stop command
this.log("Recording is ON... Waiting for stop command", "main");
@@ -261,7 +311,7 @@ class BackendProcessor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (this.muted)
{
- this.log("Muted, skipping processing", "main");
+ log('System muted, skipping processing', 'STATUS');
continue;
}
this.playTingSound();
@@ -304,7 +354,7 @@ class BackendProcessor
this.sendStatusToRenderer("Answering...")
await this.tts.generateSpeech(answer);
console.log("TTS generated speech");
- await this.tts.playAudio();
+ // await this.tts.playAudio();
console.log("TTS played audio");
this.mainWindow.webContents.send('answer-play');
}
@@ -313,32 +363,34 @@ class BackendProcessor
}
playTingSound() {
- const audioPath = path.join(__dirname, 'assets', 'beep.mp3');
+ log('Playing notification sound', 'AUDIO');
+ log(`Using audio file: ${this.audioPath}`, 'AUDIO');
- if (process.platform === 'darwin') {
- // For macOS, use afplay
- exec(`afplay "${audioPath}"`, (error) => {
- if (error) {
- console.error('Error playing sound:', error);
- }
- });
- } else if (process.platform === 'win32') {
- // For Windows, you might use PowerShell
- exec(`powershell -c (New-Object Media.SoundPlayer "${audioPath}").PlaySync()`, (error) => {
- if (error) {
- console.error('Error playing sound:', error);
- }
- });
- } else {
- // For Linux, you might use paplay or another command
- console.log('Sound playback not implemented for this platform');
+ if (process.platform === 'darwin')
+ {
+ exec(`/usr/bin/afplay "${this.audioPath}"`, (error) => {
+ if (error) {
+ log(`Error playing sound: ${error.message}`, 'ERROR');
+ } else {
+ log('Sound played successfully', 'AUDIO');
+ }
+ });
+ } else if (process.platform === 'win32')
+ {
+ exec(`powershell -c (New-Object Media.SoundPlayer "${this.audioPath}").PlaySync()`, (error) => {
+ if (error) {
+ log(`Error playing sound: ${error.message}`, 'ERROR');
+ } else {
+ log('Sound played successfully', 'AUDIO');
+ }
+ });
}
}
// Methods to handle commands from main process
setMute(isMuted)
{
this.muted = isMuted;
- this.log(`Microphone ${this.muted ? 'muted' : 'unmuted'}`, "handle_commands");
+ log(`Microphone ${this.muted ? 'muted' : 'unmuted'}`, 'CONTROL');
// this.updateStatus();
}
@@ -354,7 +406,14 @@ class BackendProcessor
this.voice_enabled = isEnabled;
this.log(`Voice output ${this.voice_enabled ? 'enabled' : 'disabled'}`, "handle_commands");
}
-
+ updateApiKey(newApiKey)
+ {
+ log('Updating API key', 'CONFIG');
+ store.set('groqApiKey', newApiKey);
+ this.transcriber = new Groq({ apiKey: newApiKey });
+ this.chat_client = new Groq({ apiKey: newApiKey });
+ log('API key updated successfully', 'CONFIG');
+ }
// processQuestion()
// {
// this.generate_answer = true;
diff --git a/src/mic_record.js b/src/mic_record.js
index bbdd9fc..c36074d 100644
--- a/src/mic_record.js
+++ b/src/mic_record.js
@@ -1,4 +1,7 @@
const { exec } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const { app } = require('electron');
/**
* Records audio using SoX and saves it to a specified output file.
@@ -9,44 +12,58 @@ const { exec } = require('child_process');
*/
function recordAudioWithSox(outputFile, sampleRate = 16000, device = 1)
{
+ const logFile = path.join(app.getPath('userData'), 'mic_record.log');
+ console.log("logFile", logFile)
+ const logMessage = (msg) =>
+ {
+ console.log(msg);
+ fs.appendFileSync(logFile, msg + '\n');
+ };
+
+ logMessage(`MIC ON : outputFile ${outputFile}`);
+ const escapedPath = `"${outputFile}"`;
+
return new Promise((resolve, reject) =>
{
let cmd;
if (process.platform === 'darwin')
{
- cmd = `sox -d ${outputFile} rate ${sampleRate} silence 1 0.1 3% 1 2.0 3%`;
+ cmd = `/opt/homebrew/bin/sox -d ${escapedPath} rate ${sampleRate} silence 1 0.1 1% 1 2.0 1%`;
}
else if (process.platform === 'win32')
{
- cmd = `sox -t waveaudio ${device} ${outputFile} rate ${sampleRate} silence 1 0.1 3% 1 3.0 3%`;
+ cmd = `/opt/homebrew/bin/sox -t waveaudio ${device} ${escapedPath} rate ${sampleRate} silence 1 0.1 3% 1 3.0 3%`;
}
else
{
- console.log('Not implemented for this platform:', process.platform);
- reject(new Error('Not implemented for this platform:', process.platform));
+ const errMsg = `Not implemented for this platform: ${process.platform}`;
+ logMessage(errMsg);
+ reject(new Error(errMsg));
}
- console.log(`Starting audio recording with command: ${cmd}`);
+ logMessage(`Starting audio recording with command: ${cmd}`);
exec(cmd, { stdio: 'inherit' }, (error, stdout, stderr) =>
{
if (error)
{
- console.error(`Error occurred during recording: ${error.message}`);
+ const errMsg = `Error occurred during recording: ${error.message}`;
+ logMessage(errMsg);
reject(error);
return;
}
if (stderr)
{
- console.warn(`Warning: ${stderr}`);
+ logMessage(`Warning: ${stderr}`);
}
if (stdout)
{
- console.log(`SoX output: ${stdout}`);
+ logMessage(`SoX output: ${stdout}`);
}
- console.log(`Recording completed successfully. Output saved to: ${outputFile}`);
- console.log(`Recording parameters: Sample rate: ${sampleRate}Hz, Silence duration: 3s`);
+ logMessage(`Recording completed successfully. Output saved to: ${escapedPath}`);
+ logMessage(`Recording parameters: Sample rate: ${sampleRate}Hz, Silence duration: 3s`);
resolve();
});
+ logMessage(`Recording completed successfully. Output saved to: ${escapedPath}`);
});
}
diff --git a/src/renderer.js b/src/renderer.js
index 6c6878d..88dea4d 100644
--- a/src/renderer.js
+++ b/src/renderer.js
@@ -1,4 +1,5 @@
const { ipcRenderer } = require('electron');
+const store = require('./store');
const { startRecording, stopRecording } = require('./screenCapture');
const { startMicrophoneRecording, stopMicrophoneRecording } = require('./micCapture');
@@ -240,4 +241,39 @@ updateStatus('Idle');
initVisualization();
// Add click event listener to the canvas
-canvas.addEventListener('click', handleCanvasClick);
\ No newline at end of file
+canvas.addEventListener('click', handleCanvasClick);
+
+// Handle saving API key
+document.getElementById('saveApiKey').addEventListener('click', () => {
+ const apiKey = document.getElementById('apiKeyInput').value.trim();
+ if (apiKey) {
+ store.set('groqApiKey', apiKey);
+ ipcRenderer.send('api-key-updated', apiKey);
+
+ // Show success message
+ const button = document.getElementById('saveApiKey');
+ button.textContent = 'Saved!';
+ setTimeout(() => {
+ button.textContent = 'Save';
+ }, 1000);
+ }
+});
+
+async function requestMicrophoneAccess() {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ console.log('Microphone access granted');
+ stream.getTracks().forEach(track => track.stop()); // Stop the test stream
+ } catch (error) {
+ console.error('Error accessing microphone:', error);
+ }
+}
+// Load saved API key when app starts
+window.addEventListener('DOMContentLoaded', async () => {
+ await requestMicrophoneAccess();
+
+ const savedApiKey = store.get('groqApiKey');
+ if (savedApiKey) {
+ document.getElementById('apiKeyInput').value = savedApiKey;
+ }
+});
\ No newline at end of file
diff --git a/src/store.js b/src/store.js
new file mode 100644
index 0000000..1ea6c13
--- /dev/null
+++ b/src/store.js
@@ -0,0 +1,10 @@
+// store.js
+const Store = require('electron-store');
+
+const store = new Store({
+ defaults: {
+ groqApiKey: null
+ }
+});
+
+module.exports = store;
\ No newline at end of file
diff --git a/src/tts.js b/src/tts.js
index 8e43c15..73071db 100644
--- a/src/tts.js
+++ b/src/tts.js
@@ -1,182 +1,230 @@
-// Import required libraries (you may need to install these via npm)
const dotenv = require('dotenv');
const axios = require('axios');
const fs = require('fs');
const { exec } = require('child_process');
+const path = require('path');
+const { app } = require('electron');
dotenv.config();
-const DEEPGRAM_API_KEY = process.env.DEEPGRAM_API_KEY;
-const COQUI_LOCAL_URL = process.env.COQUI_LOCAL_URL;
-const PIPER_LOCAL_URL = process.env.PIPER_LOCAL_URL;
+// Setup logging
+function createLogger()
+{
+ const logFile = path.join(app.getPath('userData'), 'mic_record.log');
+
+ return (message) => {
+ const timestamp = new Date().toISOString();
+ const logMessage = `[${timestamp}] [TTS] ${message}\n`;
+
+ // Log to console
+ console.log(logMessage);
+
+ // Append to file
+ try {
+ fs.appendFileSync(logFile, logMessage);
+ } catch (error) {
+ console.error('Failed to write to log file:', error);
+ }
+ };
+}
+
+const log = createLogger();
class SpeechGenerator {
- constructor() {
- if (this.constructor === SpeechGenerator) {
- throw new Error("Abstract classes can't be instantiated.");
+ constructor()
+ {
+ if (this.constructor === SpeechGenerator) {
+ throw new Error("Abstract classes can't be instantiated.");
+ }
}
- }
- async generateSpeech(text, filename = "audio.mp3") {
- throw new Error("Method 'generateSpeech()' must be implemented.");
- }
+ async generateSpeech(text, filename = "audio.mp3") {
+ throw new Error("Method 'generateSpeech()' must be implemented.");
+ }
- async playAudio(filename) {
- throw new Error("Method 'playAudio()' must be implemented.");
- }
+ async playAudio(filename) {
+ throw new Error("Method 'playAudio()' must be implemented.");
+ }
- stopAudio() {
- throw new Error("Method 'stopAudio()' must be implemented.");
- }
+ stopAudio() {
+ throw new Error("Method 'stopAudio()' must be implemented.");
+ }
}
-class DeepGram extends SpeechGenerator {
- constructor() {
- super();
- // Initialize DeepGram client here
- }
-
- async generateSpeech(text, filename = "audio.mp3") {
- try {
- // Implement DeepGram API call here
- console.log(`Generated speech saved to ${filename}`);
- return filename;
- } catch (error) {
- console.error(`Exception: ${error}`);
- return null;
- }
- }
-
- async playAudio(filename) {
- // Implement audio playback (this might require a browser environment or additional libraries)
- console.log(`Playing audio: ${filename}`);
- }
-
- stopAudio() {
- // Implement stop audio functionality
- console.log("Audio playback stopped");
- }
-}
+class DeepGram extends SpeechGenerator
+{
+ constructor()
+ {
+ super();
+ log('DeepGram TTS initialized');
+ }
-class Coqui extends SpeechGenerator {
- constructor(url = COQUI_LOCAL_URL) {
- super();
- this.url = url;
- }
-
- async generateSpeech(text, filename = "audio.mp3") {
- try {
- const response = await axios.post(this.url, null, { params: { text } });
- if (response.status === 200) {
- fs.writeFileSync(filename, response.data);
- console.log(`Audio saved to ${filename}`);
- return filename;
- } else {
- console.error(`Error: ${response.status}`);
- console.error(response.data);
- return null;
- }
- } catch (error) {
- console.error(`Exception: ${error}`);
- return null;
- }
- }
-
- async playAudio(filename) {
- // Implement audio playback (this might require a browser environment or additional libraries)
- console.log(`Playing audio: ${filename}`);
- }
-
- stopAudio() {
- // Implement stop audio functionality
- console.log("Audio playback stopped");
- }
-}
+ async generateSpeech(text, filename = "audio.mp3")
+ {
+ try {
+ log(`Attempting to generate speech via DeepGram: "${text.substring(0, 50)}..."`);
+ // Implement DeepGram API call here
+ log(`Generated speech saved to ${filename}`);
+ return filename;
+ } catch (error) {
+ log(`DeepGram error: ${error.message}`);
+ return null;
+ }
+ }
+
+ async playAudio(filename)
+ {
+ log(`Playing audio: ${filename}`);
+ }
-class Piper extends SpeechGenerator {
- constructor(url = PIPER_LOCAL_URL) {
- super();
- this.url = url;
- }
-
- async generateSpeech(text, filename = "audio.mp3") {
- try {
- const response = await axios.post(this.url, null, { params: { text } });
- if (response.status === 200) {
- fs.writeFileSync(filename, response.data);
- console.log(`Audio saved to ${filename}`);
- return filename;
- } else {
- console.error(`Error: ${response.status}`);
- console.error(response.data);
- return null;
- }
- } catch (error) {
- console.error(`Exception: ${error}`);
- return null;
- }
- }
-
- async playAudio(filename) {
- // Implement audio playback (this might require a browser environment or additional libraries)
- console.log(`Playing audio: ${filename}`);
- }
-
- stopAudio() {
- // Implement stop audio functionality
- console.log("Audio playback stopped");
- }
+ stopAudio()
+ {
+ log("Stopping DeepGram audio playback");
+ }
}
-class MacTTS extends SpeechGenerator {
- constructor() {
- super();
- this.process = null;
- this.defaultFilename = "audio.aiff"; // Default filename
- }
-
- async generateSpeech(text, filename = this.defaultFilename) {
- return new Promise((resolve, reject) => {
- exec(`say -o ${filename} "${text}"`, (error) => {
- if (error) {
- console.error(`Error occurred while trying to generate speech: ${error}`);
- reject(null);
- } else {
- console.log(`Audio saved to ${filename}`);
- resolve(filename);
+class Coqui extends SpeechGenerator
+{
+ constructor(url = COQUI_LOCAL_URL)
+ {
+ super();
+ this.url = url;
+ log(`Coqui TTS initialized with URL: ${url}`);
+ }
+
+ async generateSpeech(text, filename = "audio.mp3")
+ {
+ try {
+ log(`Generating speech via Coqui: "${text.substring(0, 50)}..."`);
+ const response = await axios.post(this.url, null, { params: { text } });
+
+ if (response.status === 200) {
+ fs.writeFileSync(filename, response.data);
+ log(`Coqui audio saved to ${filename}`);
+ return filename;
+ } else {
+ log(`Coqui error: ${response.status} - ${response.statusText}`);
+ return null;
+ }
+ } catch (error) {
+ log(`Coqui exception: ${error.message}`);
+ return null;
}
- });
- });
- }
-
- async playAudio(filename = this.defaultFilename) {
- return new Promise((resolve, reject) => {
- this.process = exec(`afplay ${filename}`, (error) => {
- if (error) {
- console.error(`Error playing audio: ${error}`);
- reject(error);
- } else {
- console.log("Audio played successfully");
- resolve();
+ }
+
+ async playAudio(filename)
+ {
+ log(`Playing Coqui audio: ${filename}`);
+ }
+
+ stopAudio()
+ {
+ log("Stopping Coqui audio playback");
+ }
+}
+
+class Piper extends SpeechGenerator
+{
+ constructor(url = PIPER_LOCAL_URL)
+ {
+ super();
+ this.url = url;
+ log(`Piper TTS initialized with URL: ${url}`);
+ }
+
+ async generateSpeech(text, filename = "audio.mp3")
+ {
+ try {
+ log(`Generating speech via Piper: "${text.substring(0, 50)}..."`);
+ const response = await axios.post(this.url, null, { params: { text } });
+
+ if (response.status === 200) {
+ fs.writeFileSync(filename, response.data);
+ log(`Piper audio saved to ${filename}`);
+ return filename;
+ } else {
+ log(`Piper error: ${response.status} - ${response.statusText}`);
+ return null;
+ }
+ } catch (error) {
+ log(`Piper exception: ${error.message}`);
+ return null;
}
- });
- });
- }
+ }
+
+ async playAudio(filename)
+ {
+ log(`Playing Piper audio: ${filename}`);
+ }
- stopAudio() {
- if (this.process) {
- this.process.kill();
- console.log("Audio playback stopped");
+ stopAudio()
+ {
+ log("Stopping Piper audio playback");
}
- }
}
-// ... (other functions like recordAudio, transcribeAudio, chatWithModel, etc.)
+class MacTTS extends SpeechGenerator
+{
+ constructor()
+ {
+ super();
+ this.process = null;
+ this.defaultFilename = "audio.aiff";
+ log('MacTTS initialized');
+ }
+
+ async generateSpeech(text, filename = this.defaultFilename)
+ {
+ return new Promise((resolve, reject) => {
+ log(`Generating speech for text: "${text.substring(0, 50)}..."`);
+ log(`Output filename: ${filename}`);
+
+ exec(`/usr/bin/say "${text}"`, (error) => {
+ if (error) {
+ const errorMsg = `Error generating speech: ${error.message}`;
+ log(errorMsg);
+ reject(error);
+ } else {
+ log(`Speech generated successfully: ${filename}`);
+ resolve(filename);
+ }
+ });
+ });
+ }
+
+ async playAudio(filename = this.defaultFilename)
+ {
+ return new Promise((resolve, reject) => {
+ log(`Playing audio file: ${filename}`);
+
+ this.process = exec(`afplay "${filename}"`, (error) => {
+ if (error) {
+ const errorMsg = `Error playing audio: ${error.message}`;
+ log(errorMsg);
+ reject(error);
+ } else {
+ log('Audio playback completed successfully');
+ resolve();
+ }
+ });
+ });
+ }
+
+ stopAudio()
+ {
+ if (this.process) {
+ log('Stopping audio playback');
+ this.process.kill();
+ log('Audio playback stopped');
+ } else {
+ log('No audio playback to stop');
+ }
+ }
+}
module.exports = {
- DeepGram,
- Coqui,
- Piper,
- MacTTS,
- // ... (export other functions as needed)
+ DeepGram,
+ Coqui,
+ Piper,
+ MacTTS
};
\ No newline at end of file
diff --git a/streamsage_extension/background.js b/streamsage_extension/background.js
deleted file mode 100644
index 7ee28de..0000000
--- a/streamsage_extension/background.js
+++ /dev/null
@@ -1,158 +0,0 @@
-// Ping the server every 30 seconds to keep the connection alive
-setInterval(() => {
- if (socket.readyState === WebSocket.OPEN) {
- socket.send(JSON.stringify({ action: 'ping' }));
- console.log('Ping sent to server');
- }
-}, 30000); // 30 seconds
-
-chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
-{
- if (request.action === "toggleMedia")
- {
- chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
- {
- if (tabs.length === 0) {
- console.warn('No active tabs found.');
- return;
- }
-
- const activeTab = tabs[0];
- console.log('Executing toggleMediaPlayback in active tab:', activeTab.id);
-
- chrome.scripting.executeScript(
- {
- target: {tabId: activeTab.id},
- function: toggleMediaPlayback
- },
- () => {
- if (chrome.runtime.lastError) {
- console.error('executeScript error:', chrome.runtime.lastError);
- } else {
- console.log('Successfully executed toggleMediaPlayback in tab:', activeTab.id);
- }
- }
- );
- });
- }
- else if (request.action === "updateStatus")
- {
- updateStatus(request.status)
- }
-});
-
-function toggleMediaPlayback()
-{
- console.log('toggleMediaPlayback function invoked.');
- const mediaElements = document.querySelectorAll('video, audio');
-
- if (mediaElements.length === 0)
- {
- alert('No media elements found on this page.');
- console.log('No media elements found on this page.');
- return;
- }
-
- mediaElements.forEach(function(media)
- {
- if (media.paused)
- {
- media.play().then(() => {
- console.log('Media played:', media);
- }).catch(error => {
- console.error('Error playing media:', error);
- });
- }
- else
- {
- media.pause();
- console.log('Media paused:', media);
- }
- });
-}
-
-function updateStatus(status)
-{
- // Handle status updates if needed
- console.log('Media status:', status)
-}
-
-// Initialize WebSocket connection
-console.log('Attempting to establish WebSocket connection to ws://127.0.0.1:3000/');
-const socket = new WebSocket('ws://127.0.0.1:3000/');
-
-socket.addEventListener('open', function (event) {
- console.log('WebSocket connection established in background.js');
- // You can send messages here
-});
-
-socket.addEventListener('error', function (event) {
- console.error('WebSocket error in background.js:', event);
- // Implement additional error handling if necessary
-});
-
-socket.addEventListener('message', function (event) {
- console.log('Message from server in background.js:', event.data);
- try {
- const message = JSON.parse(event.data);
- if (message.action === 'toggleMedia') {
- console.log('Received toggleMedia command from WebSocket server');
-
- // Query the active tab in the current window
- chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
- if (tabs.length === 0) {
- console.warn('No active tabs found.');
- return;
- }
-
- const activeTab = tabs[0];
- console.log('Active tab ID:', activeTab.id);
-
- // Execute the toggleMediaPlayback function in the active tab
- chrome.scripting.executeScript(
- {
- target: {tabId: activeTab.id},
- function: toggleMediaPlayback
- },
- () => {
- if (chrome.runtime.lastError) {
- console.error('executeScript error:', chrome.runtime.lastError);
- } else {
- console.log('Executed toggleMediaPlayback in active tab:', activeTab.id);
- }
- }
- );
- });
- } else {
- console.warn('Unknown action received:', message.action);
- }
- } catch (error) {
- console.error('Error parsing message from WebSocket:', error);
- }
-});
-
-socket.addEventListener('close', function (event) {
- console.log('WebSocket connection closed in background.js:', event);
- // Implement reconnection logic if necessary
-});
-
-// Remove the duplicate message listener to avoid conflicts
-// chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
-// if (message.action === 'toggleMedia') {
-// console.log('Received toggleMedia command from content.js');
-// // Forward the toggle command to the WebSocket server
-// socket.send(JSON.stringify({ action: 'toggleMedia' }));
-// }
-// });
-
-// Function to send toggle command to all connected clients (if needed)
-function sendToggleCommand() {
- const message = JSON.stringify({ action: 'toggleMedia' });
- console.log('Sending toggleMedia command to all connected clients');
- wss.clients.forEach(client => {
- if (client.readyState === WebSocket.OPEN) {
- client.send(message);
- console.log('Sent toggleMedia command to a client');
- }
- });
-}
\ No newline at end of file
diff --git a/streamsage_extension/com.example.media_controller.json b/streamsage_extension/com.example.media_controller.json
deleted file mode 100644
index 86857fa..0000000
--- a/streamsage_extension/com.example.media_controller.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "com.example.media_controller",
- "description": "Native host for Media Controller extension",
- "path": "path/to/nativeHost.js",
- "type": "stdio",
- "allowed_origins": [
- "chrome-extension://YOUR_EXTENSION_ID/"
- ]
-}
\ No newline at end of file
diff --git a/streamsage_extension/content.js b/streamsage_extension/content.js
deleted file mode 100644
index 2c4c1e8..0000000
--- a/streamsage_extension/content.js
+++ /dev/null
@@ -1,57 +0,0 @@
-(() => {
- // Remove direct WebSocket connection from content.js
-
- function toggleMediaPlayback() {
- const mediaElements = document.querySelectorAll('video, audio');
-
- if (mediaElements.length === 0) {
- console.log('No media elements found on this page.');
- return;
- }
-
- mediaElements.forEach(media => {
- if (media.paused) {
- media.play();
- } else {
- media.pause();
- }
- });
-
- // Send a message to background.js to handle the toggle command
- chrome.runtime.sendMessage({ action: 'toggleMedia' }, (response) => {
- if (chrome.runtime.lastError) {
- console.error('Error sending message to background:', chrome.runtime.lastError);
- } else {
- console.log('Toggle command sent to background.js');
- }
- });
-
- console.log('Attempting to establish WebSocket connection to ws://localhost:3000/');
- const socket = new WebSocket('ws://localhost:3000/');
-
- socket.addEventListener('open', function (event) {
- console.log('WebSocket connection established');
- // You can send messages here
- });
-
- socket.addEventListener('error', function (event) {
- console.error('WebSocket error:', event);
- console.log('Error details:', event.message || event);
- });
-
- socket.addEventListener('message', function (event) {
- console.log('Message from server:', event.data);
- });
-
- socket.addEventListener('close', function (event) {
- console.log('WebSocket connection closed:', event);
- });
- }
-
- // Example: Add a keyboard shortcut or button to trigger toggleMediaPlayback
- document.addEventListener('keydown', (e) => {
- if (e.ctrlKey && e.key === 'm') { // Example: Ctrl + M
- toggleMediaPlayback();
- }
- });
-})();
\ No newline at end of file
diff --git "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (1).png" "b/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (1).png"
deleted file mode 100644
index 5c24361..0000000
Binary files "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (1).png" and /dev/null differ
diff --git "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (2).png" "b/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (2).png"
deleted file mode 100644
index 9b54940..0000000
Binary files "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (2).png" and /dev/null differ
diff --git "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (3).png" "b/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (3).png"
deleted file mode 100644
index a8c12f8..0000000
Binary files "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (3).png" and /dev/null differ
diff --git "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (4).png" "b/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (4).png"
deleted file mode 100644
index a4b639f..0000000
Binary files "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM (4).png" and /dev/null differ
diff --git "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM.png" "b/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM.png"
deleted file mode 100644
index 9b54940..0000000
Binary files "a/streamsage_extension/icons/Screenshot 2024-10-06 at 9.43.56\342\200\257PM.png" and /dev/null differ
diff --git a/streamsage_extension/icons/icon128.png b/streamsage_extension/icons/icon128.png
deleted file mode 100644
index f61e6b2..0000000
Binary files a/streamsage_extension/icons/icon128.png and /dev/null differ
diff --git a/streamsage_extension/icons/icon48.png b/streamsage_extension/icons/icon48.png
deleted file mode 100644
index 7b4fbbe..0000000
Binary files a/streamsage_extension/icons/icon48.png and /dev/null differ
diff --git a/streamsage_extension/icons/icons.jpg b/streamsage_extension/icons/icons.jpg
deleted file mode 100644
index 985659d..0000000
Binary files a/streamsage_extension/icons/icons.jpg and /dev/null differ
diff --git a/streamsage_extension/index.html b/streamsage_extension/index.html
deleted file mode 100644
index 21c9bbf..0000000
--- a/streamsage_extension/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- Electron Control Panel
-
-
- Electron Control Panel
-
-
-
-
\ No newline at end of file
diff --git a/streamsage_extension/main.js b/streamsage_extension/main.js
deleted file mode 100644
index 29b196b..0000000
--- a/streamsage_extension/main.js
+++ /dev/null
@@ -1,81 +0,0 @@
-const { app, BrowserWindow, ipcMain } = require('electron');
-const path = require('path');
-const WebSocket = require('ws');
-
-let mainWindow;
-let wss;
-
-function createWindow () {
- mainWindow = new BrowserWindow({
- width: 800,
- height: 600,
- webPreferences: {
- preload: path.join(__dirname, 'preload.js'),
- nodeIntegration: false,
- contextIsolation: true
- }
- });
-
- mainWindow.loadFile('index.html');
-}
-
-app.whenReady().then(() => {
- createWindow();
-
- console.log('Initializing WebSocket server on port 3000');
- // Initialize WebSocket server on port 3000
- wss = new WebSocket.Server({ port: 3000 }, () => {
- console.log('WebSocket Server is listening on ws://localhost:3000');
- });
-
- wss.on('connection', (ws) => {
- console.log('A new client Connected!');
- ws.send('Welcome New Client!');
-
- ws.on('message', (message) => {
- console.log('received: %s', message);
- // Broadcast to all connected clients
- wss.clients.forEach((client) => {
- if (client !== ws && client.readyState === WebSocket.OPEN) {
- client.send(message);
- console.log('Broadcasted message to a client');
- }
- });
- });
-
- ws.on('error', (error) => {
- console.error('WebSocket server error:', error);
- });
-
- ws.on('close', () => {
- console.log('Chrome extension disconnected');
- });
- });
-
- // Handle IPC from renderer to send commands via WebSocket
- ipcMain.on('send-toggle', (event, arg) => {
- console.log('Received toggle command from renderer');
- sendToggleCommand();
- });
-
- app.on('activate', function () {
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
- });
-});
-
-app.on('window-all-closed', function () {
- console.log('All windows closed. Quitting app.');
- if (process.platform !== 'darwin') app.quit();
-});
-
-// Function to send toggle command to all connected clients
-function sendToggleCommand() {
- const message = JSON.stringify({ action: 'toggleMedia' });
- console.log('Sending toggleMedia command to all connected clients');
- wss.clients.forEach(client => {
- if (client.readyState === WebSocket.OPEN) {
- client.send(message);
- console.log('Sent toggleMedia command to a client');
- }
- });
-}
\ No newline at end of file
diff --git a/streamsage_extension/manifest.json b/streamsage_extension/manifest.json
deleted file mode 100644
index 5bc7470..0000000
--- a/streamsage_extension/manifest.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "manifest_version": 3,
- "name": "Media Controller",
- "version": "1.0",
- "description": "Play or pause media using WebSocket commands and a browser button.",
- "permissions":
- [
- "activeTab",
- "scripting"
- ],
- "host_permissions":
- [
- "ws://127.0.0.1:3000/*"
- ],
- "background":
- {
- "service_worker": "background.js"
- },
- "action":
- {
- "default_popup": "popup.html",
- "default_icon":
- {
- "16": "icons/icon16.png",
- "48": "icons/icon48.png",
- "128": "icons/icon128.png"
- }
- },
- "icons":
- {
- "16": "icons/icon16.png",
- "48": "icons/icon48.png",
- "128": "icons/icon128.png"
- },
- "content_scripts":
- [
- {
- "matches": [""],
- "js": ["content.js"]
- }
- ]
-}
\ No newline at end of file
diff --git a/streamsage_extension/native/nativeHost.js b/streamsage_extension/native/nativeHost.js
deleted file mode 100644
index 64514b4..0000000
--- a/streamsage_extension/native/nativeHost.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const http = require('http')
-const readline = require('readline')
-
-const PORT = 8080
-
-// Function to send messages to the Chrome extension
-function sendMessageToExtension(message)
-{
- // Implement Native Messaging protocol to communicate with the extension
- // This typically involves reading from stdin and writing to stdout with proper framing
- // For simplicity, this is a placeholder
- console.log(JSON.stringify(message))
-}
-
-// Create HTTP server
-const server = http.createServer((req, res) =>
-{
- if (req.method === 'POST' && req.url === '/toggle')
- {
- sendMessageToExtension({action: "toggleMedia"})
- res.writeHead(200, {'Content-Type': 'text/plain'})
- res.end('Toggle command sent.\n')
- }
- else
- {
- res.writeHead(404, {'Content-Type': 'text/plain'})
- res.end('Not Found\n')
- }
-})
-
-server.listen(PORT, () =>
-{
- console.log(`Native host listening on port ${PORT}`)
-})
\ No newline at end of file
diff --git a/streamsage_extension/package.json b/streamsage_extension/package.json
deleted file mode 100644
index 63ecbdd..0000000
--- a/streamsage_extension/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "dependencies": {
- "electron": "^32.1.2",
- "ws": "^8.18.0"
- },
- "scripts": {
- "start": "node main.js"
- }
-}
diff --git a/streamsage_extension/popup.html b/streamsage_extension/popup.html
deleted file mode 100644
index af24987..0000000
--- a/streamsage_extension/popup.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
- Media Controller
-
-
-
- Connected to Electron app.
-
-
-
-
-
\ No newline at end of file
diff --git a/streamsage_extension/popup.js b/streamsage_extension/popup.js
deleted file mode 100644
index bc414ef..0000000
--- a/streamsage_extension/popup.js
+++ /dev/null
@@ -1,53 +0,0 @@
-document.addEventListener('DOMContentLoaded', function()
-{
- const statusDiv = document.getElementById('status')
- const toggleButton = document.getElementById('toggleButton')
-
- // Update status on load
- updateMediaStatus()
-
- // Add event listener to button
- toggleButton.addEventListener('click', function()
- {
- chrome.runtime.sendMessage({action: "toggleMedia"})
- setTimeout(updateMediaStatus, 500) // Delay to allow state change
- })
-
- function updateMediaStatus()
- {
- chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
- {
- chrome.scripting.executeScript(
- {
- target: {tabId: tabs[0].id},
- function: getMediaStatus
- },
- (results) =>
- {
- if (results && results[0] && results[0].result)
- {
- statusDiv.textContent = results[0].result
- }
- else
- {
- statusDiv.textContent = 'No media found'
- }
- }
- )
- })
- }
-})
-
-function getMediaStatus()
-{
- const mediaElements = document.querySelectorAll('video, audio')
-
- if (mediaElements.length === 0)
- {
- return 'No media on this page'
- }
-
- const isPlaying = Array.from(mediaElements).some(media => !media.paused)
-
- return isPlaying ? 'Playing' : 'Paused'
-}
\ No newline at end of file
diff --git a/streamsage_extension/preload.js b/streamsage_extension/preload.js
deleted file mode 100644
index 0519ecb..0000000
--- a/streamsage_extension/preload.js
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/streamsage_extension/renderer.js b/streamsage_extension/renderer.js
deleted file mode 100644
index a1346d9..0000000
--- a/streamsage_extension/renderer.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const { ipcRenderer } = require('electron');
-
-document.getElementById('toggleButton').addEventListener('click', () => {
- ipcRenderer.send('send-toggle', 'toggleMedia');
-});
\ No newline at end of file