Children's Musical - JavaScript Audio Player Embed

Frontend

AWS Services Used

Dev Tools

Overview

This is a simple audio player interface for a local children’s musical that I created to be embedded into a wordpress page. It is built using vanilla JavaScript and HTML5 audio. It is designed to be embedded in a website and is responsive. The player is hosted in S3 behind a cloudfrount distribution and is served over https.

Project Goals

Audio Player for Kids to Practice I wanted to create a simple audio player that would allow kids to practice the songs from the musical. I wanted it to be easy to use and responsive so that it would work on any device and the leaders wouldn’t have to worry about cds or other devices.

Learn JavaScript This gave me an opportunity to work on my JavaScript skills and learn more about how to build a simple audio player. I also wanted to learn more about how to use HTML5 audio and how to make it responsive.

Deployment

S3 and Cloudfront Utilized S3 and Cloudfront to host the player and serve it over https. This allowed me to easily embed the player in a wordpress page and serve it securely.

Iframe Embed in Wordpress Utilized an iframe to embed the player in a wordpress page. This allowed me to easily embed the player in a wordpress page and serve it securely.

Code Snippets

<!DOCTYPE html>
<html>
<head>
    <title>Holy Moses CD</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="audioplayer.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

<body>
<div class="container">
                <div class="row justify-content-center">
     
<div id="audioPlayer">
     <img id="albumCover" src="https://s3.amazonaws.com/cloudzack.com/ghwc/holy-moses/image002.jpg" alt="Album Cover">
    <br><div id="currentTrack"></div>
<div id="progressContainer">
    <div id="progressBar">
        <div id="progress"></div>
    </div>
    <div id="timeDisplay">
        <div id="currentTime">0:00</div>
        <div id="remainingTime">0:00</div>
    </div>
</div>
<div id="controls">
    <div class="icon-container" onclick="skipBackward()">
        <i class="fas fa-undo-alt"></i>
        <span class="skip-time">15</span>
    </div>
    <i class="fas fa-step-backward" onclick="previousSong()"></i>
    <i class="fas fa-play" id="play" onclick="playSong(currentIndex)"></i>
    <i class="fas fa-pause" id="pause" style="display: none;" onclick="pauseSong()"></i>
    <i class="fas fa-step-forward" onclick="nextSong()"></i>
    <div class="icon-container" onclick="skipForward()">
        <i class="fas fa-redo-alt"></i>
        <span class="skip-time">15</span>
    </div>
</div>
<ul id="songLinks"></ul>
</div>

              </div>
            </div>


<script src="audioplayer.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
        

</body>
</html>
#audioPlayer {
    width: 100%;
    margin: 0 auto;
    background-color: #eee;
    padding: 20px;
    border-radius: 15px;
    text-align: center;
    
}

#audioPlayer button {
    margin: 10px 5px;
    padding: 5px 10px;
    font-size: 16px;
    border: none;
    background-color: #333;
    color: white;
    border-radius: 5px;
    cursor: pointer;
}

#currentTrack {
    font-size: 20px;
    margin-bottom: 10px;
}

#progressContainer {
    width: 100%;
}

#progressBar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 20px;
    background: #f3f3f3;
    border-radius: 10px;
    margin-bottom: 20px;
}

#timeDisplay {
    display: flex;
    justify-content: space-between;
    width: 100%;
}

#currentTime, #remainingTime {
    font-size: 0.8em;
}


#progress {
    height: 20px;
    background: #4caf50;
    border-radius: 10px;
    width: 0;
}

#controls i {
    font-size: 2.5em; /* Make the icon larger. Adjust this value as needed. */
    margin: 12px; /* Add space around the icon. Adjust this value as needed. */
    cursor: pointer; /* Change cursor to pointer when hovering over the icon */
}

#albumCover {
    border-radius: 15px;  /* Rounded corners */
    box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.3);  /* Drop shadow */
    max-width: 100%;  /* Responsive image, scales down if container is smaller */
    height: auto; /* Keep aspect ratio */
    display: block;  /* Center the image */
    margin-left: auto;
    margin-right: auto;
}
#songLinks {
    list-style: none;
    padding: 0;
    margin: 0;
    text-align: left;
}

#songLinks li {
    padding: 10px 0;
}

#songLinks a {
    text-decoration: none;
    color: #007BFF; /* Change this color to match your theme */
    font-size: 1.2em;
    transition: color 0.2s ease-in-out;
}

#songLinks a:hover,
#songLinks a:focus {
    color: #0056b3; /* Change this color to match your theme */
}

.icon-container {
    display: inline-block;
    position: relative;
    cursor: pointer;
    font-size: 0.6em;  /* This line makes the skip icons 80% the size of other icons */
}

