Skip to content
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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

tarunsinghofficial
Copy link

@tarunsinghofficial tarunsinghofficial commented Mar 8, 2024

The web audio samples lack a stop button functionality, making it difficult for users to pause or stop audio playback once it has started. This limitation hinders the user experience and can be frustrating for individuals testing the demos.

Fix: This PR addresses the issue by implementing a stop button feature in the web audio samples.

Changes Made

  • Updated build_info.json to reflect changes in the project.
  • Modified index.njk and main.js files for several audio worklet demos:
    • audio-worklet-node-options: Updated options handling for audio worklet nodes.
    • hello-audio-worklet: Improved initialization and error handling.
    • noise-generator: Enhanced noise generation functionality.
    • one-pole-filter: Refinements to the one-pole filter algorithm.
    • volume-meter: Improved volume meter visualization and accuracy.
  • Updated styles.css for better styling and consistency across demos.

Purpose

The purpose of this PR is to ensure that the web audio samples provide a smooth and engaging experience for users. By addressing reported bugs and implementing enhancements, we aim to make the demos more useful and accessible to developers interested in web audio technologies.

Screenshots

bandicam2024-03-0822-44-52-351-ezgif com-crop

bandicam2024-03-0822-45-48-001-ezgif com-crop
4-4f8b-4650-bbdf-f652b23fcba0)

…nfo.json, index.njk, and main.js files for various audio worklet demos. Also, updated styles.css.
Copy link

google-cla bot commented Mar 8, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@hoch hoch self-requested a review March 11, 2024 17:24
Copy link
Member

@hoch hoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We appreciate your excellent contribution! You scoped the work and created a meaningful PR in a good shape.

My suggestion is more of a directional change for the PR, but I believe it is necessary. Please take a look and let me know if you have more questions.

@@ -3,24 +3,54 @@
// found in the LICENSE file.

const audioContext = new AudioContext();
let oscillator;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it clear, how about oscillatorNode?

Copy link
Author

Choose a reason for hiding this comment

The 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.

