Skip to content

Commit

Permalink
ui improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
jsalsman committed Nov 26, 2023
1 parent 5dc8b53 commit 38cc33d
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 93 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ for comments and code.
MIT license

# TODO
- link, size, and duration on the playback page (in progress)
- update screenshot and cover image with the noise supression checkbox
- production WSGI server? Replit deployments might not be cross-platform
- maybe companding with sox?
Expand Down
16 changes: 11 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
# dev: https://replit.com/@jsalsman/webrec
# github: https://github.com/jsalsman/webrec

from flask import Flask, request, render_template, redirect, send_from_directory
from flask import (Flask, request, render_template, redirect,
send_from_directory, url_for)
from flask_socketio import SocketIO # Fails over to POST submissions
import sox # needs command line sox and the pysox package
import lameenc # to produce .mp3 files for playback
Expand Down Expand Up @@ -109,9 +110,14 @@ def process_file(raw_filename):
f'All audio using {audio_space:.2f} MB.')
return mp3_fn

@app.route('/playback/<filename>')
def playback(filename):
return render_template('playback.html', audio=filename)
@app.route('/playback/<fn>')
def playback(fn):
size = os.path.getsize('static/' + fn) / 1024 # Size in KB
duration = sox.file_info.duration('static/' + fn) # Duration in seconds
full_url = request.url_root + 'get_audio/' + fn
clean_url = full_url.replace('http://', '').replace('https://', '')
return render_template('playback.html', audio=fn, url=clean_url,
size=f'{size:.1f}', duration=f'{duration:.1f}')

@app.route('/get-audio/<filename>') # download the trimmed audio
def get_audio(filename):
Expand All @@ -126,7 +132,7 @@ def send_js(path):
if f.startswith('audio-')]:
os.remove(file)

# WebSocket implementation
# Socket.IO implementation
active_streams = {}
sid_to_filename = {}

Expand Down
48 changes: 41 additions & 7 deletions templates/playback.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,52 @@
<title>Playback Audio</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/static/microphone.png">
<style>
html, body {
height: 100%; /* Full height for the body */
margin: 0;
}

body {
display: flex; /* Flexbox layout */
flex-direction: column; /* Vertical stacking */
justify-content: center; /* Center content vertically */
align-items: center; /* Center content horizontally */
text-align: center; /* including text lol */
font-family: 'Verdana', sans-serif;
background-color: #fafafa; /* very light gray */
-webkit-text-size-adjust: 100%;
}

a {
text-decoration: none; /* Removes underlining from links */
}

h1 {
font-family: 'Georgia', serif;
}

audio {
margin: 20px 0; /* Vertical margin for audio */
}
</style>
</head>
<body>
<h1>Playback Recorded Audio</h1>

<audio controls autoplay onended="window.location.href='/record';">
<source src="{{ url_for('get_audio', filename=audio) }}" type="audio/wav">
<source src="/get-audio/{{ audio }}" type="audio/mp3">
Your browser does not support the audio element.
</audio>
<script>
// Redirect to '/record' if the audio can't be played
document.querySelector('audio').onerror = function() {
window.location.href = '/record';
};
</script>

<div class="audio-info">
<a href="/get-audio/{{ audio }}">{{ url }}</a>
<br><br>
This link will persist for no less than ten minutes.
<br><br>
{{ duration }} seconds
<br><br>
{{ size }} KB
</div>
</body>
</html>
156 changes: 76 additions & 80 deletions templates/record.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,97 +169,93 @@ <h3>Apple Safari users: <a
// Flag for if Socket.IO is streaming, and how many chunks and kilobytes
var socket = false, chunksStreamed = 0, streamedKB = 0;

