diff --git a/app.js b/app.js index fbef980..059ebad 100644 --- a/app.js +++ b/app.js @@ -58,6 +58,11 @@ app.get('/speaker', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'speaker.html')); }); +// Serve speaker view +app.get('/live-typing', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'live_typing.html')); +}); + // Serve audience/projector view app.get('/audience', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'audience.html')); @@ -96,6 +101,10 @@ io.on('connection', (socket) => { updateOBSSubtitle(currentLine); generateSRT(currentLine); }); + + socket.on('live_typing', (text) => { + socket.broadcast.emit('live_typing', text); // Broadcast to audience + }); }); async function updateOBSSubtitle(captionText) { diff --git a/package.json b/package.json index 663db84..446dc5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "speechsub", - "version": "1.0.0", + "version": "0.2.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -18,7 +18,10 @@ "socket.io": "^4.8.0" }, "pkg": { - "assets": "public/*", + "assets": [ + "public/*", + "node_modules/socket.io/client-dist/**/*" + ], "outputPath": "dist" } } diff --git a/public/audience.html b/public/audience.html index ecf096e..a095928 100644 --- a/public/audience.html +++ b/public/audience.html @@ -15,14 +15,14 @@ padding: 0; -webkit-text-fill-color: white; -webkit-text-stroke-color: black; - -webkit-text-stroke-width: 6.00px; + /*-webkit-text-stroke-width: 6.00px; */ paint-order: stroke fill; } #container { overflow: hidden; height: 200px; /* Ensure container is tall enough for 4 lines */ - max-width: 42ch; + width: 42ch; position: relative; padding-left: 20px; /* Add some padding to the left */ margin-left: auto; @@ -30,17 +30,26 @@ } #content { - position: absolute; + position: relative; top: 0; - transition: top 0.5s ease; /* Smooth scrolling */ + left: auto; + right: auto; + /*transition: top 0.5s ease;*/ /* Smooth scrolling */ } .line { opacity: 1; + min-height: 44px; white-space: normal; /* Allow lines to wrap */ word-wrap: break-word; /* Break long words if needed */ margin: 5px 0; - } + line-height: 48px; + } + + .line span { + background-color: rgba(0, 0, 0, 0.7); + padding: 2px; + } .currentLine { font-weight: bold; @@ -77,7 +86,9 @@ const div = document.createElement('div'); div.classList.add('line'); if (index === 2) div.classList.add('currentLine'); // Highlight the current line - div.textContent = line; + const span = document.createElement('span'); + span.textContent = line; + div.appendChild(span); return div; }); @@ -101,6 +112,50 @@ currentLineIndex = 0; updateDisplay(); // Display the initial lines }); + + // Update audience text based on live typing + socket.on('live_typing', (text) => { + const lines = text.split('\n'); // Split by new lines + const fullLines = lines.slice(-3); // Get the last three lines + + // Check if the last line is complete + let lastLine = fullLines[fullLines.length - 1]; + const isLastWordComplete = lastLine.endsWith(' ') || lastLine.endsWith('.') || lastLine.endsWith(',') || lastLine.endsWith('!') || lastLine.endsWith('?') || lastLine.endsWith('\n'); + + // If the last word is not complete, hide it + if (!isLastWordComplete) { + lastLine = lastLine.trim().split(' ').slice(0, -1).join(' '); // Remove the last word + } + + // Join back the lines and update content + contentElement.innerHTML = ''; + // Create a new block of lines + let displayLines = (fullLines.slice(0, -1)).map((line, index) => { + const div = document.createElement('div'); + div.classList.add('line'); + const span = document.createElement('span'); + span.textContent = line; + div.appendChild(span); + return div; + }); + const lastLineDiv = document.createElement('div'); + lastLineDiv.classList.add('line'); + lastLineDiv.classList.add('currentLine'); + const lastLineSpan = document.createElement('span'); + lastLineSpan.textContent = lastLine; + lastLineDiv.appendChild(lastLineSpan); + displayLines.push(lastLineDiv) + + displayLines.forEach(div => contentElement.appendChild(div)); + + // Scroll animation based on the height of each line + //const lineHeight = document.querySelector('.line').offsetHeight; + //const previousLinesHeight = (currentLineIndex - start - 1) * lineHeight; // Offset for lines above the current line + const previousLinesHeight = document.querySelectorAll('.line')[1].offsetHeight; + contentElement.style.top = `-${previousLinesHeight}px`; + + }); + diff --git a/public/live_typing.html b/public/live_typing.html new file mode 100644 index 0000000..aefec76 --- /dev/null +++ b/public/live_typing.html @@ -0,0 +1,39 @@ + + + + + + Live Typing Speaker View + + + +

Live Typing Speaker View

+ + + + + +