-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
160 additions
and
177 deletions.
There are no files selected for viewing
270 changes: 122 additions & 148 deletions
270
web/modules/custom/giv_din_stemme/js/giv_din_stemme.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,158 +1,132 @@ | ||
// Set up basic variables for app | ||
const record = document.querySelector(".record"); | ||
const stop = document.querySelector(".stop"); | ||
const canvas = document.querySelector(".visualizer"); | ||
const mainSection = document.querySelector(".main-controls"); | ||
const soundClips = document.querySelector(".sound-clips"); | ||
const submitButton = document.querySelector("#read_submit_button"); | ||
const fileElement = document.querySelector("#audio_input"); | ||
const durationElement = document.querySelector("#recording_duration"); | ||
|
||
// Disable stop button while not recording | ||
stop.disabled = true; | ||
submitButton.disabled = true; | ||
|
||
// Visualiser setup - create web audio api context and canvas | ||
let audioCtx; | ||
const canvasCtx = canvas.getContext("2d"); | ||
|
||
// Main block for doing the audio recording | ||
if (navigator.mediaDevices.getUserMedia) { | ||
const constraints = { audio: true }; | ||
let chunks = []; | ||
let startTime; | ||
let endTime; | ||
|
||
let onSuccess = function (stream) { | ||
const mediaRecorder = new MediaRecorder(stream); | ||
|
||
visualize(stream); | ||
|
||
record.onclick = function () { | ||
startTime = Date.now(); | ||
mediaRecorder.start(); | ||
record.style.background = "red"; | ||
|
||
stop.disabled = false; | ||
record.disabled = true; | ||
}; | ||
|
||
stop.onclick = function () { | ||
mediaRecorder.stop(); | ||
record.style.background = ""; | ||
record.style.color = ""; | ||
|
||
stop.disabled = true; | ||
record.disabled = false; | ||
endTime = Date.now(); | ||
(async () => { | ||
|
||
let volumeCallback = null; | ||
let volumeInterval = null; | ||
const volumeVisualizer = document.getElementById('btn-microphone-toggle'); | ||
const toggleButton = document.getElementById('btn-microphone-toggle'); | ||
const mainSection = document.querySelector(".main-controls"); | ||
const soundClips = document.querySelector(".sound-clips"); | ||
const submitButton = document.querySelector("#read_submit_button"); | ||
const fileElement = document.querySelector("#audio_input"); | ||
const durationElement = document.querySelector("#recording_duration"); | ||
|
||
// Initialize | ||
try { | ||
const audioStream = await navigator.mediaDevices.getUserMedia({ | ||
audio: { | ||
echoCancellation: true | ||
} | ||
}); | ||
const audioContext = new AudioContext(); | ||
const audioSource = audioContext.createMediaStreamSource(audioStream); | ||
const analyser = audioContext.createAnalyser(); | ||
analyser.fftSize = 512; | ||
analyser.minDecibels = -127; | ||
analyser.maxDecibels = 0; | ||
analyser.smoothingTimeConstant = 0.4; | ||
audioSource.connect(analyser); | ||
const volumes = new Uint8Array(analyser.frequencyBinCount); | ||
|
||
volumeCallback = () => { | ||
analyser.getByteFrequencyData(volumes); | ||
let volumeSum = 0; | ||
for(const volume of volumes) | ||
volumeSum += volume; | ||
const averageVolume = volumeSum / volumes.length; | ||
// Value range: 127 = analyser.maxDecibels - analyser.minDecibels; | ||
volumeVisualizer.style.setProperty('--volume', Math.ceil((averageVolume * 100 / 127)) + 'px'); | ||
}; | ||
} catch(e) { | ||
console.error('Failed to initialize volume visualizer.', e); | ||
} | ||
|
||
mediaRecorder.onstop = function (e) { | ||
const clipContainer = document.createElement("article"); | ||
const clipLabel = document.createElement("p"); | ||
const audio = document.createElement("audio"); | ||
const deleteButton = document.createElement("button"); | ||
|
||
clipContainer.classList.add("clip"); | ||
audio.setAttribute("controls", ""); | ||
deleteButton.textContent = "Delete"; | ||
|
||
clipContainer.appendChild(audio); | ||
clipContainer.appendChild(clipLabel); | ||
clipContainer.appendChild(deleteButton); | ||
soundClips.appendChild(clipContainer); | ||
|
||
const blob = new Blob(chunks, { type: "audio/mp3" }); | ||
chunks = []; | ||
|
||
audio.src = window.URL.createObjectURL(blob); | ||
|
||
// Convert blob to file and attach to file element. | ||
let file = new File([blob], "audio_recording.mp3", {type:blob.type, lastModified:new Date().getTime()}); | ||
let container = new DataTransfer(); | ||
container.items.add(file); | ||
fileElement.files = container.files; | ||
|
||
// Set duration time | ||
durationElement.value = Math.round((endTime - startTime) / 1000); | ||
|
||
deleteButton.onclick = (e) => { | ||
let evtTgt = e.target; | ||
evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode); | ||
record.disabled = false; | ||
submitButton.disabled = true; | ||
fileElement.files = new DataTransfer().files; | ||
startTime = null; | ||
endTime = null; | ||
// Disable next button while not recording | ||
submitButton.disabled = true; | ||
|
||
// Main block for doing the audio recording | ||
if (navigator.mediaDevices.getUserMedia) { | ||
const constraints = { audio: true }; | ||
let chunks = []; | ||
let startTime; | ||
let endTime; | ||
|
||
let onSuccess = function (stream) { | ||
const mediaRecorder = new MediaRecorder(stream); | ||
|
||
// visualize(stream); | ||
|
||
toggleButton.addEventListener('click', () => { | ||
if (toggleButton.classList.contains('active')) { | ||
if (volumeInterval !== null) { | ||
clearInterval(volumeInterval); | ||
volumeInterval = null; | ||
} | ||
mediaRecorder.stop(); | ||
endTime = Date.now(); | ||
toggleButton.classList.remove('active') | ||
} | ||
else { | ||
toggleButton.classList.add('active'); | ||
startTime = Date.now(); | ||
mediaRecorder.start(); | ||
if (volumeCallback !== null && volumeInterval === null) { | ||
volumeInterval = setInterval(volumeCallback, 100); | ||
} | ||
} | ||
}); | ||
|
||
mediaRecorder.onstop = function (e) { | ||
const clipContainer = document.createElement("article"); | ||
// TODO: Do we use this clipLabel? | ||
const clipLabel = document.createElement("p"); | ||
const audio = document.createElement("audio"); | ||
const deleteButton = document.createElement("button"); | ||
|
||
clipContainer.classList.add("clip", "flex"); | ||
deleteButton.classList.add("btn-primary"); | ||
audio.setAttribute("controls", ""); | ||
// TODO: This should be translateable | ||
deleteButton.textContent = "Delete recording"; | ||
|
||
clipContainer.appendChild(audio); | ||
clipContainer.appendChild(clipLabel); | ||
clipContainer.appendChild(deleteButton); | ||
soundClips.appendChild(clipContainer); | ||
|
||
const blob = new Blob(chunks, { type: "audio/mp3" }); | ||
chunks = []; | ||
|
||
audio.src = window.URL.createObjectURL(blob); | ||
|
||
// Convert blob to file and attach to file element. | ||
let file = new File([blob], "audio_recording.mp3", {type:blob.type, lastModified:new Date().getTime()}); | ||
let container = new DataTransfer(); | ||
container.items.add(file); | ||
fileElement.files = container.files; | ||
|
||
// Set duration time | ||
durationElement.value = Math.round((endTime - startTime) / 1000); | ||
|
||
deleteButton.onclick = (e) => { | ||
let evtTgt = e.target; | ||
evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode); | ||
submitButton.disabled = true; | ||
fileElement.files = new DataTransfer().files; | ||
startTime = null; | ||
endTime = null; | ||
}; | ||
|
||
submitButton.disabled = false; | ||
}; | ||
|
||
submitButton.disabled = false; | ||
record.disabled = true; | ||
}; | ||
|
||
mediaRecorder.ondataavailable = function (e) { | ||
chunks.push(e.data); | ||
mediaRecorder.ondataavailable = function (e) { | ||
chunks.push(e.data); | ||
}; | ||
}; | ||
}; | ||
|
||
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess); | ||
} | ||
|
||
function visualize(stream) { | ||
if (!audioCtx) { | ||
audioCtx = new AudioContext(); | ||
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess); | ||
} | ||
|
||
const source = audioCtx.createMediaStreamSource(stream); | ||
|
||
const analyser = audioCtx.createAnalyser(); | ||
analyser.fftSize = 2048; | ||
const bufferLength = analyser.frequencyBinCount; | ||
const dataArray = new Uint8Array(bufferLength); | ||
|
||
source.connect(analyser); | ||
|
||
draw(); | ||
|
||
function draw() { | ||
const WIDTH = canvas.width; | ||
const HEIGHT = canvas.height; | ||
|
||
requestAnimationFrame(draw); | ||
|
||
analyser.getByteTimeDomainData(dataArray); | ||
|
||
canvasCtx.fillStyle = "rgb(200, 200, 200)"; | ||
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); | ||
|
||
canvasCtx.lineWidth = 2; | ||
canvasCtx.strokeStyle = "rgb(0, 0, 0)"; | ||
|
||
canvasCtx.beginPath(); | ||
|
||
let sliceWidth = (WIDTH * 1.0) / bufferLength; | ||
let x = 0; | ||
|
||
for (let i = 0; i < bufferLength; i++) { | ||
let v = dataArray[i] / 128.0; | ||
let y = (v * HEIGHT) / 2; | ||
|
||
if (i === 0) { | ||
canvasCtx.moveTo(x, y); | ||
} else { | ||
canvasCtx.lineTo(x, y); | ||
} | ||
|
||
x += sliceWidth; | ||
} | ||
|
||
canvasCtx.lineTo(canvas.width, canvas.height / 2); | ||
canvasCtx.stroke(); | ||
} | ||
} | ||
|
||
window.onresize = function () { | ||
canvas.width = mainSection.offsetWidth; | ||
}; | ||
})(); | ||
|
||
window.onresize(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
web/modules/custom/giv_din_stemme/templates/components/button-microphone-toggle.html.twig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<div id="btn-microphone-toggle" class="btn-microphone-toggle rounded-full flex place-content-center w-20 h-20 cursor-pointer text-center bg-brand visualizer-shadow"> | ||
<i class="text-white text-3xl fa-solid fa-microphone-slash place-self-center"></i> | ||
<i class="text-white text-3xl fa-solid fa-microphone place-self-center"></i> | ||
</div> |
33 changes: 16 additions & 17 deletions
33
web/modules/custom/giv_din_stemme/templates/read-page.html.twig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters