Not really sure how to title this, but that's the best I could come up with.

I've created a music player in the browser for my own personal use (so there's no emphasis on cross-browser or anything like that). It all works dandy except I am having issues when pressing the space bar.

Currently songs are listed on the page based on a search criteria. Each song has a play/pause button that reflects what the current state is. When the button is clicked it executes the appropriate command to the player (.play() or .pause()). I have also added in some jQuery code so when the space bar is pressed, it acts like I am clicking the corresponding button. Up to this everything works correctly. The space bar fails however in one very specific scenario: If I click the play/pause button and then press the space bar without clicking or doing anything else, it sends two "click"/"press" commands to player, effectively creating a stutter effect since the player is now pausing and then immediately playing, or playing and then immediately pausing. I have viewed the "double" click in the Firebug console.

I have searched quite a bit online but have found nothing relevant. To be clear, I am already hiding the player's controls by omitting the "controls" attribute. However I have tried troubleshooting with them present, and I think I am onto something. By default Firefox (and it would seem other browsers, as this issue does exist in both Chrome and IE11) allows you to play and pause the audio with the space bar if you are focused on the player. And I think that's the kicker: When the .play() or .pause() function executes, it appears as though it inherently gives focus to the audio element, even though this focus doesn't seem to be loggable by Firebug.

I have tried a couple rudimentary things to try to "counteract" this inherent focusing such as triggering a focus or click event on other elements on the page, but it doesn't seem to make any difference.

Perhaps most importantly... by adding .preventDefault() to the event doesn't stop the audio element from being controlled by the keyboard. In other words, without trying to do anything else, if I tell the event to test for the space bar and to prevent default, if I click on the audio element (when it has the controls attribute since it's then visible) and then press the space bar it will play or pause the song. I can't seem to find anyway to stop this.

Does anyone have any insight into this? Thanks for reading!

    Set a timeout creating a min time between events:

    $action_allowed = true;
    
    $('button/spacebar').event('whatever', function($e) {
        // check if we've executed this event recently
        if(!$action_allowed) {
            $e.preventDefault();
            return;
        }
    
    // do your stuff
    
    // set variable to false, and give it a timeout before being true
    $action_allowed = false;
    setTimeout(function() {
        $action_allowed = true;
    }, 500);
    });
    

      Thanks for the reply, Derokorian.

      Unfortunately it doesn't work exactly how it should. While the timeout is "running" it works as intended except of course the space bar is disabled. However, once the timeout has "timed out" it goes back to its original behaviour (until you trigger the timeout again, which causes the stutter but only after the timeout has timed out). I've tried implementing it in numerous ways but it just doesn't work correctly.

      It just baffles me that this sort of thing cannot be controlled via code. At least no way that I no of yet. I guess I could try looking at some jQuery audio players out there and see how they do it. I think I'll just mark this as "maybe later" and take a look at it again when I have more time.

        If you wanted to post some code, I'd be happy to take a whack at it for you (especially with all the help you've given me in the past).

          So here's the actual event of the space bar. There's a search bar at the top of the page so obviously the space bar needs to work correctly if it has focus.

          //Press spacebar to pause or play the current song
          $(window).keypress(function(event)
          {
              if(event.which === 32 && !$('#search').is(':focus'))
              {
                  event.preventDefault();
          	change_song(index);
              }
          });
          

          The change_song() function is below.

          function change_song(i)
          {
              var song = $('div.search-result:visible').eq(i);
          
          var button = song.find('button');
          var new_source = song.find('span.source').text();
          var current_source = $('#player source').attr('src');
          var cover = song.find('span.cover').text();
          var title = song.find('span.song').text();
          var year = song.find('span.year').text();
          var album = song.find('span.album').text();
          var genre = song.find('span.genre').text();
          
          if(new_source != current_source)
          {
          $('img.cover').attr('src', cover);
          
          $('#progress').attr('value', 0.0);
          
          $('div.search-result button').removeClass('playing');
          button.addClass('playing');
          
          player.paused;
          $('#player source').attr('src', new_source);
          player.load();
          player.play();
          
          $('#title + label span').text(title);
          $('#album + label span').text(album);
          $('#year + label span').text(year);
          $('#genre + label span').text(genre);
          
          $('div.left, div.right').addClass('playing');
          
          $('title').text(title);
          
          var width = Math.round($('div.right').width() * 0.25);
          var current_max_width = $('div.left').outerWidth();
          
          if(width > current_max_width || $('div.left').attr('style') === undefined)
          {		
          	$('div.left').css('max-width', width + 'px');
          }
          }
          else
          {		
          if(player.paused)
          {
          	player.play();
          	button.addClass('playing');
          }
          else
          {
          	player.pause();
          	button.removeClass('playing');
          }
          }
          }
          

          It could use some cleanup, but for now it's "working as intended". The index is simply the index of the song on the page. Basically I have a PHP script that recursively searches through my music library collecting information about albums and songs (found a neat PHP library that reads metatag info) and generates an HTML file which I just include into my page. While I type in the search bar, it looks in certain spots (AKA, song title) and then hides or shows the results. Generating it beforehand allows it to be pretty much instant which is cool.

          For reference this is the code for when the play/pause button is clicked:

          //Click the play button
          $('div.right').on('click', 'div.search-result button', function(event)
          {
              var button = $(this);
              var parent = button.parent();
              index = $('div.search-result:visible').index(parent);
              change_song(index);
          });
          

          The only other time change_song() is called is 'ended' when the current song finishes. It increments the index and passes the new index to the change_song() function, effectively starting the next song since the sources are different, otherwise program flows to the "else" in the if statement.

          Last night I also quickly tried adding an event listener to the audio tag (both via jQuery and through traditional JavaScript means) to listen for keypress, keydown, and keyup and to .preventDefault(), but it had no effect.

          Also for reference here's some typical markup for a song on the page. I am using an image sprite for the button for "pause" and "play" images.

          <div class="search-result song">
              <button></button>
              <span class="title">Metallica - Hit the Lights</span>
              <span class="duration">4:18</span>
              <span class="hidden source">music/Metallica [US]/[1983] Kill 'Em All/Metallica - 01 - Hit the Lights.mp3</span>
              <span class="hidden type">audio/mpeg</span>
              <span class="hidden cover">music/Metallica [US]/[1983] Kill 'Em All/cover.jpg</span>
              <span class="hidden album">Kill 'Em All</span>
              <span class="hidden band">Metallica</span>
              <span class="hidden song">Hit the Lights</span>
              <span class="hidden year">1983</span>
              <span class="hidden genre">Thrash Metal</span>
              <span class="hidden track">01</span>
          </div>
          

          Thank you for taking a look at this. If you need any more info just let me know.

            But if I understand things correctly, a click on the space bar should always play / pause a song, because you will always have index set and space is bound to window, not any individual button or the audio player.

            Also, you should be able to determine the element bubble-chain. I do not remember what it is called off-hand, and it differs between browsers, but using chrome there should be an array on the event object from the original event target up to the element on which the event handler is installed. E.g. if you hit space while a button is active and the event handler is installed on window, that array might hold
            0: button
            1: div
            2: body
            3: window

            Looking at that array, you can easily see where the event originated and where it was caught.

              johanafm;11045417 wrote:

              But if I understand things correctly, a click on the space bar should always play / pause a song, because you will always have index set and space is bound to window, not any individual button or the audio player.

              Yes my intention was to be able to pause or play a song at any time so long as the search bar doesn't have focus; however, that part isn't the issue. The issue is my event is fired but so is the inherent control on the audio element. If I were to remove my space bar event entirely and click the pause/play button, I could still hit the space bar and it would pause/play the song. But as soon as I click somewhere else on the page, I cannot. It's as if the .play() and .pause() functions give an inherent focus to the audio element (that I am seemingly unable to track/test for in jQuery). The problem is it's firing both, but so far I have not been able to find out a way to disable the inherent keyboard controls on the audio tag.

              Remember this "double play/double pause" anomaly only occurs when I click the play/pause button and then immediately press the space bar after. If I click somewhere on the page then it's not an issue. 99% of the time it shouldn't really affect me, but since I've come up against this wall now I am determined to figure something out (such is the nature of programming...).

              johanafm;11045417 wrote:

              Also, you should be able to determine the element bubble-chain. I do not remember what it is called off-hand, and it differs between browsers, but using chrome there should be an array on the event object from the original event target up to the element on which the event handler is installed. E.g. if you hit space while a button is active and the event handler is installed on window, that array might hold
              0: button
              1: div
              2: body
              3: window

              Looking at that array, you can easily see where the event originated and where it was caught.

              I will look into this. I have Chrome installed but I am sure Firebug can check this as well. I am hoping something is triggered because so far all attempts to even track the inherent keyboard control have been futile.

                Ah now I get what you mean.

                The audio src attribute seems to always resolve to a fully qualified URL, which includes protocol. E.g. &#8221;http://example.com/music/song.mp3&#8221; rather than simply &#8221;/music/song.mp3&#8221;. This will make your comparison fail every time

                if(new_source != current_source)
                
                  johanafm;11045439 wrote:

                  Ah now I get what you mean.

                  The audio src attribute seems to always resolve to a fully qualified URL, which includes protocol. E.g. &#8221;http://example.com/music/song.mp3&#8221; rather than simply &#8221;/music/song.mp3&#8221;. This will make your comparison fail every time

                  if(new_source != current_source)
                  

                  It doesn't fail. I know through all the testing I have done in the last couple of days, and just now I put in a console.log('new song') in the IF portion and a console.log('same song') in the ELSE and it executes as intended.

                    Holy crap I think I got it!

                    Originally I had my space bar event bound to keyup, but that wasn't preventing the screen from scrolling, as is default browser behaviour (it played/paused the songs fine though). I didn't remember running into this weird anomaly. After some research I found that to prevent that you need to bind to keypress, which I did and it worked. It was only after I discovered this weird bug/anomaly - but I never really put 2 and 2 together.

                    So with that in mind, my code now looks like this:

                    //Press spacebar to pause or play the current song
                    $(window).keyup(function(event)
                    {
                        if(event.which === 32 && !$('#search').is(':focus'))
                        {
                    	event.preventDefault();
                    	change_song(index);
                        }
                    }).keypress(function(event)
                    {
                        if(event.which === 32 && !$('#search').is(':focus'))
                        {
                    	event.preventDefault();
                        }
                    });
                    

                    I can now play/pause a song at any time so long as I am not focused in the search bar, and if I click the play/pause button of a song and then immediately press the space bar after, it doesn't do the stutter behaviour.

                    shrug

                      Write a Reply...