-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Implemented STOP button behavior to toggle audio playback #353
base: main
Are you sure you want to change the base?
Changes from 1 commit
bf3ea58
894b234
2df8734
00ddbef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…nfo.json, index.njk, and main.js files for various audio worklet demos. Also, updated styles.css.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
{"version":"3.1.0","revision":"7201485","lastUpdated":"2023-11-02","copyrightYear":2023} | ||
{"version":"3.1.0","revision":"0643084","lastUpdated":"2024-03-07","copyrightYear":2024} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,30 +3,44 @@ | |
// found in the LICENSE file. | ||
|
||
const audioContext = new AudioContext(); | ||
let oscillatorProcessor; | ||
|
||
const startAudio = async (context, options) => { | ||
await context.audioWorklet.addModule('oscillator-processor.js'); | ||
const oscillatorProcessor = | ||
new AudioWorkletNode(context, 'oscillator-processor', { | ||
processorOptions: { | ||
waveformType: options.waveformType, | ||
frequency: options.frequency, | ||
}}); | ||
oscillatorProcessor = | ||
new AudioWorkletNode(context, 'oscillator-processor', { | ||
processorOptions: { | ||
waveformType: options.waveformType, | ||
frequency: options.frequency, | ||
} | ||
}); | ||
oscillatorProcessor.connect(context.destination); | ||
}; | ||
|
||
// A simplem onLoad handler. It also handles user gesture to unlock the audio | ||
const stopAudio = () => { | ||
if (oscillatorProcessor) { | ||
oscillatorProcessor.disconnect(); | ||
oscillatorProcessor = null; | ||
} | ||
}; | ||
|
||
// A simple onLoad handler. It also handles user gesture to unlock the audio | ||
// playback. | ||
window.addEventListener('load', async () => { | ||
const buttonEl = document.getElementById('button-start'); | ||
buttonEl.disabled = false; | ||
|
||
buttonEl.addEventListener('click', async () => { | ||
const waveformType = | ||
if (!oscillatorProcessor) { // If audio is not playing, start audio | ||
const waveformType = | ||
document.querySelector('#demo-select-waveform-type').value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: 4-space indent |
||
const frequency = document.querySelector('#demo-input-frequency').value; | ||
await startAudio(audioContext, {waveformType, frequency}); | ||
audioContext.resume(); | ||
buttonEl.disabled = true; | ||
buttonEl.textContent = 'Playing...'; | ||
const frequency = document.querySelector('#demo-input-frequency').value; | ||
await startAudio(audioContext, { waveformType, frequency }); | ||
hoch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
audioContext.resume(); | ||
buttonEl.textContent = 'STOP'; | ||
} else { // If audio is playing, stop audio | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Ditto on comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
stopAudio(); | ||
buttonEl.textContent = 'START'; | ||
} | ||
}, false); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,24 +3,54 @@ | |
// found in the LICENSE file. | ||
|
||
const audioContext = new AudioContext(); | ||
let oscillator; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make it clear, how about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the suggestion. I've updated the variable name to oscillatorNode for better clarity. Please let me know if you have any further suggestions. |
||
let isPlaying = false; | ||
|
||
const startAudio = async (context) => { | ||
await context.audioWorklet.addModule('bypass-processor.js'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto here; calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done the requested changes as per the feedback. |
||
const oscillator = new OscillatorNode(context); | ||
oscillator = new OscillatorNode(context); | ||
const bypasser = new AudioWorkletNode(context, 'bypass-processor'); | ||
oscillator.connect(bypasser).connect(context.destination); | ||
oscillator.start(); | ||
}; | ||
|
||
const stopAudio = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should leave the rest and simply suspend the context. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion. I've modified the stopAudio() function to suspend the audio context instead of stopping and disconnecting the oscillator node. Please review the changes and let me know if they meet the requirements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also has the same problem with |
||
if (oscillator) { | ||
oscillator.stop(); | ||
oscillator.disconnect(); | ||
} | ||
}; | ||
|
||
// A simplem onLoad handler. It also handles user gesture to unlock the audio | ||
// playback. | ||
window.addEventListener('load', async () => { | ||
const buttonEl = document.getElementById('button-start'); | ||
buttonEl.disabled = false; | ||
|
||
buttonEl.addEventListener('click', async () => { | ||
await startAudio(audioContext); | ||
audioContext.resume(); | ||
buttonEl.disabled = true; | ||
buttonEl.textContent = 'Playing...'; | ||
}, false); | ||
if (!isPlaying) { | ||
await startAudio(audioContext); | ||
audioContext.resume(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I modified the button click event handler to check if the AudioContext is suspended and call resume() accordingly. |
||
isPlaying = true; | ||
buttonEl.textContent = 'Playing...'; | ||
buttonEl.classList.remove('start-button'); | ||
} else { | ||
stopAudio(); | ||
isPlaying = false; | ||
buttonEl.textContent = 'START'; | ||
buttonEl.classList.add('start-button'); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly to the previous comment, the logic between L24~L40 is slightly confusing. I think it's possible to split the check of |
||
}); | ||
|
||
buttonEl.addEventListener('mouseenter', () => { | ||
if (isPlaying) { | ||
buttonEl.textContent = 'STOP'; | ||
} | ||
}); | ||
|
||
buttonEl.addEventListener('mouseleave', () => { | ||
if (isPlaying) { | ||
buttonEl.textContent = 'Playing...'; | ||
} | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,14 @@ | |
// found in the LICENSE file. | ||
|
||
const audioContext = new AudioContext(); | ||
let isPlaying = false; | ||
let noiseGenerator; | ||
|
||
const startAudio = async (context) => { | ||
await context.audioWorklet.addModule('noise-generator.js'); | ||
const modulator = new OscillatorNode(context); | ||
const modGain = new GainNode(context); | ||
const noiseGenerator = new AudioWorkletNode(context, 'noise-generator'); | ||
noiseGenerator = new AudioWorkletNode(context, 'noise-generator'); | ||
noiseGenerator.connect(context.destination); | ||
|
||
// Connect the oscillator to 'amplitude' AudioParam. | ||
|
@@ -20,15 +22,28 @@ const startAudio = async (context) => { | |
modulator.start(); | ||
}; | ||
|
||
// A simplem onLoad handler. It also handles user gesture to unlock the audio | ||
const stopAudio = () => { | ||
if (noiseGenerator) { | ||
noiseGenerator.disconnect(); | ||
isPlaying = false; | ||
} | ||
}; | ||
|
||
// A simple onLoad handler. It also handles user gesture to unlock the audio | ||
// playback. | ||
window.addEventListener('load', async () => { | ||
const buttonEl = document.getElementById('button-start'); | ||
buttonEl.disabled = false; | ||
|
||
buttonEl.addEventListener('click', async () => { | ||
await startAudio(audioContext); | ||
audioContext.resume(); | ||
buttonEl.disabled = true; | ||
buttonEl.textContent = 'Playing...'; | ||
}, false); | ||
if (!isPlaying) { // If not playing, start audio | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Google's JS style doesn't use this type of inline comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved the comments to next line. |
||
await startAudio(audioContext); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will call L10 (addModule) multiple times depending on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have updated the code to ensure that the module is added only once by using a flag (moduleAdded) to track whether the module has already been loaded. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this example is pretty much the same with the other oscillator example. Why do we need a different flag ( |
||
audioContext.resume(); | ||
isPlaying = true; | ||
buttonEl.textContent = 'Playing...'; | ||
} else { // If playing, stop audio | ||
stopAudio(); | ||
buttonEl.textContent = 'START'; | ||
} | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,12 @@ | |
// found in the LICENSE file. | ||
|
||
const audioContext = new AudioContext(); | ||
let oscillator; | ||
let isPlaying = false; | ||
|
||
const startAudio = async (context) => { | ||
await context.audioWorklet.addModule('one-pole-processor.js'); | ||
const oscillator = new OscillatorNode(context); | ||
oscillator = new OscillatorNode(context); | ||
const filter = new AudioWorkletNode(context, 'one-pole-processor'); | ||
const frequencyParam = filter.parameters.get('frequency'); | ||
|
||
|
@@ -19,15 +21,45 @@ const startAudio = async (context) => { | |
.exponentialRampToValueAtTime(0.01, 8.0); | ||
}; | ||
|
||
const stopAudio = () => { | ||
if (oscillator) { | ||
oscillator.stop(); | ||
oscillator.disconnect(); | ||
} | ||
}; | ||
|
||
|
||
// A simplem onLoad handler. It also handles user gesture to unlock the audio | ||
// playback. | ||
window.addEventListener('load', async () => { | ||
const buttonEl = document.getElementById('button-start'); | ||
buttonEl.disabled = false; | ||
|
||
buttonEl.addEventListener('click', async () => { | ||
await startAudio(audioContext); | ||
audioContext.resume(); | ||
buttonEl.disabled = true; | ||
buttonEl.textContent = 'Playing...'; | ||
}, false); | ||
if (!isPlaying) { | ||
await startAudio(audioContext); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added the flag check here same as above. |
||
audioContext.resume(); | ||
isPlaying = true; | ||
buttonEl.textContent = 'Playing...'; | ||
buttonEl.classList.remove('start-button'); | ||
} else { | ||
stopAudio(); | ||
isPlaying = false; | ||
buttonEl.textContent = 'START'; | ||
buttonEl.classList.add('start-button'); | ||
} | ||
}); | ||
|
||
buttonEl.addEventListener('mouseenter', () => { | ||
if (isPlaying) { | ||
buttonEl.textContent = 'STOP'; | ||
} | ||
}); | ||
|
||
buttonEl.addEventListener('mouseleave', () => { | ||
if (isPlaying) { | ||
buttonEl.textContent = 'Playing...'; | ||
} | ||
}); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,29 +3,47 @@ | |
// found in the LICENSE file. | ||
|
||
const audioContext = new AudioContext(); | ||
let mediaStream; | ||
let volumeMeterNode; | ||
|
||
const startAudio = async (context, meterElement) => { | ||
await context.audioWorklet.addModule('volume-meter-processor.js'); | ||
const mediaStream = await navigator.mediaDevices.getUserMedia({audio: true}); | ||
mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); | ||
const micNode = context.createMediaStreamSource(mediaStream); | ||
const volumeMeterNode = new AudioWorkletNode(context, 'volume-meter'); | ||
volumeMeterNode.port.onmessage = ({data}) => { | ||
volumeMeterNode = new AudioWorkletNode(context, 'volume-meter'); | ||
volumeMeterNode.port.onmessage = ({ data }) => { | ||
meterElement.value = data * 500; | ||
}; | ||
micNode.connect(volumeMeterNode).connect(context.destination); | ||
}; | ||
|
||
// A simplem onLoad handler. It also handles user gesture to unlock the audio | ||
const stopAudio = () => { | ||
if (mediaStream) { | ||
mediaStream.getTracks().forEach(track => track.stop()); | ||
mediaStream = null; | ||
} | ||
if (volumeMeterNode) { | ||
volumeMeterNode.disconnect(); | ||
volumeMeterNode = null; | ||
} | ||
}; | ||
|
||
// A simple onLoad handler. It also handles user gesture to unlock the audio | ||
// playback. | ||
window.addEventListener('load', async () => { | ||
const buttonEl = document.getElementById('button-start'); | ||
const meterEl = document.getElementById('volume-meter'); | ||
buttonEl.disabled = false; | ||
meterEl.disabled = false; | ||
|
||
buttonEl.addEventListener('click', async () => { | ||
await startAudio(audioContext, meterEl); | ||
audioContext.resume(); | ||
buttonEl.disabled = true; | ||
buttonEl.textContent = 'Playing...'; | ||
if (!mediaStream) { // If audio is not playing, start audio | ||
await startAudio(audioContext, meterEl); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made the requested changes here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added the flag check here too. |
||
audioContext.resume(); | ||
buttonEl.textContent = 'STOP'; | ||
} else { // If audio is playing, stop audio | ||
stopAudio(); | ||
buttonEl.textContent = 'START'; | ||
} | ||
}, false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Let's move the comment below the
if()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.