// Function executed when the window loads
// window.onload = async function() {
// Check if the Web Audio API is supported by the browser
if (!window.audioContext ) {
try {
// Polyfill for AudioContext for wider browser support
window.AudioContext =
window.AudioContext || window.webkitAudioContext;
// Create a new AudioContext with a specific sample rate
context = new AudioContext({ sampleRate: 16000 });
// Check if the AudioContext is active
if (context.state === 'running') {
gotContext = true;
} else {
// If suspended, AudioContext requires a user click to activate
document.getElementById('start').textContent = 'Detect Audio Levels';
document.getElementById('start').disabled = false;
}
} catch (e) {
// Handle the case where Web Audio API is not supported by the browser
document.getElementById('start').disabled = true;
alert('Web audio is not supported on this browser.');
}
} else {
// Initialize the AudioContext for browsers that support it natively
// Check if the Web Audio API is supported by the browser
if (!window.audioContext ) {
try {
// Polyfill for AudioContext for wider browser support
window.AudioContext =
window.AudioContext || window.webkitAudioContext;
// Create a new AudioContext with a specific sample rate
context = new AudioContext({ sampleRate: 16000 });
// Check and update context status
// Check if the AudioContext is active
if (context.state === 'running') {
gotContext = true;
} else {
// If AudioContext is suspended, enable it with user interaction
} else {
// If suspended, AudioContext requires a user click to activate
document.getElementById('start').textContent = 'Detect Audio Levels';
document.getElementById('start').disabled = false;
}
} catch (e) {
// Handle the case where Web Audio API is not supported by the browser
document.getElementById('start').disabled = true;
alert('Web audio is not supported on this browser.');
}

// Use WebRTC API to check for microphone access
navigator.mediaDevices.enumerateDevices().then(async devices => {
// Check if any of the media devices are microphones
if (devices.some(device => device.kind === 'audioinput'
&& device.label)) {
// The user has granted microphone access
if ( {{ force_click|tojson }} ) { // |tojson lowercases booleans
document.getElementById('start').textContent = 'Detect Audio Levels';
document.getElementById('start').disabled = false;
} else if (context.state === 'running') {
await initRecorder();
}
} else {
// Microphone access is not granted or no mic is available
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
// Special help for Apple Safari users on microphone permissions
document.getElementById('safari-help').style.display = 'block';
}
document.getElementById('start').textContent = 'Allow Microphone Use';
} else {
// Initialize the AudioContext for browsers that support it natively
context = new AudioContext({ sampleRate: 16000 });
// Check and update context status
if (context.state === 'running') {
gotContext = true;
} else {
// If AudioContext is suspended, enable it with user interaction
document.getElementById('start').textContent = 'Detect Audio Levels';
document.getElementById('start').disabled = false;
}
}

// Use WebRTC API to check for microphone access
navigator.mediaDevices.enumerateDevices().then(async devices => {
// Check if any of the media devices are microphones
if (devices.some(device => device.kind === 'audioinput'
&& device.label)) {
// The user has granted microphone access
if ( {{ force_click|tojson }} ) { // |tojson lowercases booleans
document.getElementById('start').textContent = 'Detect Audio Levels';
document.getElementById('start').disabled = false;
}
});

// Add event listener for the 'start' button
document.getElementById('start').addEventListener('click', async () => {
if (!recorder) {
// If the recorder is not initialized, initialize it
document.getElementById('start').disabled = true;
document.getElementById('start').textContent = 'Initializing...';
context.resume();
} else if (context.state === 'running') {
await initRecorder();
} else if (!isRecording) {
// If not currently recording, start recording
startRecording();
} else {
// If currently recording, stop recording
stopRecording();
}
});

// Event listener for the 'startOver' button
document.getElementById('startOver').onclick = function() {
if (recorder && isRecording) {
// Send a message to reset the recording if already in progress
recorder.port.postMessage({ message: 'UPDATE_RECORDING_STATE',
setRecording: true }); // Reset samples to zero
clearTimeout(vad_timeout);
} else {
// Microphone access is not granted or no mic is available
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
// Special help for Apple Safari users on microphone permissions
document.getElementById('safari-help').style.display = 'block';
}
document.getElementById('start').textContent = 'Allow Microphone Use';
document.getElementById('start').disabled = false;
}

// Event listener for the noise suppression checkbox
document.getElementById('suppressNoise').addEventListener('change',
setNoiseSuppression);
});

// Add event listener for the 'start' button
document.getElementById('start').addEventListener('click', async () => {
if (!recorder) {
// If the recorder is not initialized, initialize it
document.getElementById('start').disabled = true;
document.getElementById('start').textContent = 'Initializing...';
context.resume();
await initRecorder();
} else if (!isRecording) {
// If not currently recording, start recording
startRecording();
} else {
// If currently recording, stop recording
stopRecording();
}
});

// Event listener for the 'startOver' button
document.getElementById('startOver').onclick = function() {
if (recorder && isRecording) {
// Send a message to reset the recording if already in progress
recorder.port.postMessage({ message: 'UPDATE_RECORDING_STATE',
setRecording: true }); // Reset samples to zero
clearTimeout(vad_timeout);
}
}

// }
// Event listener for the noise suppression checkbox
document.getElementById('suppressNoise').addEventListener('change',
setNoiseSuppression);

// Function to initialize the audio recorder
async function initRecorder() {
Expand Down Expand Up @@ -324,7 +320,7 @@ <h3>Apple Safari users: <a
// Send the chunk if the buffer reaches the size limit
if (chunkBuffer.length >= chunkBufferSizeLimit) {
mySocket.emit('audio_chunk', new Uint8Array(chunkBuffer),
(status, error) => {
(status, error) => {
if (status === 'fail') {
socket = false; // Disable the socket on failure
document.getElementById('socketSpan').innerHTML =
Expand Down Expand Up @@ -354,7 +350,7 @@ <h3>Apple Safari users: <a
if (socket) {
if (chunkBuffer.length) {
mySocket.emit('audio_chunk', new Uint8Array(chunkBuffer),
(status, error) => {
(status, error) => {
if (status === 'fail') {
socket = false; // Disable the socket on failure
log('audio_chunk socket error: ' + error)
Expand Down

0 comments on commit 38cc33d

Please sign in to comment.