7

I have a music blog that contains various embedded soundcloud and youtube players.

What I want to do is prevent any audio from playing simultaneously. In other words while I am playing a youtube video, if I click to play the soundcloud embed I want the youtube player to pause and vice versa.

I have developed code that pauses the streaming youtube player if I click to play another youtube player (soundcloud already does this inherently). I just need to make it cross compatible. Really would appreciate some help, thanks.

var playerCurrentlyPlaying = null;
var players = {}
YT_ready(function() {
   $(".youtube_embed").each(function() {
       var identifier = this.id;
       var frameID = getFrameID(identifier);
       if (frameID) {
            players[frameID] = new YT.Player(frameID, {
                events: {
                    "onStateChange": function(event){
                        if (event.data == YT.PlayerState.PLAYING)
                        {
                            if(playerCurrentlyPlaying != null &&
                               playerCurrentlyPlaying != frameID)
                            callPlayer( playerCurrentlyPlaying , 'pauseVideo' );
                            playerCurrentlyPlaying = frameID;
                        }
                    }
                }
            });
        }
   });
});

I have used functions from these sources: Listening for Youtube Event in JavaScript or jQuery

YouTube iframe API: how do I control a iframe player that's already in the HTML?

YouTube API Target (multiple) existing iframe(s)

Rendered HTML using UniqueID():

<span class="soundcloud_embed" id="soundcloud_post_312">
  <iframe id="ui-id-1" width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F103518792&show_artwork=true&secret_token=s-LnOTK"></iframe>
</span>

<span class="youtube_embed" id="youtube_post_309">
  <iframe id="ui-id-2" width="528" height="190" src="//www.youtube.com/embed/Y3CYKXBEtf0" frameborder="0" allowfullscreen></iframe>
</span>
Community
  • 1
  • 1
Jaqx
  • 826
  • 3
  • 14
  • 39

2 Answers2

6

First of all, things will be greatly simplified if you are able to get an ID onto the iframes themselves, like this:

<span class="soundcloud_embed">
  <iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F4997623" id="sound1"></iframe>
</span>
<span class="soundcloud_embed">
  <iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F105153133" id="sound2"></iframe>
</span>
<span class="youtube_embed">
  <iframe width="400" height="225" src="//www.youtube.com/embed/tv8WgLEIPBg" id="vid1" frameborder="0" allowfullscreen></iframe>
</span>
<span class="youtube_embed">
  <iframe width="400" height="225" src="//www.youtube.com/embed/FmICU1gMAAw" id="vid2" frameborder="0" allowfullscreen></iframe>
</span>

If you'd prefer not to hack the auto_html gem that you mention in order to get IDs, you can either use jquery's .uniqueId() function to generate them on all iframes or use the getFrameID() helper method from the post you referred to (and seem to already be using).

To make it as easy as possible to get the two APIs (Youtube and Soundcloud) to be able to respond to each other's events, you can use a couple of control objects that keep track of which players are on your page and which of them is currently playing (this strategy is similar to that employed by the links you referred to, but are expanded to be able to keep track of which player belongs to which API). With it, define a generic pause function that serves as a simplistic wrapper for both APIs:

var playerCurrentlyPlaying = {"api":null,"frameID":null};
var players = {"yt":{},"sc":{}};

pauseCurrentPlayer=function() {
  var api=playerCurrentlyPlaying["api"],
      frameid=playerCurrentlyPlaying["frameID"];

  switch(api) {
    case "yt":
      players[api][frameid].pauseVideo();
      break;
    case "sc":
      players[api][frameid]["widget"].pause();
      break;
  }
};

Next, you'll want to define two separate functions; the first will be called whenever a YouTube play event is captured, and the second whenever a Soundcloud play event is captured. Currently the Soundcloud HTML5 Widget has a few bugs that force the necessity to include some pretty ugly hacks--namely, the Play event sometimes isn't fired when you play a Soundcloud sound for the first time. Luckily, the Play_Progress event is, so we can leverage that, but must also include a workaround so it doesn't create a race condition when pausing a sound and starting a video:

onYTPlay =function(frameid) {
  if (playerCurrentlyPlaying["frameID"]!=frameid && playerCurrentlyPlaying["frameID"]!=null) {
     pauseCurrentPlayer();
  }
  playerCurrentlyPlaying["api"]="yt";
  playerCurrentlyPlaying["frameID"]=frameid;
};

onSCPlay=function(frameid,event) {
  if (event==SC.Widget.Events.PLAY||players["sc"][frameid]["firstplay"]==true) {
     if (playerCurrentlyPlaying["api"]=="yt") { // because any soundcloud player will be automatically paused by another soundcloud event, we only have to worry if the currently active player is Youtube
        pauseCurrentPlayer();
     }
     playerCurrentlyPlaying["api"]="sc";
     playerCurrentlyPlaying["frameID"]=frameid;
     players["sc"][frameid]["firstplay"]=false;
  }
};

Finally, you can then add your hooks to the Youtube embeds and the Soundcloud embeds, remembering the hack we have to put in for the Soundcloud Play event. Don't forget to embed the Soundcloud Widget API (<script src="w.soundcloud.com/player/api.js"; type="text/javascript"></script>), and then use this code to do your bindings:

var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

