Getting audio players to play their respective streams out of their element

It is possible to remove all of the other sets of button code, but doing so must be performed in a more careful manner.

Click on the play button twice, it disappears:

That’s happening because “active” is being used in the CSS styles, when “activated” must be used instead.

Got it:

Both versions are done now.

What are the two versions?

Buttons C & E are different.

You have a lot of duplication in your code now.

I want to help you to remove some of that annoying duplication, which will also make it easier for you to manage your code.

The first thing you can to do help remove duplication is to rename hidea/hideb/hidec/… to just “hide” in your HTML, CSS, and JavaScript.

That will leave you with several duplicate lines, such as:

        el.classList.remove("hide");
        el.classList.remove("hide");
        el.classList.remove("hide");
        el.classList.remove("hide");
        el.classList.remove("hide");
        el.classList.remove("hide");

You can now remove all of those duplicates, leaving you with just:

        el.classList.remove("hide");

After other adjustments have been made, to the initial/play/pause/speaker class names, and adding a “playButton” class to each button (for the purpose of scripting), we are now ready to combine two scripts together.

The getButtonContainer() and pauseAllButtons() functions can be made the same across all of the code by using “.pauseButton” instead of pauseButtona, pauseButtonb, etc…

The upTo() statements can all use the generic “.playButton” classname too.

Other things that the diff between code A and code B reveals, is that code B uses playButton when it should use togglePlayButton.

We can now remove code A, and have code B handle things for both buttons. Code A has no initial overlay, so we check if that initial element exists before using that.

    playButtons.forEach(function (button) {
        if (button.querySelector(".initial")) {
            button.addEventListener("click", initialOverlayClickHandler);
        } else {
            button.addEventListener("click", playButtonClickHandler);
        }
    });

And we now have one set of code that controls both the A and B buttons. https://jsfiddle.net/zfs5qqq9/230/

We can now extend that further to the button C.

Comparing the A+B code with C, there’s a lot of formatting differences between them. Running all the code through JSBeautifier makes it much easier to see in DiffChecker that there’s very little that’s different about both sets of code.

Code C is missing code that deals with the initial overlay, which is fine, and when “active” and “activated” from code A+B are added to the C buttons, nothing will happen because there are no class declarations for the C button that will be affected by the “active” and “activated” class names.

We do need to add mouseover and mouseout events to the A+B code, which results in some duplication between the initialOverlayClickHandler and the other code further below that adds events, so we’ll create a new function to add the playbutton handlers.

    function addPlayButtonHandlers(button) {
        button.addEventListener("click", playButtonClickHandler);
        button.addEventListener("mouseover", playButtonMouseoverHandler);
        button.addEventListener("mouseout", playButtonMouseoutHandler);
    }
    function initialOverlayClickHandler(evt) {
        var button = getButtonContainer(evt.target);
        hideInitialOverlay(button);
        button.removeEventListener("click", initialOverlayClickHandler);
        // button.addEventListener("click", playButtonClickHandler);
        // button.addEventListener("mouseover", playButtonMouseoverHandler);
        // button.addEventListener("mouseout", playButtonMouseoutHandler);
        addPlayButtonHandlers(button);   
        // ...
    }
    // ...
        if (button.querySelector(".initial")) {
            button.addEventListener("click", initialOverlayClickHandler);
        } else {
            // button.addEventListener("click", playButtonClickHandler);
            addPlayButtonHandlers(button);
        }

Because of Button A, we don’t want those mouseover/mouseout event handlers being assigned if there is no speaker icon in the button, so we can use an if statement to guard against that.

    function addPlayButtonHandlers(button) {
        button.addEventListener("click", playButtonClickHandler);
        if (button.querySelector(".speaker")) {
            button.addEventListener("mouseover", playButtonMouseoverHandler);
            button.addEventListener("mouseout", playButtonMouseoutHandler);
        }
    }

And, the A+B code works properly once more with the A and B buttons.

We can now have that first set of code also support the C buttons.

    var playButtons = document.querySelectorAll(".playButtona, .playButtonb, .playButtonc");

We now have one set of code that controls buttons A+B+C https://jsfiddle.net/zfs5qqq9/231/
When that code controls all of the buttons, we can replace those separate classnames with just the .playButton class name instead. That can’t occur though until the first set of code supports buttons, D, E, and F too.

Is this true?

Be aware that the class name .active can easily be confused with pseudo :active

.playButtona.active

The classname might be confused by a person, that reads “.active” as if it might be “:active”

If that really becomes a problem, you can rename “active” to something else such as “playing”

2 Likes

Using DiffChecker to compare the A+B+C code to the D code, there’s nothing extra in the D code that needs to be supported, so we can tell the A+B+C to support D too, and after removing the D code, everything all works.

    var playButtons = document.querySelectorAll(".playButtona, .playButtonb, .playButtonc, .playButtond");

Code E has some significant differences though.
Code A+B+C+D (we can call the Main code now), uses getButtonContainer() and code E uses the upTo() function. We can combine those together nicely by having getButtonContainer() use the upTo() function.

    function upTo(el, selector) {
        while (el.matches(selector) === false) {
            el = el.parentNode;
        }
        return el;
    }

    function getButtonContainer(el) {
        return upTo(el, ".playButton");
        // while (el.classList.contains("playButton") === false) {
        //     el = el.parentNode;
        // }
        // return el;
    }

The links are the only other thing to be dealt with. Currently they being controlled by a separate “inactive” class, but they can just as easily be controlled by the “activated” classname instead.

  /* .linkse.inactive a { */
  .linkse a {
    display: none;
  }
  .linkse.activated a {
    display: initial;
  }
    function hideInitialOverlay(button) {
        var link = upTo(button, ".linkse");
        // link.classList.remove("inactive");
        hide(button.querySelector(".initial"));
        button.classList.add("activated");
        link.classList.add("activated");
    }
    // document.querySelector(".linkse").classList.add("inactive");

We can also rename linkse to just links, and things there are tidied up too.

The main code can now be updated, to add “activated” to all link elements.

    function getLinks(button) {
        var links = upTo(button, ".links");
        if (links && links.classList.contains("links")) {
            return links;
        }
    }
    // ...
    function hideInitialOverlay(button) {
        var links = getLinks(button);
        hide(button.querySelector(".initial"));
        if (links) {
            links.classList.add("activated");
        }
        showPlayButton(button);
    }

And because links can’t be found from all buttons, the upTo function needs to be updated so that it can deal with not finding what it’s been asked for.

    function upTo(el, selector) {
        while (el.nodeName !== "HTML" && el.matches(selector) === false) {
            el = el.parentNode;
        }
        if (el.nodeName !== "HTML") {
            return el;
        }
    }

Button E isn’t supposed to play when you first click on it either, so we can add an `initialPause" class to that button, and look for that from the initialOverlayClickHandler() function.

<div class="playButton playButtone initialPause">
    function initialOverlayClickHandler(evt) {
        // ...
        addPlayButtonHandlers(button);
        playButtonClickHandler(evt);
        if (button.classList.contains("initialPause")) {
            togglePlayButton(button);
        }
    }

That gives us main code that works with the Buttons A through to E https://jsfiddle.net/zfs5qqq9/234, and it’s just a matter of looking at the last button now.

The changes for button E have resulted in a minor issue, where playing some other button then clicking on button E’s initial overlay, causes that other button to stop playing.

To fix that, button E needs to not play then pause when we first click on it.

    function initialOverlayClickHandler(evt) {
        // ...
        addPlayButtonHandlers(button);
        if (button.classList.contains("initialPause")) {
            return;
        }
        playButtonClickHandler(evt);
        // if (button.classList.contains("initialPause")) {
        //     togglePlayButton(button);
        // }
    }

And, the play button doesn’t show, because the CSS needs to be fixed for the E button.

  .playButtone.activated {

Clicking to remove the initial image doesn’t trigger activated on the play button, so we need to check if the links are activated instead.

  .links.activated .playButtone {

And everything now works properly with button E. https://jsfiddle.net/zfs5qqq9/235/

With the last button, when we update that main set of code to also handle the last button, the code is so capable now that no further changes are required.

    var playButtons = document.querySelectorAll(".playButtona, .playButtonb, .playButtonc, .playButtond, .playButtone, .playButtonf");

Because all of the buttons are now being supported, we don’t need to individually name them, and can just get all of the .playButton elements.

    var playButtons = document.querySelectorAll(".playButton");

And the JavaScript code for it all is:

(function iife() {
    "use strict";

    function show(el) {
        el.classList.remove("hide");
    }

    function hide(el) {
        el.classList.add("hide");
    }

    function upTo(el, selector) {
        while (el.nodeName !== "HTML" && el.matches(selector) === false) {
            el = el.parentNode;
        }
        if (el.nodeName !== "HTML") {
            return el;
        }
    }

    function getButtonContainer(el) {
        return upTo(el, ".playButton");
    }

    function hideAllButtons(button) {
        button.querySelectorAll(".initial, .play, .pause, .speaker").forEach(hide);
    }

    function getLinks(button) {
        var links = upTo(button, ".links");
        if (links && links.classList.contains("links")) {
            return links;
        }
    }

    function getPlay(button) {
        return button.querySelector(".play");
    }

    function getPause(button) {
        return button.querySelector(".pause");
    }

    function getSpeaker(button) {
        return button.querySelector(".speaker");
    }

    function showPlayButton(button) {
        var play = getPlay(button);
        hideAllButtons(button);
        show(play);
        button.classList.remove("active");
    }

    function hideInitialOverlay(button) {
        var links = getLinks(button);
        hide(button.querySelector(".initial"));
        if (links) {
            links.classList.add("activated");
        }
        showPlayButton(button);
    }

    function isPlaying(button) {
        var play = getPlay(button);
        return play.classList.contains("hide");
    }

    function pauseAllButtons() {
        var buttons = document.querySelectorAll(".playButton");
        buttons.forEach(function hidePause(button) {
            if (isPlaying(button)) {
                showPlayButton(button);
            }
        });
    }

    function showPauseButton(button) {
        var pause = getPause(button);
        pauseAllButtons();
        hideAllButtons(button);
        show(pause);
        button.classList.add("active");
        button.classList.add("activated");
    }

    function showSpeakerButton(button) {
        var speaker = getSpeaker(button);
        hideAllButtons(button);
        show(speaker);
    }

    function getAudio() {
        return document.querySelector("audio");
    }

    function playAudio(player, button) {
        var src = button.getAttribute("data-audio");
        player.volume = 1.0;
        player.setAttribute("src", src);
        player.play();
    }

    function pauseAudio(player) {
        player.pause();
    }

    function togglePlayButton(button) {
        var player = getAudio();
        if (isPlaying(button)) {
            showPlayButton(button);
            pauseAudio(player);
        } else {
            showPauseButton(button);
            playAudio(player, button);
        }
    }

    function showPause(button) {
        if (isPlaying(button)) {
            showPauseButton(button);
        }
    }

    function showSpeaker(button) {
        if (isPlaying(button)) {
            showSpeakerButton(button);
        }
    }

    function playButtonClickHandler(evt) {
        var button = getButtonContainer(evt.target);
        togglePlayButton(button);
    }

    function playButtonMouseoverHandler(evt) {
        var button = getButtonContainer(evt.target);
        showPause(button);
    }

    function playButtonMouseoutHandler(evt) {
        var button = getButtonContainer(evt.target);
        showSpeaker(button);
    }

    function addPlayButtonHandlers(button) {
        button.addEventListener("click", playButtonClickHandler);
        if (button.querySelector(".speaker")) {
            button.addEventListener("mouseover", playButtonMouseoverHandler);
            button.addEventListener("mouseout", playButtonMouseoutHandler);
        }
    }
    function initialOverlayClickHandler(evt) {
        var button = getButtonContainer(evt.target);
        hideInitialOverlay(button);
        button.removeEventListener("click", initialOverlayClickHandler);
        addPlayButtonHandlers(button);
        if (button.classList.contains("initialPause")) {
            return;
        }
        playButtonClickHandler(evt);
    }
    var playButtons = document.querySelectorAll(".playButton");
    playButtons.forEach(function addHandler(button) {
        if (button.querySelector(".initial")) {
            button.addEventListener("click", initialOverlayClickHandler);
        } else {
            addPlayButtonHandlers(button);
        }
    });
}());

That’s only 171 lines of code, and less complexity to deal with, compared with the 692 lines of code that you were having to deal with earlier.

The full updated code for handling all of the buttons is at https://jsfiddle.net/zfs5qqq9/236/

I’m having an issue with Player B

It’s supposed to stay on the blue image after you click off it.

Instructions:
Click on Player B, then click on another player, Player B should stay on the blue image and not change.

I’m using image sprites with the code, do you understand what the issue is, or maybe I should ask someone in the html spot.

It was set to active when it should’ve been activated.

Fixed.

This is not good, right?

.playButtonb.activated
.playButtone.activated

I should change one of the activated’s to a different name, right?

Don’t change them to different names. CSS gets more powerful when the same name is used to easily reduce duplication.

There was interference last time, and so, that’s why we changed one from active to to activated.

This may not be what I want:

How would I add a
TimeRanges.start()

to the code?

How would I delay the start of the audio after I click play?

How would I add a 1, 2, or 3 second delay until the audio starts playing?