.skip-time {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 0.7em;
}
var audioPlayer = document.getElementById("audioPlayer");
var audio = new Audio();
var trackTitle = document.getElementById("currentTrack");
var playButton = document.getElementById("play");
var pauseButton = document.getElementById("pause");
var trackList = [
    "https://www.ghwconline.org/wp-content/uploads/2023/05/01.-Trading-Places-Opener.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/02.-Dialogue.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/03.-Holy-Moses.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/04.-Dialogue.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/05.-The-Basket-Case.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/06.-Dialogue-Egyptian-Brick-Jingle.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/07.-I-Am.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/08.-Dialogue.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/09.-A-Plague-Of-Plagues.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/10.-Dialogue-Passover-Pita-Jingle.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/11.-No-One-Compares-To-You.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/12.-Dialogue-Almighty-Manna-Mix-Jingle.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/13.-The-Perfect-Ten.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/14.-Dialogue.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/15.-When-My-Heart-Turns-To-Him.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/16.-Dialogue.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/17.-I-Am-Bound-For-The-Promised-Land.mp3",
    "https://www.ghwconline.org/wp-content/uploads/2023/05/18.-Holy-Moses-Reprise.mp3"

];


var trackNames = trackList.map(url => url.split('/').pop());
var currentIndex = 0;

var trackNames = trackList.map(url => {
    var rawName = url.split('/').pop();
    var cleanName = rawName.replace(/-/g, ' ').replace('.mp3', '');
    return cleanName;
});

audio.src = trackList[currentIndex];
trackTitle.innerHTML = trackNames[currentIndex];

function selectSong(event, index) {
    event.preventDefault();
    currentIndex = index;
    playSong(currentIndex);
}

function scrub(e) {
    var scrubTime = (e.offsetX / progressContainer.offsetWidth) * audio.duration;
    audio.currentTime = scrubTime;
}

audio.ontimeupdate = updateProgress;

document.getElementById('progressContainer').addEventListener('click', scrub);


// Add links to songs
trackNames.forEach(function(name, index) {
    var li = document.createElement("li");
    var link = document.createElement("a");
    link.href = "#";
    link.innerHTML = name;
    link.onclick = function(e) { selectSong(e, index); };
    li.appendChild(link);
    songLinks.appendChild(li);
});


function playSong(index) {
    audio.src = trackList[index];
    var rawName = trackNames[index];
    var cleanName = rawName.replace(/-/g, ' ').replace('.mp3', '');
    trackTitle.innerHTML = cleanName;
    audio.play();
    playButton.style.display = "none";
    pauseButton.style.display = "inline-block";
}


function pauseSong() {
    audio.pause();
    playButton.style.display = "inline-block";
    pauseButton.style.display = "none";
}

function nextSong() {
    currentIndex = (currentIndex < trackList.length - 1) ? currentIndex + 1 : 0;
    playSong(currentIndex);
}

function previousSong() {
    currentIndex = (currentIndex > 0) ? currentIndex - 1 : trackList.length - 1;
    playSong(currentIndex);
}

audio.ontimeupdate = function() {
    var progress = document.getElementById('progress');
    var value = 0;
    if (audio.currentTime > 0) {
        value = Math.floor((100 / audio.duration) * audio.currentTime);
    }
    progress.style.width = value + "%";
};

audio.onended = function() {
    nextSong();
};


var currentTimeDisplay = document.getElementById("currentTime");
var remainingTimeDisplay = document.getElementById("remainingTime");

function updateProgress() {
    var progress = document.getElementById('progress');
    var value = 0;
    if (audio.currentTime > 0) {
        value = Math.floor((100 / audio.duration) * audio.currentTime);
    }
    progress.style.width = value + "%";

    var currentSeconds = Math.floor(audio.currentTime);
    var currentMinutes = Math.floor(currentSeconds / 60);
    var currentSeconds = currentSeconds % 60;
    currentTimeDisplay.innerHTML = currentMinutes + ":" + (currentSeconds < 10 ? "0" + currentSeconds : currentSeconds);

    var totalSeconds = Math.floor(audio.duration - audio.currentTime);
    var remainingMinutes = Math.floor(totalSeconds / 60);
    var remainingSeconds = totalSeconds % 60;
    remainingTimeDisplay.innerHTML = remainingMinutes + ":" + (remainingSeconds < 10 ? "0" + remainingSeconds : remainingSeconds);
}


audio.ontimeupdate = updateProgress;

audio.onended = function() {
    nextSong();
};

function skipForward() {
    audio.currentTime = Math.min(audio.duration, audio.currentTime + 15);
    updateProgress();
}

function skipBackward() {
    audio.currentTime = Math.max(0, audio.currentTime - 15);
    updateProgress();
}

Summary

Project Reflections: This was a short and simple project that was a great learning experience and also helped out the kids and teachers who ran the children’s musical. It was a great opportunity to deepen my skills in JavaScript and HTML5 audio. I was able to learn a lot about how to build a simple audio player and how to embed it in a wordpress page. It was so vital for relationship building with the kids and I am excited to see how it helps them grow in their understanding of music and singing.