function onYouTubeIframeAPIReady() {
  $(".youtube_embed iframe").each(function() {
    players["yt"][$(this).attr('id')] = new YT.Player($(this).attr('id'), {
      events: { 'onStateChange': onYTPlayerStateChange }
    });
  });
}

onYTPlayerStateChange = function(event) {
  if (event.data==YT.PlayerState.PLAYING) {
     onYTPlay(event.target.a.id);
  }
};

(function(){
    $(".soundcloud_embed iframe").each(function() {
      var frameid=$(this).attr('id');
      players["sc"][frameid]={};
      players["sc"][frameid]={"widget":SC.Widget(document.getElementById(frameid)),"firstplay":true};
      players["sc"][frameid]["widget"].bind(SC.Widget.Events.READY, function() {
       players["sc"][frameid]["widget"].bind(SC.Widget.Events.PLAY, function() {
        onSCPlay(frameid,SC.Widget.Events.PLAY);
       });
       players["sc"][frameid]["widget"].bind(SC.Widget.Events.PLAY_PROGRESS, function() {
        onSCPlay(frameid,SC.Widget.Events.PLAY_PROGRESS);
       });
     });
    });
   }());

This approach is set up to handle multiple players of both APIs, and should be decently extensible if you want to support other embeddable media widgets (provided they expose an event when they start to play and they have the ability to programmatically pause players).

jlmcdonald
  • 13,408
  • 2
  • 54
  • 64
  • 1
    Firstly, Thank you for your time and this fantastic write up. Please take a look at my updated section. I'm not sure if this line in the pause function is working: players[api][frameID].pauseVideo();. When I replace the function back with the callplayer i had previously, the YTs pause as orginial – Jaqx Aug 15 '13 at 08:49
  • I wonder if there might be a typo or other problem somewhere else in your code ... when I used your ready function instead of the one I offered (where I'd extrapolated out the callbacks into their own functions instead of embedding them), but I kept everything else the same, the YT players still paused properly using my pauseCurrentPlayer() function. Are you getting any console errors? – jlmcdonald Aug 15 '13 at 16:04
  • Do note as well that, if you use that getFrameId and callplayer helper functions, you'll have to modify them to be able to work with soundcloud as well, given that they use regexes to make them YT specific (hence I said at first it was simpler to ignore the helper functions altogether and just put IDs on your iframes yourself or add them with jQuery's .uniqueID() function before running any other code). – jlmcdonald Aug 15 '13 at 16:05
  • I get this error in the JS console when I click around on some YT players: Uncaught TypeError: Object # has no method 'pauseVideo' application.js?body=1:179 pauseCurrentPlayer application.js?body=1:179 players.yt.(anonymous function).YT.Player.events.onStateChange. – Jaqx Aug 16 '13 at 01:00
  • This sounds like the wrong ID is being saved as a key in the players object ... it's not using an actual ID string but is using the function itself (as if getFrameID isn't returning what it is supposed to). Doublecheck that function to make sure it's implemented correctly (and test to make sure that what is coming back from it is an actual ID string, used as a key when referencing the player object). Just to cover your bases, have you tried eschewing the helper functions and just using the code above? I'm not saying that's the only way, but it may help isolate the problem. – jlmcdonald Aug 16 '13 at 04:46
  • Can you please take a look at my update code section. playing SC doesn't seem to pause youtube. SC api seems unresponsive. – Jaqx Aug 19 '13 at 01:16
  • This was my fault ... I forgot to put in the original code which soundcloud API you need. It isn't the sdk, but the HTML5 widget API ... put this in your partial instead of the reference to the soundcloud SDK.js file: now fixed in my code above. Also, in your partial you may want to take the script tags (that bind the players) out of your if loops. It doesn't seem to hurt that they're inside, but you're basically binding things multiple times, which could lead to inefficiencies. – jlmcdonald Aug 19 '13 at 04:15
  • 2
    In case it's helpful, here's a complete working fiddle: http://jsfiddle.net/jlmcdonald/NqRqm/1/ – jlmcdonald Aug 19 '13 at 04:17
  • Thanks so much, it works well. I don't want to ask too much of you but do you know how I could auto-play one embed after another? For it to know which embed to play next i guess the only way is to look at the ID of the frame currentlyplaying and on playerstate END play the frame with it's id + 1? I can post this as a different question with another bounty if you are willing to help out. If not, I'm grateful for all your help up to this point! – Jaqx Aug 19 '13 at 04:58
  • I think you ought to post it as another question, as SO works best when each question is tightly focused and doesn't turn into extended conversations ... I'm happy to look at it at work tomorrow, and you could get others looking at it, too. – jlmcdonald Aug 19 '13 at 05:03
  • Thanks, whenever you wish to take a look: http://stackoverflow.com/questions/18311192/auto-play-youtube-and-soundcloud-embeds-after-one-another – Jaqx Aug 19 '13 at 10:10
0

it's possible. check widget api playground. you can watch all state events from soundcloud iframe widget. this can be done on flash embed too. https://w.soundcloud.com/player/api_playground.html

watch for SC.Widget.Events.PLAY {"loadedProgress":0,"currentPosition":0,"relativePosition":0} event on start playing

cucko
  • 1,436
  • 11
  • 25