buttonEl.addEventListener('click', async () => {
const waveformType =
if (!oscillatorProcessor) { // If audio is not playing, start audio
const waveformType =
document.querySelector('#demo-select-waveform-type').value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: 4-space indent

@@ -3,24 +3,54 @@
// found in the LICENSE file.

const audioContext = new AudioContext();
let oscillator;
let isPlaying = false;

const startAudio = async (context) => {
await context.audioWorklet.addModule('bypass-processor.js');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here; calling addModule every call of startAudio is not recommended.

Copy link
Author

@tarunsinghofficial tarunsinghofficial Mar 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done the requested changes as per the feedback.

const bypasser = new AudioWorkletNode(context, 'bypass-processor');
oscillator.connect(bypasser).connect(context.destination);
oscillator.start();
};

const stopAudio = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should leave the rest and simply suspend the context.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also has the same problem with src/audio-worklet/basic/audio-worklet-node-options/main.js above. See my comments for the explanation.

@@ -3,21 +3,22 @@
// found in the LICENSE file.

const audioContext = new AudioContext();
let oscillator;
let oscillatorNode;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the name to oscillatorNode

@tarunsinghofficial
Copy link
Author

Any update on the PR?

Copy link
Member

@hoch hoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating the PR, but I think we have a design problem to solve:
addModule() can only be called once throughout the document lifecycle.

My recommendation is to leave the most of the code intact, and use suspend and resume for starting/stopping the AudioContext.

audioContext.resume();
buttonEl.disabled = true;
buttonEl.textContent = 'Playing...';
if (!oscillatorProcessor) { // If audio is not playing, start audio
Copy link
Member

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().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

await context.audioWorklet.addModule('oscillator-processor.js');
const oscillatorProcessor =
if (!oscillatorProcessor) {
await context.audioWorklet.addModule('oscillator-processor.js');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be called multiple times in any scenario. I think you'll get an error if you do this. Have you checked?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can't be called multiple times, so I have modified it. No, I didn't get any error though!

await startAudio(audioContext, { waveformType, frequency });
audioContext.resume();
buttonEl.textContent = 'STOP';
} else { // If audio is playing, stop audio
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Ditto on comments.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const bypasser = new AudioWorkletNode(context, 'bypass-processor');
oscillator.connect(bypasser).connect(context.destination);
oscillator.start();
};

const stopAudio = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also has the same problem with src/audio-worklet/basic/audio-worklet-node-options/main.js above. See my comments for the explanation.

buttonEl.disabled = true;
buttonEl.textContent = 'Playing...';
}, false);
if (!isPlaying) { // If not playing, start audio
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved the comments to next line.

@tarunsinghofficial
Copy link
Author

Hi @hoch! I have made the required changes, let me know if they commit is now goog and ready to be merged.
Thanks

@tarunsinghofficial
Copy link
Author

Hi @hoch

Any update on this PR? I know that I created this issue under the GSoC tag, but please don't close this PR as it is great improvement in the web audio samples for chrome labs.

Thanks

@hoch
Copy link
Member

hoch commented Apr 2, 2024

Of course. We'll continue to work on this. The team is currently a bit behind schedule, so reviewing this PR will take some time. Thanks for your patience!

@tarunsinghofficial
Copy link
Author

Of course. We'll continue to work on this. The team is currently a bit behind schedule, so reviewing this PR will take some time. Thanks for your patience!

Hi @hoch

Any update on this PR?

@tarunsinghofficial
Copy link
Author

Is this PR still ongoing?

@hoch
Copy link
Member

hoch commented Apr 18, 2024

Yes. We will review the PR soon. Thanks for your patience.

Copy link
Member

@hoch hoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarunsinghofficial Thanks for your patience, and we appreciate your contribution. The PR still seems to have outstanding issues, and it needs more review and local testing.

}, false);
if (!oscillatorNode) {
await startAudio(audioContext);
audioContext.resume();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once oscillatorNode is valid, resume() doesn't get called from anywhere. How does this recover from suspended status?

Copy link
Author

Choose a reason for hiding this comment

The 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.

}, false);
if (!isPlaying) {
// If not playing, start the audio.
await startAudio(audioContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will call L10 (addModule) multiple times depending on isPlaying. Have you tested locally? Loading the same script twice should throw an exception.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 (moduleAdded) only for this example?

buttonEl.textContent = 'Playing...';
}, false);
if (!isPlaying) {
await startAudio(audioContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the flag check here same as above.

buttonEl.disabled = true;
buttonEl.textContent = 'Playing...';
if (!mediaStream) { // If audio is not playing, start audio
await startAudio(audioContext, meterEl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the requested changes here:

#362

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the flag check here too.

@hoch hoch added the gsoc24 Google Summer of Code 2024 label May 15, 2024
…let, one-pole-filter, volume-meter, noise-generator
@tarunsinghofficial
Copy link
Author

Hi @hoch, I hope you're doing well! I have made all the necessary changes, and tested them locally, and as far as I know, it is ready to be merged to the main branch since all the requested changes have been completed.

Copy link
Member

@hoch hoch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the slow response, but I think this PR needs more work. Overall, many examples have a same structure, so perhaps it's better to break down the PR into smaller ones and focus on getting a small thing done first?

isPlaying = false;
buttonEl.textContent = 'START';
buttonEl.classList.add('start-button');
}
Copy link
Member

Choose a reason for hiding this comment

The 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 oscillatorNode out of this if/else block, so the block only handles the context state.

await audioContext.resume();
isPlaying = true;
buttonEl.textContent = 'Playing...';
buttonEl.classList.remove('start-button');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L26-L29 are exactly same with L31-L34. Perhaps this block can be simplified further?


const startAudio = async (context) => {
await context.audioWorklet.addModule('noise-generator.js');

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this empty line.

}, false);
if (!isPlaying) {
// If not playing, start the audio.
await startAudio(audioContext);
Copy link
Member

Choose a reason for hiding this comment

The 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 (moduleAdded) only for this example?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gsoc24 Google Summer of Code 2024
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants