FeedBagel/12daysofweb.dev/Audio APIAudio API·4y agoprogrammingRead on 12daysofweb.devFrom the articleWhile you're not likely to be embedding audio on the web every day, you never know when it might be useful! The API is vast, and complements the native HTML element. We'll review the element first, then look at a few parts of the API to build a simple custom player. The audio HTML element # The HTML element allows you to embed audio anywhere, and adding the controls attribute also displays the browser's audio player. The results of that HTML will display a little differently depending on your browser. Audio converted and edited from the Jingle Bells midi . Additional available attributes outside of controls include autoplay , loop , and muted . You can also include elements as child elements of instead of using the src attribute. You may also define what, if anything, to preload, where the default is different per browser. The option selected for preloading depends on how you intend to use the audio element Options include none, metadata, and auto. Creating a custom audio player # The primary issue with the native player is that there is no cross-browser styling control over its elements. And each browser player is also inconsistent in which controls they provide. Chromium : play/pause, duration and time elapsed, timeline scrubber, volume, and additional menu to download or select playback speed Firefox : play/pause, duration and time elapsed, timeline scrubber, and volume Safari : play/pause, timeline scrubber, and time elapsed For our custom player, we're using a reduced scope since this is intended as an introduction to this API. There are certainly more features you could add, but we'll focus on: Displaying track title (static) Duration and time elapsed Play/pause state toggle Mute/unmute state toggle Audio player HTML # First, we'll build out semantic, accessible HTML for our custom audio player. I encourage always starting with this step (yes, even if you're using a framework). We'll create a container with the role of "group" to help define the boundaries of our custom player for assistive technology (AT), as well as an aria-label to label the group "Audio player". And, we'll go ahead and add an element as the first child. Note that we're specifically defining the preload here since we are planning to use the metadata for display. < div role = " group " aria-label = " Audio player " class = " audio-player " > < audio preload = " metadata " src = " jingle-bells.mp3 " > audio > div > Since we haven't added the controls attribute, browsers will default to hiding the audio element. Next, we'll use a headline element to label what is currently playing: < h3 class = " audio-player__current " > < small > Playing: small > Jingle Bells h3 > Following that, we'll add two button elements to use as our player toggles for play and mute actions. We're wrapping the text in a span which we'll accessibly hide so that it is used for the label by AT. The icon svg include aria-hidden="true" since they are decorative as far as AT is concerned given the accessible labeling text. < button data-action = " play " data-state = " paused " class = " audio-player__button " type = " button " > < span class = " audio-player__label " > Play span > < svg data-play height = " 24 " width = " 24 " aria-hidden = " true " > < path d = " M8 5v14l11-7z " /> svg > < svg data-pause hidden height = " 24 " width = " 24 " > < path d = " M6 19h4V5H6v14zm8-14v14h4V5h-4z " /> svg > button > < button data-action = " mute " class = " audio-player__button " type = " button " aria-pressed = " false " > < span class = " audio-player__label " > Mute span > < svg height = " 24 " width = " 24 " aria-hidden = " true " > svg > button > There are two SVG icons for a "play" and "pause" icon, where the "pause" initially has the hidden attribute so that it is not displayed. We'll toggle the icons' hidden attribute as well as the label content when it is selected. The other important attributes on the buttons are: data-action : used for finding the element in JS. We're avoiding an ID in case of multiple audio players and avoiding a class to discourage removing or changing the name aria-pressed : identifies the mute button as a toggle button for AT and will convey the changed state which will update dynamically in JS Finally, we'll use a paragraph to contain the time information and set default zero values. Here again, we're preparing our markup to hide the AT labels, so the final visual will be 0:00 / 0:00 . < p class = " audio-player__time " > < span class = " audio-player__label " > Elapsed time span > < span data-time = " remaining " > 0:00 span > / < span class = " audio-player__label " > Total time span > < span data-time = " total " > 0:00 span > p > Audio player styles # Since our audio player is completely made of custom elements now, we have full styling control. We'll be leveraging CSS grid to arrange the elements. You can expand the styles to review them fully, and lookout for a few key features: We hide our labels using the styles from Scott O'Hara's visually hidden class On the .audio-player container, we set width using fit-content which visually shrinks the player to its intrinsic width where supported For the toggle buttons SVG, we set pointer-events: none so that the click event "falls through" to the button element The time content has set font-variant-numeric: tabular-nums which, when supported by the font, requests that numeric values take on monospace behavior, which prevents our audio player's width visibly adjusting when wider or narrower numbers are displayed Audio player CSS .audio-player * { margin : 0 ; } .audio-player__label { clip : rect ( 0 0 0 0 ) ; clip-path : inset ( 50% ) ; height : 1px ; overflow : hidden ; position : absolute ; white-space : nowrap ; width : 1px ; } .audio-player { padding : 1rem ; background : #222 ; color : #fff ; border-radius : 0.5rem ; display : grid ; grid-template-columns : auto auto 1fr ; align-items : center ; width : fit-content ; gap : 1rem ; } .audio-player__current { grid-column : 1 / -1 ; display : flex ; align-items : center ; gap : 0.25em ; font-size : 1.25rem ; font-weight : bold ; } .audio-player__current small { font-weight : normal ; text-transform : uppercase ; } .audio-player__button { all : unset ; cursor : pointer ; display : inline-grid ; place-content : center ; border-radius : 0.25rem ; border : 2px solid #fff ; padding : 0.15em ; } .audio-player__button:focus { outline : 2px solid #fff ; outline-offset : 2px ; } .audio-player__button svg { fill : currentcolor ; pointer-events : none ; } .audio-player__button[aria-pressed=true], .audio-player__button[data-state=playing] { background-color : #fff ; color : #222 ; } .audio-player__time { font-variant-numeric : tabular-nums ; } Playing: Jingle Bells Play Mute Elapsed time 0:00 / Total time 0:00 Audio player API events # Both the audio and video API inherit the HTMLMediaElement interface . There are many properties, methods, and events that you can use. We'll hone in on some of the most essential for our simple player. The capabilities we need for our audio player: Toggle the aria-pressed state for the play and mute action buttons Toggle play state Toggle mute state Set the total duration Update the elapsed timestamp during playback Reset the elapsed timestamp and play toggle state when the audio ends First, we'll find each element, including the since we directly respond to events and set properties on it. const audioElement = document . querySelector ( ".audio-player audio" ) ; const audioPlay = document . querySelector ( '[data-action="play"]' ) ; const playLabel = audioPlay . querySelector ( '.audio-player__label' ) ; const playIcon = audioPlay . querySelector ( '[data-play]' ) ; const pauseIcon = audioPlay . querySelector ( '[data-pause]' ) ; const audioMute = document . querySelector ( '[data-action="mute"]' ) ; const audioTimeRemaining = document . querySelector ( '[data-time="remaining"]' ) ; const audioTimeTotal = document . querySelector ( '[data-time="total"]' ) ; Our play button functionality is the most complex. For setting play versus pause, we call those functions against the audio element. Additionally, we update the data-state which is tied to styles, as well as change the text content of the label. Last, we use the handle toggleAttribute JS function to toggle the hidden attribute of the icons to efficiently flip their visibility. audioPlay . addEventListener ( "click" , ( e ) => { const isPlaying = audioPlay . dataset . state === "playing" ; if ( isPlaying ) { audioElement . pause ( ) ; audioPlay . dataset . state = 'paused' ; playLabel . textContent = "Play" ; } else { audioElement . play ( ) ; audioPlay . dataset . state = 'playing' ; playLabel . textContent = "Pause" ; } playIcon . toggleAttribute ( 'hidden' ) ; pauseIcon . toggleAttribute ( 'hidden' ) ; } ) ; For mute, we toggle the boolean muted property and aria-pressed state. audioMute . addEventListener ( "click" , ( e ) => { const isMuted = audioMute . getAttribute ( "aria-pressed" ) === "true" ; audioMute . setAttribute ( "aria-pressed" , ! isMuted ) ; audioElement . muted = ! isMuted ; } ) ; Now that the toggles are working, it's time to handle the time updates. There are three events we'll listen to: loadedmetadata : allows us to confidently set the total time by getting the duration timeupdate : fires during playback and we'll use it to update the elapsed time ended : signals the audio file has completed playback which we'll use to reset the player state Since the time is calculated in seconds, we'll first create a function to format it to mm:ss for display . const formatAudioTime = ( time ) => { if ( isNaN ( time ) ) return ; const minuteValue = Math . floor ( time / 60 ) ; let secondValue = Math . floor ( time - minuteValue * 60 ) ; if ( secondValue < 10 ) { secondValue = "0" + secondValue ; } return minuteValue + ":" + secondValue ; } ; Then, we'll call the formatting function for setting the total time upon the loadedmetadata event and setting the time remaining on the timeupdate event. audioElement . addEventListener ( "loadedmetadata" , ( ) => ( audioTimeTotal . textContent = formatAudioTime ( audioElement . duration ) ) ) ; audioElement . addEventListener ( "timeupdate" , ( ) => ( audioTimeRemaining . textContent = formatAudioTime ( audioElement . currentTime ) ) ) ; Finally, we'll complete our player by resetting the play button state and remaining time upon the ended event. audioElement . addEventListener ( "ended" , ( ) => { audioPlay . dataset . state = 'paused' ; playLabel . textContent = "Play" ; playIcon . toggleAttribute ( 'hidden' ) ; pauseIcon . toggleAttribute ( 'hidden' ) ; audioTimeRemaining . textContent = "0:00" ; } ) ; The following is our final demo as well as the complete script. Audio player JS // .playable scoping due to previous CSS demo // ⚡️ Challenge: Adapt this to work for multiple players per page const audioElement = document . querySelector ( ".playable.audio-player audio" ) ; const audioPlay = document . querySelector ( '.playable [data-action="play"]' ) ; const playLabel = audioPlay . querySelector ( '.audio-player__label' ) ; const playIcon = audioPlay . querySelector ( '[data-play]' ) ; const pauseIcon = audioPlay . querySelector ( '[data-pause]' ) ; const audioMute = document . querySelector ( '.playable [data-action="mute"]' ) ; const audioTimeRemaining = document . querySelector ( '.playable [data-time="remaining"]' ) ; const audioTimeTotal = document . querySelector ( '.playable [data-time="total"]' ) ; audioPlay . addEventListener ( "click" , ( e ) => { const isPlaying = audioPlay . dataset . state === "playing" ; if ( isPlaying ) { audioElement . pause ( ) ; audioPlay . dataset . state = 'paused' ; playLabel . textContent = "Play" ; } else { audioElement . play ( ) ; audioPlay . dataset . state = 'playing' ; playLabel . textContent = "Pause" ; } playIcon . toggleAttribute ( 'hidden' ) ; pauseIcon . toggleAttribute ( 'hidden' ) ; } ) ; audioMute . addEventListener ( "click" , ( e ) => { const isMuted = audioMute . getAttribute ( "aria-pressed" ) === "true" ; audioMute . setAttribute ( "aria-pressed" , ! isMuted ) ; audioElement . muted = ! isMuted ; } ) ; const formatAudioTime = ( time ) => { if ( isNaN ( time ) ) return ; const minuteValue = Math . floor ( time / 60 ) ; let secondValue = Math . floor ( time - minuteValue * 60 ) ; if ( secondValue < 10 ) { secondValue = "0" + secondValue ; } return minuteValue + ":" + secondValue ; } ; audioElement . addEventListener ( "loadedmetadata" , ( ) => ( audioTimeTotal . textContent = formatAudioTime ( audioElement . duration ) ) ) ; audioElement . addEventListener ( "timeupdate" , ( ) => ( audioTimeRemaining . textContent = formatAudioTime ( audioElement . currentTime ) ) ) ; audioElement . addEventListener ( "ended" , ( ) => { audioPlay . dataset . state = 'paused' ; playLabel . textContent = "Play" ; playIcon . toggleAttribute ( 'hidden' ) ; pauseIcon . toggleAttribute ( 'hidden' ) ; audioTimeRemaining . textContent = "0:00" ; } ) ; Playing: Jingle Bells Play Mute Elapsed time 0:00 / Total time 0:00 Ideas for using the audio API # Audio elements with no controls can be used to help respond to play() commands to add fun audio enhancements. Click Jason's head for inspiration . Be sure to also provide the user a toggle to turn off all sounds. Create a more full-featured player including visualizations Use the API to aid animation like Andrew Rubin does to animate a cartoon band Have a podcast? Now you can create a custom player embed! Additional resources # Learn with Jason from Lindsey Kopacz to create an accessible audio player MDN has a large section with many guides to explore the audio API Check out what all is available for the HTMLMediaElement (which applies to video, too) If you're wondering why we didn't use aria-pressed for the play button, learn from Sarah Higley about conveying the state of toggle buttons to ATContinue reading on 12daysofweb.devYou might also wanna readWeb Audio Studio: Interactive Visual Tool for Learning Web Audio APIWeb Audio Studio is an interactive browser-based tool that allows developers to learn and experiment with the Web Audio API through visual pwebaudio.studio·4mo agoExploring YouTube audio streaming in Emacs with ready-playerThe article discusses the author's experience using the Emacs package 'ready-player' for music listening, and their desire to extend it for xenodium.com·16d agoAudioPod Music Generation API: Add Soundtracks to Any AI AppAAudioPod AI·1mo agoGapless.js: JavaScript Plugin for Seamless Audio Playback on the WebGapless.js is a JavaScript plugin for seamless audio playback on the web that uses HTML5 audio and Web Audio API to enable gapless transitiogithub.com·4mo agoChrome 151 introduces the <usermedia> HTML element for declarative camera and microphone accessChrome 151 introduces the <usermedia> HTML element, a new Capability Element that provides a declarative, user-activated control for accessideveloper.chrome.com·7d agoA Comprehensive Overview of Espressif's Audio Effects libraryDdocs.espressif.com·1y agoCommentsSign in to join the conversation.No comments yet. Be the first.audio-api
Comments
Sign in to join the conversation.
No comments yet. Be the first.